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