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