##// END OF EJS Templates
scmutil: clean up use of two-argument raise...
Augie Fackler -
r18177:203b7a75 default
parent child Browse files
Show More
@@ -1,962 +1,962
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import util, error, osutil, revset, similar, encoding, phases
10 10 import match as matchmod
11 11 import os, errno, re, stat, sys, glob
12 12
13 13 def nochangesfound(ui, repo, excluded=None):
14 14 '''Report no changes for push/pull, excluded is None or a list of
15 15 nodes excluded from the push/pull.
16 16 '''
17 17 secretlist = []
18 18 if excluded:
19 19 for n in excluded:
20 20 ctx = repo[n]
21 21 if ctx.phase() >= phases.secret and not ctx.extinct():
22 22 secretlist.append(n)
23 23
24 24 if secretlist:
25 25 ui.status(_("no changes found (ignored %d secret changesets)\n")
26 26 % len(secretlist))
27 27 else:
28 28 ui.status(_("no changes found\n"))
29 29
30 30 def checknewlabel(repo, lbl, kind):
31 31 if lbl in ['tip', '.', 'null']:
32 32 raise util.Abort(_("the name '%s' is reserved") % lbl)
33 33 for c in (':', '\0', '\n', '\r'):
34 34 if c in lbl:
35 35 raise util.Abort(_("%r cannot be used in a name") % c)
36 36
37 37 def checkfilename(f):
38 38 '''Check that the filename f is an acceptable filename for a tracked file'''
39 39 if '\r' in f or '\n' in f:
40 40 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
41 41
42 42 def checkportable(ui, f):
43 43 '''Check if filename f is portable and warn or abort depending on config'''
44 44 checkfilename(f)
45 45 abort, warn = checkportabilityalert(ui)
46 46 if abort or warn:
47 47 msg = util.checkwinfilename(f)
48 48 if msg:
49 49 msg = "%s: %r" % (msg, f)
50 50 if abort:
51 51 raise util.Abort(msg)
52 52 ui.warn(_("warning: %s\n") % msg)
53 53
54 54 def checkportabilityalert(ui):
55 55 '''check if the user's config requests nothing, a warning, or abort for
56 56 non-portable filenames'''
57 57 val = ui.config('ui', 'portablefilenames', 'warn')
58 58 lval = val.lower()
59 59 bval = util.parsebool(val)
60 60 abort = os.name == 'nt' or lval == 'abort'
61 61 warn = bval or lval == 'warn'
62 62 if bval is None and not (warn or abort or lval == 'ignore'):
63 63 raise error.ConfigError(
64 64 _("ui.portablefilenames value is invalid ('%s')") % val)
65 65 return abort, warn
66 66
67 67 class casecollisionauditor(object):
68 68 def __init__(self, ui, abort, dirstate):
69 69 self._ui = ui
70 70 self._abort = abort
71 71 allfiles = '\0'.join(dirstate._map)
72 72 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
73 73 self._dirstate = dirstate
74 74 # The purpose of _newfiles is so that we don't complain about
75 75 # case collisions if someone were to call this object with the
76 76 # same filename twice.
77 77 self._newfiles = set()
78 78
79 79 def __call__(self, f):
80 80 fl = encoding.lower(f)
81 81 if (fl in self._loweredfiles and f not in self._dirstate and
82 82 f not in self._newfiles):
83 83 msg = _('possible case-folding collision for %s') % f
84 84 if self._abort:
85 85 raise util.Abort(msg)
86 86 self._ui.warn(_("warning: %s\n") % msg)
87 87 self._loweredfiles.add(fl)
88 88 self._newfiles.add(f)
89 89
90 90 class pathauditor(object):
91 91 '''ensure that a filesystem path contains no banned components.
92 92 the following properties of a path are checked:
93 93
94 94 - ends with a directory separator
95 95 - under top-level .hg
96 96 - starts at the root of a windows drive
97 97 - contains ".."
98 98 - traverses a symlink (e.g. a/symlink_here/b)
99 99 - inside a nested repository (a callback can be used to approve
100 100 some nested repositories, e.g., subrepositories)
101 101 '''
102 102
103 103 def __init__(self, root, callback=None):
104 104 self.audited = set()
105 105 self.auditeddir = set()
106 106 self.root = root
107 107 self.callback = callback
108 108 if os.path.lexists(root) and not util.checkcase(root):
109 109 self.normcase = util.normcase
110 110 else:
111 111 self.normcase = lambda x: x
112 112
113 113 def __call__(self, path):
114 114 '''Check the relative path.
115 115 path may contain a pattern (e.g. foodir/**.txt)'''
116 116
117 117 path = util.localpath(path)
118 118 normpath = self.normcase(path)
119 119 if normpath in self.audited:
120 120 return
121 121 # AIX ignores "/" at end of path, others raise EISDIR.
122 122 if util.endswithsep(path):
123 123 raise util.Abort(_("path ends in directory separator: %s") % path)
124 124 parts = util.splitpath(path)
125 125 if (os.path.splitdrive(path)[0]
126 126 or parts[0].lower() in ('.hg', '.hg.', '')
127 127 or os.pardir in parts):
128 128 raise util.Abort(_("path contains illegal component: %s") % path)
129 129 if '.hg' in path.lower():
130 130 lparts = [p.lower() for p in parts]
131 131 for p in '.hg', '.hg.':
132 132 if p in lparts[1:]:
133 133 pos = lparts.index(p)
134 134 base = os.path.join(*parts[:pos])
135 135 raise util.Abort(_("path '%s' is inside nested repo %r")
136 136 % (path, base))
137 137
138 138 normparts = util.splitpath(normpath)
139 139 assert len(parts) == len(normparts)
140 140
141 141 parts.pop()
142 142 normparts.pop()
143 143 prefixes = []
144 144 while parts:
145 145 prefix = os.sep.join(parts)
146 146 normprefix = os.sep.join(normparts)
147 147 if normprefix in self.auditeddir:
148 148 break
149 149 curpath = os.path.join(self.root, prefix)
150 150 try:
151 151 st = os.lstat(curpath)
152 152 except OSError, err:
153 153 # EINVAL can be raised as invalid path syntax under win32.
154 154 # They must be ignored for patterns can be checked too.
155 155 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
156 156 raise
157 157 else:
158 158 if stat.S_ISLNK(st.st_mode):
159 159 raise util.Abort(
160 160 _('path %r traverses symbolic link %r')
161 161 % (path, prefix))
162 162 elif (stat.S_ISDIR(st.st_mode) and
163 163 os.path.isdir(os.path.join(curpath, '.hg'))):
164 164 if not self.callback or not self.callback(curpath):
165 165 raise util.Abort(_("path '%s' is inside nested "
166 166 "repo %r")
167 167 % (path, prefix))
168 168 prefixes.append(normprefix)
169 169 parts.pop()
170 170 normparts.pop()
171 171
172 172 self.audited.add(normpath)
173 173 # only add prefixes to the cache after checking everything: we don't
174 174 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
175 175 self.auditeddir.update(prefixes)
176 176
177 177 class abstractvfs(object):
178 178 """Abstract base class; cannot be instantiated"""
179 179
180 180 def __init__(self, *args, **kwargs):
181 181 '''Prevent instantiation; don't call this from subclasses.'''
182 182 raise NotImplementedError('attempted instantiating ' + str(type(self)))
183 183
184 184 def tryread(self, path):
185 185 '''gracefully return an empty string for missing files'''
186 186 try:
187 187 return self.read(path)
188 188 except IOError, inst:
189 189 if inst.errno != errno.ENOENT:
190 190 raise
191 191 return ""
192 192
193 193 def read(self, path):
194 194 fp = self(path, 'rb')
195 195 try:
196 196 return fp.read()
197 197 finally:
198 198 fp.close()
199 199
200 200 def write(self, path, data):
201 201 fp = self(path, 'wb')
202 202 try:
203 203 return fp.write(data)
204 204 finally:
205 205 fp.close()
206 206
207 207 def append(self, path, data):
208 208 fp = self(path, 'ab')
209 209 try:
210 210 return fp.write(data)
211 211 finally:
212 212 fp.close()
213 213
214 214 def exists(self, path=None):
215 215 return os.path.exists(self.join(path))
216 216
217 217 def isdir(self, path=None):
218 218 return os.path.isdir(self.join(path))
219 219
220 220 def makedir(self, path=None, notindexed=True):
221 221 return util.makedir(self.join(path), notindexed)
222 222
223 223 def makedirs(self, path=None, mode=None):
224 224 return util.makedirs(self.join(path), mode)
225 225
226 226 def mkdir(self, path=None):
227 227 return os.mkdir(self.join(path))
228 228
229 229 def readdir(self, path=None, stat=None, skip=None):
230 230 return osutil.listdir(self.join(path), stat, skip)
231 231
232 232 def stat(self, path=None):
233 233 return os.stat(self.join(path))
234 234
235 235 class vfs(abstractvfs):
236 236 '''Operate files relative to a base directory
237 237
238 238 This class is used to hide the details of COW semantics and
239 239 remote file access from higher level code.
240 240 '''
241 241 def __init__(self, base, audit=True, expand=False):
242 242 if expand:
243 243 base = os.path.realpath(util.expandpath(base))
244 244 self.base = base
245 245 self._setmustaudit(audit)
246 246 self.createmode = None
247 247 self._trustnlink = None
248 248
249 249 def _getmustaudit(self):
250 250 return self._audit
251 251
252 252 def _setmustaudit(self, onoff):
253 253 self._audit = onoff
254 254 if onoff:
255 255 self.auditor = pathauditor(self.base)
256 256 else:
257 257 self.auditor = util.always
258 258
259 259 mustaudit = property(_getmustaudit, _setmustaudit)
260 260
261 261 @util.propertycache
262 262 def _cansymlink(self):
263 263 return util.checklink(self.base)
264 264
265 265 def _fixfilemode(self, name):
266 266 if self.createmode is None:
267 267 return
268 268 os.chmod(name, self.createmode & 0666)
269 269
270 270 def __call__(self, path, mode="r", text=False, atomictemp=False):
271 271 if self._audit:
272 272 r = util.checkosfilename(path)
273 273 if r:
274 274 raise util.Abort("%s: %r" % (r, path))
275 275 self.auditor(path)
276 276 f = self.join(path)
277 277
278 278 if not text and "b" not in mode:
279 279 mode += "b" # for that other OS
280 280
281 281 nlink = -1
282 282 if mode not in ('r', 'rb'):
283 283 dirname, basename = util.split(f)
284 284 # If basename is empty, then the path is malformed because it points
285 285 # to a directory. Let the posixfile() call below raise IOError.
286 286 if basename:
287 287 if atomictemp:
288 288 if not os.path.isdir(dirname):
289 289 util.makedirs(dirname, self.createmode)
290 290 return util.atomictempfile(f, mode, self.createmode)
291 291 try:
292 292 if 'w' in mode:
293 293 util.unlink(f)
294 294 nlink = 0
295 295 else:
296 296 # nlinks() may behave differently for files on Windows
297 297 # shares if the file is open.
298 298 fd = util.posixfile(f)
299 299 nlink = util.nlinks(f)
300 300 if nlink < 1:
301 301 nlink = 2 # force mktempcopy (issue1922)
302 302 fd.close()
303 303 except (OSError, IOError), e:
304 304 if e.errno != errno.ENOENT:
305 305 raise
306 306 nlink = 0
307 307 if not os.path.isdir(dirname):
308 308 util.makedirs(dirname, self.createmode)
309 309 if nlink > 0:
310 310 if self._trustnlink is None:
311 311 self._trustnlink = nlink > 1 or util.checknlink(f)
312 312 if nlink > 1 or not self._trustnlink:
313 313 util.rename(util.mktempcopy(f), f)
314 314 fp = util.posixfile(f, mode)
315 315 if nlink == 0:
316 316 self._fixfilemode(f)
317 317 return fp
318 318
319 319 def symlink(self, src, dst):
320 320 self.auditor(dst)
321 321 linkname = self.join(dst)
322 322 try:
323 323 os.unlink(linkname)
324 324 except OSError:
325 325 pass
326 326
327 327 dirname = os.path.dirname(linkname)
328 328 if not os.path.exists(dirname):
329 329 util.makedirs(dirname, self.createmode)
330 330
331 331 if self._cansymlink:
332 332 try:
333 333 os.symlink(src, linkname)
334 334 except OSError, err:
335 335 raise OSError(err.errno, _('could not symlink to %r: %s') %
336 336 (src, err.strerror), linkname)
337 337 else:
338 338 self.write(dst, src)
339 339
340 340 def audit(self, path):
341 341 self.auditor(path)
342 342
343 343 def join(self, path):
344 344 if path:
345 345 return os.path.join(self.base, path)
346 346 else:
347 347 return self.base
348 348
349 349 opener = vfs
350 350
351 351 class auditvfs(object):
352 352 def __init__(self, vfs):
353 353 self.vfs = vfs
354 354
355 355 def _getmustaudit(self):
356 356 return self.vfs.mustaudit
357 357
358 358 def _setmustaudit(self, onoff):
359 359 self.vfs.mustaudit = onoff
360 360
361 361 mustaudit = property(_getmustaudit, _setmustaudit)
362 362
363 363 class filtervfs(abstractvfs, auditvfs):
364 364 '''Wrapper vfs for filtering filenames with a function.'''
365 365
366 366 def __init__(self, vfs, filter):
367 367 auditvfs.__init__(self, vfs)
368 368 self._filter = filter
369 369
370 370 def __call__(self, path, *args, **kwargs):
371 371 return self.vfs(self._filter(path), *args, **kwargs)
372 372
373 373 def join(self, path):
374 374 if path:
375 375 return self.vfs.join(self._filter(path))
376 376 else:
377 377 return self.vfs.join(path)
378 378
379 379 filteropener = filtervfs
380 380
381 381 def canonpath(root, cwd, myname, auditor=None):
382 382 '''return the canonical path of myname, given cwd and root'''
383 383 if util.endswithsep(root):
384 384 rootsep = root
385 385 else:
386 386 rootsep = root + os.sep
387 387 name = myname
388 388 if not os.path.isabs(name):
389 389 name = os.path.join(root, cwd, name)
390 390 name = os.path.normpath(name)
391 391 if auditor is None:
392 392 auditor = pathauditor(root)
393 393 if name != rootsep and name.startswith(rootsep):
394 394 name = name[len(rootsep):]
395 395 auditor(name)
396 396 return util.pconvert(name)
397 397 elif name == root:
398 398 return ''
399 399 else:
400 400 # Determine whether `name' is in the hierarchy at or beneath `root',
401 401 # by iterating name=dirname(name) until that causes no change (can't
402 402 # check name == '/', because that doesn't work on windows). The list
403 403 # `rel' holds the reversed list of components making up the relative
404 404 # file name we want.
405 405 rel = []
406 406 while True:
407 407 try:
408 408 s = util.samefile(name, root)
409 409 except OSError:
410 410 s = False
411 411 if s:
412 412 if not rel:
413 413 # name was actually the same as root (maybe a symlink)
414 414 return ''
415 415 rel.reverse()
416 416 name = os.path.join(*rel)
417 417 auditor(name)
418 418 return util.pconvert(name)
419 419 dirname, basename = util.split(name)
420 420 rel.append(basename)
421 421 if dirname == name:
422 422 break
423 423 name = dirname
424 424
425 425 raise util.Abort('%s not under root' % myname)
426 426
427 427 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
428 428 '''yield every hg repository under path, always recursively.
429 429 The recurse flag will only control recursion into repo working dirs'''
430 430 def errhandler(err):
431 431 if err.filename == path:
432 432 raise err
433 433 samestat = getattr(os.path, 'samestat', None)
434 434 if followsym and samestat is not None:
435 435 def adddir(dirlst, dirname):
436 436 match = False
437 437 dirstat = os.stat(dirname)
438 438 for lstdirstat in dirlst:
439 439 if samestat(dirstat, lstdirstat):
440 440 match = True
441 441 break
442 442 if not match:
443 443 dirlst.append(dirstat)
444 444 return not match
445 445 else:
446 446 followsym = False
447 447
448 448 if (seen_dirs is None) and followsym:
449 449 seen_dirs = []
450 450 adddir(seen_dirs, path)
451 451 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
452 452 dirs.sort()
453 453 if '.hg' in dirs:
454 454 yield root # found a repository
455 455 qroot = os.path.join(root, '.hg', 'patches')
456 456 if os.path.isdir(os.path.join(qroot, '.hg')):
457 457 yield qroot # we have a patch queue repo here
458 458 if recurse:
459 459 # avoid recursing inside the .hg directory
460 460 dirs.remove('.hg')
461 461 else:
462 462 dirs[:] = [] # don't descend further
463 463 elif followsym:
464 464 newdirs = []
465 465 for d in dirs:
466 466 fname = os.path.join(root, d)
467 467 if adddir(seen_dirs, fname):
468 468 if os.path.islink(fname):
469 469 for hgname in walkrepos(fname, True, seen_dirs):
470 470 yield hgname
471 471 else:
472 472 newdirs.append(d)
473 473 dirs[:] = newdirs
474 474
475 475 def osrcpath():
476 476 '''return default os-specific hgrc search path'''
477 477 path = systemrcpath()
478 478 path.extend(userrcpath())
479 479 path = [os.path.normpath(f) for f in path]
480 480 return path
481 481
482 482 _rcpath = None
483 483
484 484 def rcpath():
485 485 '''return hgrc search path. if env var HGRCPATH is set, use it.
486 486 for each item in path, if directory, use files ending in .rc,
487 487 else use item.
488 488 make HGRCPATH empty to only look in .hg/hgrc of current repo.
489 489 if no HGRCPATH, use default os-specific path.'''
490 490 global _rcpath
491 491 if _rcpath is None:
492 492 if 'HGRCPATH' in os.environ:
493 493 _rcpath = []
494 494 for p in os.environ['HGRCPATH'].split(os.pathsep):
495 495 if not p:
496 496 continue
497 497 p = util.expandpath(p)
498 498 if os.path.isdir(p):
499 499 for f, kind in osutil.listdir(p):
500 500 if f.endswith('.rc'):
501 501 _rcpath.append(os.path.join(p, f))
502 502 else:
503 503 _rcpath.append(p)
504 504 else:
505 505 _rcpath = osrcpath()
506 506 return _rcpath
507 507
508 508 if os.name != 'nt':
509 509
510 510 def rcfiles(path):
511 511 rcs = [os.path.join(path, 'hgrc')]
512 512 rcdir = os.path.join(path, 'hgrc.d')
513 513 try:
514 514 rcs.extend([os.path.join(rcdir, f)
515 515 for f, kind in osutil.listdir(rcdir)
516 516 if f.endswith(".rc")])
517 517 except OSError:
518 518 pass
519 519 return rcs
520 520
521 521 def systemrcpath():
522 522 path = []
523 523 if sys.platform == 'plan9':
524 524 root = 'lib/mercurial'
525 525 else:
526 526 root = 'etc/mercurial'
527 527 # old mod_python does not set sys.argv
528 528 if len(getattr(sys, 'argv', [])) > 0:
529 529 p = os.path.dirname(os.path.dirname(sys.argv[0]))
530 530 path.extend(rcfiles(os.path.join(p, root)))
531 531 path.extend(rcfiles('/' + root))
532 532 return path
533 533
534 534 def userrcpath():
535 535 if sys.platform == 'plan9':
536 536 return [os.environ['home'] + '/lib/hgrc']
537 537 else:
538 538 return [os.path.expanduser('~/.hgrc')]
539 539
540 540 else:
541 541
542 542 import _winreg
543 543
544 544 def systemrcpath():
545 545 '''return default os-specific hgrc search path'''
546 546 rcpath = []
547 547 filename = util.executablepath()
548 548 # Use mercurial.ini found in directory with hg.exe
549 549 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
550 550 if os.path.isfile(progrc):
551 551 rcpath.append(progrc)
552 552 return rcpath
553 553 # Use hgrc.d found in directory with hg.exe
554 554 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
555 555 if os.path.isdir(progrcd):
556 556 for f, kind in osutil.listdir(progrcd):
557 557 if f.endswith('.rc'):
558 558 rcpath.append(os.path.join(progrcd, f))
559 559 return rcpath
560 560 # else look for a system rcpath in the registry
561 561 value = util.lookupreg('SOFTWARE\\Mercurial', None,
562 562 _winreg.HKEY_LOCAL_MACHINE)
563 563 if not isinstance(value, str) or not value:
564 564 return rcpath
565 565 value = util.localpath(value)
566 566 for p in value.split(os.pathsep):
567 567 if p.lower().endswith('mercurial.ini'):
568 568 rcpath.append(p)
569 569 elif os.path.isdir(p):
570 570 for f, kind in osutil.listdir(p):
571 571 if f.endswith('.rc'):
572 572 rcpath.append(os.path.join(p, f))
573 573 return rcpath
574 574
575 575 def userrcpath():
576 576 '''return os-specific hgrc search path to the user dir'''
577 577 home = os.path.expanduser('~')
578 578 path = [os.path.join(home, 'mercurial.ini'),
579 579 os.path.join(home, '.hgrc')]
580 580 userprofile = os.environ.get('USERPROFILE')
581 581 if userprofile:
582 582 path.append(os.path.join(userprofile, 'mercurial.ini'))
583 583 path.append(os.path.join(userprofile, '.hgrc'))
584 584 return path
585 585
586 586 def revsingle(repo, revspec, default='.'):
587 587 if not revspec:
588 588 return repo[default]
589 589
590 590 l = revrange(repo, [revspec])
591 591 if len(l) < 1:
592 592 raise util.Abort(_('empty revision set'))
593 593 return repo[l[-1]]
594 594
595 595 def revpair(repo, revs):
596 596 if not revs:
597 597 return repo.dirstate.p1(), None
598 598
599 599 l = revrange(repo, revs)
600 600
601 601 if len(l) == 0:
602 602 if revs:
603 603 raise util.Abort(_('empty revision range'))
604 604 return repo.dirstate.p1(), None
605 605
606 606 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
607 607 return repo.lookup(l[0]), None
608 608
609 609 return repo.lookup(l[0]), repo.lookup(l[-1])
610 610
611 611 _revrangesep = ':'
612 612
613 613 def revrange(repo, revs):
614 614 """Yield revision as strings from a list of revision specifications."""
615 615
616 616 def revfix(repo, val, defval):
617 617 if not val and val != 0 and defval is not None:
618 618 return defval
619 619 return repo[val].rev()
620 620
621 621 seen, l = set(), []
622 622 for spec in revs:
623 623 if l and not seen:
624 624 seen = set(l)
625 625 # attempt to parse old-style ranges first to deal with
626 626 # things like old-tag which contain query metacharacters
627 627 try:
628 628 if isinstance(spec, int):
629 629 seen.add(spec)
630 630 l.append(spec)
631 631 continue
632 632
633 633 if _revrangesep in spec:
634 634 start, end = spec.split(_revrangesep, 1)
635 635 start = revfix(repo, start, 0)
636 636 end = revfix(repo, end, len(repo) - 1)
637 637 rangeiter = repo.changelog.revs(start, end)
638 638 if not seen and not l:
639 639 # by far the most common case: revs = ["-1:0"]
640 640 l = list(rangeiter)
641 641 # defer syncing seen until next iteration
642 642 continue
643 643 newrevs = set(rangeiter)
644 644 if seen:
645 645 newrevs.difference_update(seen)
646 646 seen.update(newrevs)
647 647 else:
648 648 seen = newrevs
649 649 l.extend(sorted(newrevs, reverse=start > end))
650 650 continue
651 651 elif spec and spec in repo: # single unquoted rev
652 652 rev = revfix(repo, spec, None)
653 653 if rev in seen:
654 654 continue
655 655 seen.add(rev)
656 656 l.append(rev)
657 657 continue
658 658 except error.RepoLookupError:
659 659 pass
660 660
661 661 # fall through to new-style queries if old-style fails
662 662 m = revset.match(repo.ui, spec)
663 663 dl = [r for r in m(repo, list(repo)) if r not in seen]
664 664 l.extend(dl)
665 665 seen.update(dl)
666 666
667 667 return l
668 668
669 669 def expandpats(pats):
670 670 if not util.expandglobs:
671 671 return list(pats)
672 672 ret = []
673 673 for p in pats:
674 674 kind, name = matchmod._patsplit(p, None)
675 675 if kind is None:
676 676 try:
677 677 globbed = glob.glob(name)
678 678 except re.error:
679 679 globbed = [name]
680 680 if globbed:
681 681 ret.extend(globbed)
682 682 continue
683 683 ret.append(p)
684 684 return ret
685 685
686 686 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
687 687 if pats == ("",):
688 688 pats = []
689 689 if not globbed and default == 'relpath':
690 690 pats = expandpats(pats or [])
691 691
692 692 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
693 693 default)
694 694 def badfn(f, msg):
695 695 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
696 696 m.bad = badfn
697 697 return m, pats
698 698
699 699 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
700 700 return matchandpats(ctx, pats, opts, globbed, default)[0]
701 701
702 702 def matchall(repo):
703 703 return matchmod.always(repo.root, repo.getcwd())
704 704
705 705 def matchfiles(repo, files):
706 706 return matchmod.exact(repo.root, repo.getcwd(), files)
707 707
708 708 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
709 709 if dry_run is None:
710 710 dry_run = opts.get('dry_run')
711 711 if similarity is None:
712 712 similarity = float(opts.get('similarity') or 0)
713 713 # we'd use status here, except handling of symlinks and ignore is tricky
714 714 added, unknown, deleted, removed = [], [], [], []
715 715 audit_path = pathauditor(repo.root)
716 716 m = match(repo[None], pats, opts)
717 717 rejected = []
718 718 m.bad = lambda x, y: rejected.append(x)
719 719
720 720 for abs in repo.walk(m):
721 721 target = repo.wjoin(abs)
722 722 good = True
723 723 try:
724 724 audit_path(abs)
725 725 except (OSError, util.Abort):
726 726 good = False
727 727 rel = m.rel(abs)
728 728 exact = m.exact(abs)
729 729 if good and abs not in repo.dirstate:
730 730 unknown.append(abs)
731 731 if repo.ui.verbose or not exact:
732 732 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
733 733 elif (repo.dirstate[abs] != 'r' and
734 734 (not good or not os.path.lexists(target) or
735 735 (os.path.isdir(target) and not os.path.islink(target)))):
736 736 deleted.append(abs)
737 737 if repo.ui.verbose or not exact:
738 738 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
739 739 # for finding renames
740 740 elif repo.dirstate[abs] == 'r':
741 741 removed.append(abs)
742 742 elif repo.dirstate[abs] == 'a':
743 743 added.append(abs)
744 744 copies = {}
745 745 if similarity > 0:
746 746 for old, new, score in similar.findrenames(repo,
747 747 added + unknown, removed + deleted, similarity):
748 748 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
749 749 repo.ui.status(_('recording removal of %s as rename to %s '
750 750 '(%d%% similar)\n') %
751 751 (m.rel(old), m.rel(new), score * 100))
752 752 copies[new] = old
753 753
754 754 if not dry_run:
755 755 wctx = repo[None]
756 756 wlock = repo.wlock()
757 757 try:
758 758 wctx.forget(deleted)
759 759 wctx.add(unknown)
760 760 for new, old in copies.iteritems():
761 761 wctx.copy(old, new)
762 762 finally:
763 763 wlock.release()
764 764
765 765 for f in rejected:
766 766 if f in m.files():
767 767 return 1
768 768 return 0
769 769
770 770 def updatedir(ui, repo, patches, similarity=0):
771 771 '''Update dirstate after patch application according to metadata'''
772 772 if not patches:
773 773 return []
774 774 copies = []
775 775 removes = set()
776 776 cfiles = patches.keys()
777 777 cwd = repo.getcwd()
778 778 if cwd:
779 779 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
780 780 for f in patches:
781 781 gp = patches[f]
782 782 if not gp:
783 783 continue
784 784 if gp.op == 'RENAME':
785 785 copies.append((gp.oldpath, gp.path))
786 786 removes.add(gp.oldpath)
787 787 elif gp.op == 'COPY':
788 788 copies.append((gp.oldpath, gp.path))
789 789 elif gp.op == 'DELETE':
790 790 removes.add(gp.path)
791 791
792 792 wctx = repo[None]
793 793 for src, dst in copies:
794 794 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
795 795 if (not similarity) and removes:
796 796 wctx.remove(sorted(removes), True)
797 797
798 798 for f in patches:
799 799 gp = patches[f]
800 800 if gp and gp.mode:
801 801 islink, isexec = gp.mode
802 802 dst = repo.wjoin(gp.path)
803 803 # patch won't create empty files
804 804 if gp.op == 'ADD' and not os.path.lexists(dst):
805 805 flags = (isexec and 'x' or '') + (islink and 'l' or '')
806 806 repo.wwrite(gp.path, '', flags)
807 807 util.setflags(dst, islink, isexec)
808 808 addremove(repo, cfiles, similarity=similarity)
809 809 files = patches.keys()
810 810 files.extend([r for r in removes if r not in files])
811 811 return sorted(files)
812 812
813 813 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
814 814 """Update the dirstate to reflect the intent of copying src to dst. For
815 815 different reasons it might not end with dst being marked as copied from src.
816 816 """
817 817 origsrc = repo.dirstate.copied(src) or src
818 818 if dst == origsrc: # copying back a copy?
819 819 if repo.dirstate[dst] not in 'mn' and not dryrun:
820 820 repo.dirstate.normallookup(dst)
821 821 else:
822 822 if repo.dirstate[origsrc] == 'a' and origsrc == src:
823 823 if not ui.quiet:
824 824 ui.warn(_("%s has not been committed yet, so no copy "
825 825 "data will be stored for %s.\n")
826 826 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
827 827 if repo.dirstate[dst] in '?r' and not dryrun:
828 828 wctx.add([dst])
829 829 elif not dryrun:
830 830 wctx.copy(origsrc, dst)
831 831
832 832 def readrequires(opener, supported):
833 833 '''Reads and parses .hg/requires and checks if all entries found
834 834 are in the list of supported features.'''
835 835 requirements = set(opener.read("requires").splitlines())
836 836 missings = []
837 837 for r in requirements:
838 838 if r not in supported:
839 839 if not r or not r[0].isalnum():
840 840 raise error.RequirementError(_(".hg/requires file is corrupt"))
841 841 missings.append(r)
842 842 missings.sort()
843 843 if missings:
844 844 raise error.RequirementError(
845 845 _("unknown repository format: requires features '%s' (upgrade "
846 846 "Mercurial)") % "', '".join(missings))
847 847 return requirements
848 848
849 849 class filecacheentry(object):
850 850 def __init__(self, path):
851 851 self.path = path
852 852 self.cachestat = filecacheentry.stat(self.path)
853 853
854 854 if self.cachestat:
855 855 self._cacheable = self.cachestat.cacheable()
856 856 else:
857 857 # None means we don't know yet
858 858 self._cacheable = None
859 859
860 860 def refresh(self):
861 861 if self.cacheable():
862 862 self.cachestat = filecacheentry.stat(self.path)
863 863
864 864 def cacheable(self):
865 865 if self._cacheable is not None:
866 866 return self._cacheable
867 867
868 868 # we don't know yet, assume it is for now
869 869 return True
870 870
871 871 def changed(self):
872 872 # no point in going further if we can't cache it
873 873 if not self.cacheable():
874 874 return True
875 875
876 876 newstat = filecacheentry.stat(self.path)
877 877
878 878 # we may not know if it's cacheable yet, check again now
879 879 if newstat and self._cacheable is None:
880 880 self._cacheable = newstat.cacheable()
881 881
882 882 # check again
883 883 if not self._cacheable:
884 884 return True
885 885
886 886 if self.cachestat != newstat:
887 887 self.cachestat = newstat
888 888 return True
889 889 else:
890 890 return False
891 891
892 892 @staticmethod
893 893 def stat(path):
894 894 try:
895 895 return util.cachestat(path)
896 896 except OSError, e:
897 897 if e.errno != errno.ENOENT:
898 898 raise
899 899
900 900 class filecache(object):
901 901 '''A property like decorator that tracks a file under .hg/ for updates.
902 902
903 903 Records stat info when called in _filecache.
904 904
905 905 On subsequent calls, compares old stat info with new info, and recreates
906 906 the object when needed, updating the new stat info in _filecache.
907 907
908 908 Mercurial either atomic renames or appends for files under .hg,
909 909 so to ensure the cache is reliable we need the filesystem to be able
910 910 to tell us if a file has been replaced. If it can't, we fallback to
911 911 recreating the object on every call (essentially the same behaviour as
912 912 propertycache).'''
913 913 def __init__(self, path):
914 914 self.path = path
915 915
916 916 def join(self, obj, fname):
917 917 """Used to compute the runtime path of the cached file.
918 918
919 919 Users should subclass filecache and provide their own version of this
920 920 function to call the appropriate join function on 'obj' (an instance
921 921 of the class that its member function was decorated).
922 922 """
923 923 return obj.join(fname)
924 924
925 925 def __call__(self, func):
926 926 self.func = func
927 927 self.name = func.__name__
928 928 return self
929 929
930 930 def __get__(self, obj, type=None):
931 931 # do we need to check if the file changed?
932 932 if self.name in obj.__dict__:
933 933 return obj.__dict__[self.name]
934 934
935 935 entry = obj._filecache.get(self.name)
936 936
937 937 if entry:
938 938 if entry.changed():
939 939 entry.obj = self.func(obj)
940 940 else:
941 941 path = self.join(obj, self.path)
942 942
943 943 # We stat -before- creating the object so our cache doesn't lie if
944 944 # a writer modified between the time we read and stat
945 945 entry = filecacheentry(path)
946 946 entry.obj = self.func(obj)
947 947
948 948 obj._filecache[self.name] = entry
949 949
950 950 obj.__dict__[self.name] = entry.obj
951 951 return entry.obj
952 952
953 953 def __set__(self, obj, value):
954 954 if self.name in obj._filecache:
955 955 obj._filecache[self.name].obj = value # update cached copy
956 956 obj.__dict__[self.name] = value # update copy returned by obj.x
957 957
958 958 def __delete__(self, obj):
959 959 try:
960 960 del obj.__dict__[self.name]
961 961 except KeyError:
962 raise AttributeError, self.name
962 raise AttributeError(self.name)
General Comments 0
You need to be logged in to leave comments. Login now