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