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