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