##// END OF EJS Templates
scmutil: simplify vfs.audit - drop wrapped vfs.auditor
Mads Kiilerich -
r18327:4aecdb91 default
parent child Browse files
Show More
@@ -1,990 +1,987
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 self.auditor = pathauditor(self.base)
255 self.audit = pathauditor(self.base)
256 256 else:
257 self.auditor = util.always
257 self.audit = 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 @util.propertycache
266 266 def _chmod(self):
267 267 return util.checkexec(self.base)
268 268
269 269 def _fixfilemode(self, name):
270 270 if self.createmode is None or not self._chmod:
271 271 return
272 272 os.chmod(name, self.createmode & 0666)
273 273
274 274 def __call__(self, path, mode="r", text=False, atomictemp=False):
275 275 if self._audit:
276 276 r = util.checkosfilename(path)
277 277 if r:
278 278 raise util.Abort("%s: %r" % (r, path))
279 self.auditor(path)
279 self.audit(path)
280 280 f = self.join(path)
281 281
282 282 if not text and "b" not in mode:
283 283 mode += "b" # for that other OS
284 284
285 285 nlink = -1
286 286 if mode not in ('r', 'rb'):
287 287 dirname, basename = util.split(f)
288 288 # If basename is empty, then the path is malformed because it points
289 289 # to a directory. Let the posixfile() call below raise IOError.
290 290 if basename:
291 291 if atomictemp:
292 292 if not os.path.isdir(dirname):
293 293 util.makedirs(dirname, self.createmode)
294 294 return util.atomictempfile(f, mode, self.createmode)
295 295 try:
296 296 if 'w' in mode:
297 297 util.unlink(f)
298 298 nlink = 0
299 299 else:
300 300 # nlinks() may behave differently for files on Windows
301 301 # shares if the file is open.
302 302 fd = util.posixfile(f)
303 303 nlink = util.nlinks(f)
304 304 if nlink < 1:
305 305 nlink = 2 # force mktempcopy (issue1922)
306 306 fd.close()
307 307 except (OSError, IOError), e:
308 308 if e.errno != errno.ENOENT:
309 309 raise
310 310 nlink = 0
311 311 if not os.path.isdir(dirname):
312 312 util.makedirs(dirname, self.createmode)
313 313 if nlink > 0:
314 314 if self._trustnlink is None:
315 315 self._trustnlink = nlink > 1 or util.checknlink(f)
316 316 if nlink > 1 or not self._trustnlink:
317 317 util.rename(util.mktempcopy(f), f)
318 318 fp = util.posixfile(f, mode)
319 319 if nlink == 0:
320 320 self._fixfilemode(f)
321 321 return fp
322 322
323 323 def symlink(self, src, dst):
324 self.auditor(dst)
324 self.audit(dst)
325 325 linkname = self.join(dst)
326 326 try:
327 327 os.unlink(linkname)
328 328 except OSError:
329 329 pass
330 330
331 331 dirname = os.path.dirname(linkname)
332 332 if not os.path.exists(dirname):
333 333 util.makedirs(dirname, self.createmode)
334 334
335 335 if self._cansymlink:
336 336 try:
337 337 os.symlink(src, linkname)
338 338 except OSError, err:
339 339 raise OSError(err.errno, _('could not symlink to %r: %s') %
340 340 (src, err.strerror), linkname)
341 341 else:
342 342 self.write(dst, src)
343 343
344 def audit(self, path):
345 self.auditor(path)
346
347 344 def join(self, path):
348 345 if path:
349 346 return os.path.join(self.base, path)
350 347 else:
351 348 return self.base
352 349
353 350 opener = vfs
354 351
355 352 class auditvfs(object):
356 353 def __init__(self, vfs):
357 354 self.vfs = vfs
358 355
359 356 def _getmustaudit(self):
360 357 return self.vfs.mustaudit
361 358
362 359 def _setmustaudit(self, onoff):
363 360 self.vfs.mustaudit = onoff
364 361
365 362 mustaudit = property(_getmustaudit, _setmustaudit)
366 363
367 364 class filtervfs(abstractvfs, auditvfs):
368 365 '''Wrapper vfs for filtering filenames with a function.'''
369 366
370 367 def __init__(self, vfs, filter):
371 368 auditvfs.__init__(self, vfs)
372 369 self._filter = filter
373 370
374 371 def __call__(self, path, *args, **kwargs):
375 372 return self.vfs(self._filter(path), *args, **kwargs)
376 373
377 374 def join(self, path):
378 375 if path:
379 376 return self.vfs.join(self._filter(path))
380 377 else:
381 378 return self.vfs.join(path)
382 379
383 380 filteropener = filtervfs
384 381
385 382 class readonlyvfs(abstractvfs, auditvfs):
386 383 '''Wrapper vfs preventing any writing.'''
387 384
388 385 def __init__(self, vfs):
389 386 auditvfs.__init__(self, vfs)
390 387
391 388 def __call__(self, path, mode='r', *args, **kw):
392 389 if mode not in ('r', 'rb'):
393 390 raise util.Abort('this vfs is read only')
394 391 return self.vfs(path, mode, *args, **kw)
395 392
396 393
397 394 def canonpath(root, cwd, myname, auditor=None):
398 395 '''return the canonical path of myname, given cwd and root'''
399 396 if util.endswithsep(root):
400 397 rootsep = root
401 398 else:
402 399 rootsep = root + os.sep
403 400 name = myname
404 401 if not os.path.isabs(name):
405 402 name = os.path.join(root, cwd, name)
406 403 name = os.path.normpath(name)
407 404 if auditor is None:
408 405 auditor = pathauditor(root)
409 406 if name != rootsep and name.startswith(rootsep):
410 407 name = name[len(rootsep):]
411 408 auditor(name)
412 409 return util.pconvert(name)
413 410 elif name == root:
414 411 return ''
415 412 else:
416 413 # Determine whether `name' is in the hierarchy at or beneath `root',
417 414 # by iterating name=dirname(name) until that causes no change (can't
418 415 # check name == '/', because that doesn't work on windows). The list
419 416 # `rel' holds the reversed list of components making up the relative
420 417 # file name we want.
421 418 rel = []
422 419 while True:
423 420 try:
424 421 s = util.samefile(name, root)
425 422 except OSError:
426 423 s = False
427 424 if s:
428 425 if not rel:
429 426 # name was actually the same as root (maybe a symlink)
430 427 return ''
431 428 rel.reverse()
432 429 name = os.path.join(*rel)
433 430 auditor(name)
434 431 return util.pconvert(name)
435 432 dirname, basename = util.split(name)
436 433 rel.append(basename)
437 434 if dirname == name:
438 435 break
439 436 name = dirname
440 437
441 438 raise util.Abort('%s not under root' % myname)
442 439
443 440 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
444 441 '''yield every hg repository under path, always recursively.
445 442 The recurse flag will only control recursion into repo working dirs'''
446 443 def errhandler(err):
447 444 if err.filename == path:
448 445 raise err
449 446 samestat = getattr(os.path, 'samestat', None)
450 447 if followsym and samestat is not None:
451 448 def adddir(dirlst, dirname):
452 449 match = False
453 450 dirstat = os.stat(dirname)
454 451 for lstdirstat in dirlst:
455 452 if samestat(dirstat, lstdirstat):
456 453 match = True
457 454 break
458 455 if not match:
459 456 dirlst.append(dirstat)
460 457 return not match
461 458 else:
462 459 followsym = False
463 460
464 461 if (seen_dirs is None) and followsym:
465 462 seen_dirs = []
466 463 adddir(seen_dirs, path)
467 464 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
468 465 dirs.sort()
469 466 if '.hg' in dirs:
470 467 yield root # found a repository
471 468 qroot = os.path.join(root, '.hg', 'patches')
472 469 if os.path.isdir(os.path.join(qroot, '.hg')):
473 470 yield qroot # we have a patch queue repo here
474 471 if recurse:
475 472 # avoid recursing inside the .hg directory
476 473 dirs.remove('.hg')
477 474 else:
478 475 dirs[:] = [] # don't descend further
479 476 elif followsym:
480 477 newdirs = []
481 478 for d in dirs:
482 479 fname = os.path.join(root, d)
483 480 if adddir(seen_dirs, fname):
484 481 if os.path.islink(fname):
485 482 for hgname in walkrepos(fname, True, seen_dirs):
486 483 yield hgname
487 484 else:
488 485 newdirs.append(d)
489 486 dirs[:] = newdirs
490 487
491 488 def osrcpath():
492 489 '''return default os-specific hgrc search path'''
493 490 path = systemrcpath()
494 491 path.extend(userrcpath())
495 492 path = [os.path.normpath(f) for f in path]
496 493 return path
497 494
498 495 _rcpath = None
499 496
500 497 def rcpath():
501 498 '''return hgrc search path. if env var HGRCPATH is set, use it.
502 499 for each item in path, if directory, use files ending in .rc,
503 500 else use item.
504 501 make HGRCPATH empty to only look in .hg/hgrc of current repo.
505 502 if no HGRCPATH, use default os-specific path.'''
506 503 global _rcpath
507 504 if _rcpath is None:
508 505 if 'HGRCPATH' in os.environ:
509 506 _rcpath = []
510 507 for p in os.environ['HGRCPATH'].split(os.pathsep):
511 508 if not p:
512 509 continue
513 510 p = util.expandpath(p)
514 511 if os.path.isdir(p):
515 512 for f, kind in osutil.listdir(p):
516 513 if f.endswith('.rc'):
517 514 _rcpath.append(os.path.join(p, f))
518 515 else:
519 516 _rcpath.append(p)
520 517 else:
521 518 _rcpath = osrcpath()
522 519 return _rcpath
523 520
524 521 if os.name != 'nt':
525 522
526 523 def rcfiles(path):
527 524 rcs = [os.path.join(path, 'hgrc')]
528 525 rcdir = os.path.join(path, 'hgrc.d')
529 526 try:
530 527 rcs.extend([os.path.join(rcdir, f)
531 528 for f, kind in osutil.listdir(rcdir)
532 529 if f.endswith(".rc")])
533 530 except OSError:
534 531 pass
535 532 return rcs
536 533
537 534 def systemrcpath():
538 535 path = []
539 536 if sys.platform == 'plan9':
540 537 root = 'lib/mercurial'
541 538 else:
542 539 root = 'etc/mercurial'
543 540 # old mod_python does not set sys.argv
544 541 if len(getattr(sys, 'argv', [])) > 0:
545 542 p = os.path.dirname(os.path.dirname(sys.argv[0]))
546 543 path.extend(rcfiles(os.path.join(p, root)))
547 544 path.extend(rcfiles('/' + root))
548 545 return path
549 546
550 547 def userrcpath():
551 548 if sys.platform == 'plan9':
552 549 return [os.environ['home'] + '/lib/hgrc']
553 550 else:
554 551 return [os.path.expanduser('~/.hgrc')]
555 552
556 553 else:
557 554
558 555 import _winreg
559 556
560 557 def systemrcpath():
561 558 '''return default os-specific hgrc search path'''
562 559 rcpath = []
563 560 filename = util.executablepath()
564 561 # Use mercurial.ini found in directory with hg.exe
565 562 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
566 563 if os.path.isfile(progrc):
567 564 rcpath.append(progrc)
568 565 return rcpath
569 566 # Use hgrc.d found in directory with hg.exe
570 567 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
571 568 if os.path.isdir(progrcd):
572 569 for f, kind in osutil.listdir(progrcd):
573 570 if f.endswith('.rc'):
574 571 rcpath.append(os.path.join(progrcd, f))
575 572 return rcpath
576 573 # else look for a system rcpath in the registry
577 574 value = util.lookupreg('SOFTWARE\\Mercurial', None,
578 575 _winreg.HKEY_LOCAL_MACHINE)
579 576 if not isinstance(value, str) or not value:
580 577 return rcpath
581 578 value = util.localpath(value)
582 579 for p in value.split(os.pathsep):
583 580 if p.lower().endswith('mercurial.ini'):
584 581 rcpath.append(p)
585 582 elif os.path.isdir(p):
586 583 for f, kind in osutil.listdir(p):
587 584 if f.endswith('.rc'):
588 585 rcpath.append(os.path.join(p, f))
589 586 return rcpath
590 587
591 588 def userrcpath():
592 589 '''return os-specific hgrc search path to the user dir'''
593 590 home = os.path.expanduser('~')
594 591 path = [os.path.join(home, 'mercurial.ini'),
595 592 os.path.join(home, '.hgrc')]
596 593 userprofile = os.environ.get('USERPROFILE')
597 594 if userprofile:
598 595 path.append(os.path.join(userprofile, 'mercurial.ini'))
599 596 path.append(os.path.join(userprofile, '.hgrc'))
600 597 return path
601 598
602 599 def revsingle(repo, revspec, default='.'):
603 600 if not revspec:
604 601 return repo[default]
605 602
606 603 l = revrange(repo, [revspec])
607 604 if len(l) < 1:
608 605 raise util.Abort(_('empty revision set'))
609 606 return repo[l[-1]]
610 607
611 608 def revpair(repo, revs):
612 609 if not revs:
613 610 return repo.dirstate.p1(), None
614 611
615 612 l = revrange(repo, revs)
616 613
617 614 if len(l) == 0:
618 615 if revs:
619 616 raise util.Abort(_('empty revision range'))
620 617 return repo.dirstate.p1(), None
621 618
622 619 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
623 620 return repo.lookup(l[0]), None
624 621
625 622 return repo.lookup(l[0]), repo.lookup(l[-1])
626 623
627 624 _revrangesep = ':'
628 625
629 626 def revrange(repo, revs):
630 627 """Yield revision as strings from a list of revision specifications."""
631 628
632 629 def revfix(repo, val, defval):
633 630 if not val and val != 0 and defval is not None:
634 631 return defval
635 632 return repo[val].rev()
636 633
637 634 seen, l = set(), []
638 635 for spec in revs:
639 636 if l and not seen:
640 637 seen = set(l)
641 638 # attempt to parse old-style ranges first to deal with
642 639 # things like old-tag which contain query metacharacters
643 640 try:
644 641 if isinstance(spec, int):
645 642 seen.add(spec)
646 643 l.append(spec)
647 644 continue
648 645
649 646 if _revrangesep in spec:
650 647 start, end = spec.split(_revrangesep, 1)
651 648 start = revfix(repo, start, 0)
652 649 end = revfix(repo, end, len(repo) - 1)
653 650 rangeiter = repo.changelog.revs(start, end)
654 651 if not seen and not l:
655 652 # by far the most common case: revs = ["-1:0"]
656 653 l = list(rangeiter)
657 654 # defer syncing seen until next iteration
658 655 continue
659 656 newrevs = set(rangeiter)
660 657 if seen:
661 658 newrevs.difference_update(seen)
662 659 seen.update(newrevs)
663 660 else:
664 661 seen = newrevs
665 662 l.extend(sorted(newrevs, reverse=start > end))
666 663 continue
667 664 elif spec and spec in repo: # single unquoted rev
668 665 rev = revfix(repo, spec, None)
669 666 if rev in seen:
670 667 continue
671 668 seen.add(rev)
672 669 l.append(rev)
673 670 continue
674 671 except error.RepoLookupError:
675 672 pass
676 673
677 674 # fall through to new-style queries if old-style fails
678 675 m = revset.match(repo.ui, spec)
679 676 dl = [r for r in m(repo, list(repo)) if r not in seen]
680 677 l.extend(dl)
681 678 seen.update(dl)
682 679
683 680 return l
684 681
685 682 def expandpats(pats):
686 683 if not util.expandglobs:
687 684 return list(pats)
688 685 ret = []
689 686 for p in pats:
690 687 kind, name = matchmod._patsplit(p, None)
691 688 if kind is None:
692 689 try:
693 690 globbed = glob.glob(name)
694 691 except re.error:
695 692 globbed = [name]
696 693 if globbed:
697 694 ret.extend(globbed)
698 695 continue
699 696 ret.append(p)
700 697 return ret
701 698
702 699 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
703 700 if pats == ("",):
704 701 pats = []
705 702 if not globbed and default == 'relpath':
706 703 pats = expandpats(pats or [])
707 704
708 705 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
709 706 default)
710 707 def badfn(f, msg):
711 708 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
712 709 m.bad = badfn
713 710 return m, pats
714 711
715 712 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
716 713 return matchandpats(ctx, pats, opts, globbed, default)[0]
717 714
718 715 def matchall(repo):
719 716 return matchmod.always(repo.root, repo.getcwd())
720 717
721 718 def matchfiles(repo, files):
722 719 return matchmod.exact(repo.root, repo.getcwd(), files)
723 720
724 721 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
725 722 if dry_run is None:
726 723 dry_run = opts.get('dry_run')
727 724 if similarity is None:
728 725 similarity = float(opts.get('similarity') or 0)
729 726 # we'd use status here, except handling of symlinks and ignore is tricky
730 727 added, unknown, deleted, removed = [], [], [], []
731 728 audit_path = pathauditor(repo.root)
732 729 m = match(repo[None], pats, opts)
733 730 rejected = []
734 731 m.bad = lambda x, y: rejected.append(x)
735 732
736 733 for abs in repo.walk(m):
737 734 target = repo.wjoin(abs)
738 735 good = True
739 736 try:
740 737 audit_path(abs)
741 738 except (OSError, util.Abort):
742 739 good = False
743 740 rel = m.rel(abs)
744 741 exact = m.exact(abs)
745 742 if good and abs not in repo.dirstate:
746 743 unknown.append(abs)
747 744 if repo.ui.verbose or not exact:
748 745 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
749 746 elif (repo.dirstate[abs] != 'r' and
750 747 (not good or not os.path.lexists(target) or
751 748 (os.path.isdir(target) and not os.path.islink(target)))):
752 749 deleted.append(abs)
753 750 if repo.ui.verbose or not exact:
754 751 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
755 752 # for finding renames
756 753 elif repo.dirstate[abs] == 'r':
757 754 removed.append(abs)
758 755 elif repo.dirstate[abs] == 'a':
759 756 added.append(abs)
760 757 copies = {}
761 758 if similarity > 0:
762 759 for old, new, score in similar.findrenames(repo,
763 760 added + unknown, removed + deleted, similarity):
764 761 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
765 762 repo.ui.status(_('recording removal of %s as rename to %s '
766 763 '(%d%% similar)\n') %
767 764 (m.rel(old), m.rel(new), score * 100))
768 765 copies[new] = old
769 766
770 767 if not dry_run:
771 768 wctx = repo[None]
772 769 wlock = repo.wlock()
773 770 try:
774 771 wctx.forget(deleted)
775 772 wctx.add(unknown)
776 773 for new, old in copies.iteritems():
777 774 wctx.copy(old, new)
778 775 finally:
779 776 wlock.release()
780 777
781 778 for f in rejected:
782 779 if f in m.files():
783 780 return 1
784 781 return 0
785 782
786 783 def updatedir(ui, repo, patches, similarity=0):
787 784 '''Update dirstate after patch application according to metadata'''
788 785 if not patches:
789 786 return []
790 787 copies = []
791 788 removes = set()
792 789 cfiles = patches.keys()
793 790 cwd = repo.getcwd()
794 791 if cwd:
795 792 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
796 793 for f in patches:
797 794 gp = patches[f]
798 795 if not gp:
799 796 continue
800 797 if gp.op == 'RENAME':
801 798 copies.append((gp.oldpath, gp.path))
802 799 removes.add(gp.oldpath)
803 800 elif gp.op == 'COPY':
804 801 copies.append((gp.oldpath, gp.path))
805 802 elif gp.op == 'DELETE':
806 803 removes.add(gp.path)
807 804
808 805 wctx = repo[None]
809 806 for src, dst in copies:
810 807 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
811 808 if (not similarity) and removes:
812 809 wctx.remove(sorted(removes), True)
813 810
814 811 for f in patches:
815 812 gp = patches[f]
816 813 if gp and gp.mode:
817 814 islink, isexec = gp.mode
818 815 dst = repo.wjoin(gp.path)
819 816 # patch won't create empty files
820 817 if gp.op == 'ADD' and not os.path.lexists(dst):
821 818 flags = (isexec and 'x' or '') + (islink and 'l' or '')
822 819 repo.wwrite(gp.path, '', flags)
823 820 util.setflags(dst, islink, isexec)
824 821 addremove(repo, cfiles, similarity=similarity)
825 822 files = patches.keys()
826 823 files.extend([r for r in removes if r not in files])
827 824 return sorted(files)
828 825
829 826 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
830 827 """Update the dirstate to reflect the intent of copying src to dst. For
831 828 different reasons it might not end with dst being marked as copied from src.
832 829 """
833 830 origsrc = repo.dirstate.copied(src) or src
834 831 if dst == origsrc: # copying back a copy?
835 832 if repo.dirstate[dst] not in 'mn' and not dryrun:
836 833 repo.dirstate.normallookup(dst)
837 834 else:
838 835 if repo.dirstate[origsrc] == 'a' and origsrc == src:
839 836 if not ui.quiet:
840 837 ui.warn(_("%s has not been committed yet, so no copy "
841 838 "data will be stored for %s.\n")
842 839 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
843 840 if repo.dirstate[dst] in '?r' and not dryrun:
844 841 wctx.add([dst])
845 842 elif not dryrun:
846 843 wctx.copy(origsrc, dst)
847 844
848 845 def readrequires(opener, supported):
849 846 '''Reads and parses .hg/requires and checks if all entries found
850 847 are in the list of supported features.'''
851 848 requirements = set(opener.read("requires").splitlines())
852 849 missings = []
853 850 for r in requirements:
854 851 if r not in supported:
855 852 if not r or not r[0].isalnum():
856 853 raise error.RequirementError(_(".hg/requires file is corrupt"))
857 854 missings.append(r)
858 855 missings.sort()
859 856 if missings:
860 857 raise error.RequirementError(
861 858 _("unknown repository format: requires features '%s' (upgrade "
862 859 "Mercurial)") % "', '".join(missings))
863 860 return requirements
864 861
865 862 class filecacheentry(object):
866 863 def __init__(self, path, stat=True):
867 864 self.path = path
868 865 self.cachestat = None
869 866 self._cacheable = None
870 867
871 868 if stat:
872 869 self.cachestat = filecacheentry.stat(self.path)
873 870
874 871 if self.cachestat:
875 872 self._cacheable = self.cachestat.cacheable()
876 873 else:
877 874 # None means we don't know yet
878 875 self._cacheable = None
879 876
880 877 def refresh(self):
881 878 if self.cacheable():
882 879 self.cachestat = filecacheentry.stat(self.path)
883 880
884 881 def cacheable(self):
885 882 if self._cacheable is not None:
886 883 return self._cacheable
887 884
888 885 # we don't know yet, assume it is for now
889 886 return True
890 887
891 888 def changed(self):
892 889 # no point in going further if we can't cache it
893 890 if not self.cacheable():
894 891 return True
895 892
896 893 newstat = filecacheentry.stat(self.path)
897 894
898 895 # we may not know if it's cacheable yet, check again now
899 896 if newstat and self._cacheable is None:
900 897 self._cacheable = newstat.cacheable()
901 898
902 899 # check again
903 900 if not self._cacheable:
904 901 return True
905 902
906 903 if self.cachestat != newstat:
907 904 self.cachestat = newstat
908 905 return True
909 906 else:
910 907 return False
911 908
912 909 @staticmethod
913 910 def stat(path):
914 911 try:
915 912 return util.cachestat(path)
916 913 except OSError, e:
917 914 if e.errno != errno.ENOENT:
918 915 raise
919 916
920 917 class filecache(object):
921 918 '''A property like decorator that tracks a file under .hg/ for updates.
922 919
923 920 Records stat info when called in _filecache.
924 921
925 922 On subsequent calls, compares old stat info with new info, and recreates
926 923 the object when needed, updating the new stat info in _filecache.
927 924
928 925 Mercurial either atomic renames or appends for files under .hg,
929 926 so to ensure the cache is reliable we need the filesystem to be able
930 927 to tell us if a file has been replaced. If it can't, we fallback to
931 928 recreating the object on every call (essentially the same behaviour as
932 929 propertycache).'''
933 930 def __init__(self, path):
934 931 self.path = path
935 932
936 933 def join(self, obj, fname):
937 934 """Used to compute the runtime path of the cached file.
938 935
939 936 Users should subclass filecache and provide their own version of this
940 937 function to call the appropriate join function on 'obj' (an instance
941 938 of the class that its member function was decorated).
942 939 """
943 940 return obj.join(fname)
944 941
945 942 def __call__(self, func):
946 943 self.func = func
947 944 self.name = func.__name__
948 945 return self
949 946
950 947 def __get__(self, obj, type=None):
951 948 # do we need to check if the file changed?
952 949 if self.name in obj.__dict__:
953 950 assert self.name in obj._filecache, self.name
954 951 return obj.__dict__[self.name]
955 952
956 953 entry = obj._filecache.get(self.name)
957 954
958 955 if entry:
959 956 if entry.changed():
960 957 entry.obj = self.func(obj)
961 958 else:
962 959 path = self.join(obj, self.path)
963 960
964 961 # We stat -before- creating the object so our cache doesn't lie if
965 962 # a writer modified between the time we read and stat
966 963 entry = filecacheentry(path)
967 964 entry.obj = self.func(obj)
968 965
969 966 obj._filecache[self.name] = entry
970 967
971 968 obj.__dict__[self.name] = entry.obj
972 969 return entry.obj
973 970
974 971 def __set__(self, obj, value):
975 972 if self.name not in obj._filecache:
976 973 # we add an entry for the missing value because X in __dict__
977 974 # implies X in _filecache
978 975 ce = filecacheentry(self.join(obj, self.path), False)
979 976 obj._filecache[self.name] = ce
980 977 else:
981 978 ce = obj._filecache[self.name]
982 979
983 980 ce.obj = value # update cached copy
984 981 obj.__dict__[self.name] = value # update copy returned by obj.x
985 982
986 983 def __delete__(self, obj):
987 984 try:
988 985 del obj.__dict__[self.name]
989 986 except KeyError:
990 987 raise AttributeError(self.name)
General Comments 0
You need to be logged in to leave comments. Login now