##// END OF EJS Templates
addremove: add back forgotten files (BC)...
Martin von Zweigbergk -
r23259:9f477802 default
parent child Browse files
Show More
@@ -1,1028 +1,1030 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 from mercurial.node import nullrev
10 10 import util, error, osutil, revset, similar, encoding, phases, parsers
11 11 import pathutil
12 12 import match as matchmod
13 13 import os, errno, re, glob, tempfile
14 14
15 15 if os.name == 'nt':
16 16 import scmwindows as scmplatform
17 17 else:
18 18 import scmposix as scmplatform
19 19
20 20 systemrcpath = scmplatform.systemrcpath
21 21 userrcpath = scmplatform.userrcpath
22 22
23 23 class status(tuple):
24 24 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
25 25 and 'ignored' properties are only relevant to the working copy.
26 26 '''
27 27
28 28 __slots__ = ()
29 29
30 30 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
31 31 clean):
32 32 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
33 33 ignored, clean))
34 34
35 35 @property
36 36 def modified(self):
37 37 '''files that have been modified'''
38 38 return self[0]
39 39
40 40 @property
41 41 def added(self):
42 42 '''files that have been added'''
43 43 return self[1]
44 44
45 45 @property
46 46 def removed(self):
47 47 '''files that have been removed'''
48 48 return self[2]
49 49
50 50 @property
51 51 def deleted(self):
52 52 '''files that are in the dirstate, but have been deleted from the
53 53 working copy (aka "missing")
54 54 '''
55 55 return self[3]
56 56
57 57 @property
58 58 def unknown(self):
59 59 '''files not in the dirstate that are not ignored'''
60 60 return self[4]
61 61
62 62 @property
63 63 def ignored(self):
64 64 '''files not in the dirstate that are ignored (by _dirignore())'''
65 65 return self[5]
66 66
67 67 @property
68 68 def clean(self):
69 69 '''files that have not been modified'''
70 70 return self[6]
71 71
72 72 def __repr__(self, *args, **kwargs):
73 73 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
74 74 'unknown=%r, ignored=%r, clean=%r>') % self)
75 75
76 76 def itersubrepos(ctx1, ctx2):
77 77 """find subrepos in ctx1 or ctx2"""
78 78 # Create a (subpath, ctx) mapping where we prefer subpaths from
79 79 # ctx1. The subpaths from ctx2 are important when the .hgsub file
80 80 # has been modified (in ctx2) but not yet committed (in ctx1).
81 81 subpaths = dict.fromkeys(ctx2.substate, ctx2)
82 82 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
83 83 for subpath, ctx in sorted(subpaths.iteritems()):
84 84 yield subpath, ctx.sub(subpath)
85 85
86 86 def nochangesfound(ui, repo, excluded=None):
87 87 '''Report no changes for push/pull, excluded is None or a list of
88 88 nodes excluded from the push/pull.
89 89 '''
90 90 secretlist = []
91 91 if excluded:
92 92 for n in excluded:
93 93 if n not in repo:
94 94 # discovery should not have included the filtered revision,
95 95 # we have to explicitly exclude it until discovery is cleanup.
96 96 continue
97 97 ctx = repo[n]
98 98 if ctx.phase() >= phases.secret and not ctx.extinct():
99 99 secretlist.append(n)
100 100
101 101 if secretlist:
102 102 ui.status(_("no changes found (ignored %d secret changesets)\n")
103 103 % len(secretlist))
104 104 else:
105 105 ui.status(_("no changes found\n"))
106 106
107 107 def checknewlabel(repo, lbl, kind):
108 108 # Do not use the "kind" parameter in ui output.
109 109 # It makes strings difficult to translate.
110 110 if lbl in ['tip', '.', 'null']:
111 111 raise util.Abort(_("the name '%s' is reserved") % lbl)
112 112 for c in (':', '\0', '\n', '\r'):
113 113 if c in lbl:
114 114 raise util.Abort(_("%r cannot be used in a name") % c)
115 115 try:
116 116 int(lbl)
117 117 raise util.Abort(_("cannot use an integer as a name"))
118 118 except ValueError:
119 119 pass
120 120
121 121 def checkfilename(f):
122 122 '''Check that the filename f is an acceptable filename for a tracked file'''
123 123 if '\r' in f or '\n' in f:
124 124 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
125 125
126 126 def checkportable(ui, f):
127 127 '''Check if filename f is portable and warn or abort depending on config'''
128 128 checkfilename(f)
129 129 abort, warn = checkportabilityalert(ui)
130 130 if abort or warn:
131 131 msg = util.checkwinfilename(f)
132 132 if msg:
133 133 msg = "%s: %r" % (msg, f)
134 134 if abort:
135 135 raise util.Abort(msg)
136 136 ui.warn(_("warning: %s\n") % msg)
137 137
138 138 def checkportabilityalert(ui):
139 139 '''check if the user's config requests nothing, a warning, or abort for
140 140 non-portable filenames'''
141 141 val = ui.config('ui', 'portablefilenames', 'warn')
142 142 lval = val.lower()
143 143 bval = util.parsebool(val)
144 144 abort = os.name == 'nt' or lval == 'abort'
145 145 warn = bval or lval == 'warn'
146 146 if bval is None and not (warn or abort or lval == 'ignore'):
147 147 raise error.ConfigError(
148 148 _("ui.portablefilenames value is invalid ('%s')") % val)
149 149 return abort, warn
150 150
151 151 class casecollisionauditor(object):
152 152 def __init__(self, ui, abort, dirstate):
153 153 self._ui = ui
154 154 self._abort = abort
155 155 allfiles = '\0'.join(dirstate._map)
156 156 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
157 157 self._dirstate = dirstate
158 158 # The purpose of _newfiles is so that we don't complain about
159 159 # case collisions if someone were to call this object with the
160 160 # same filename twice.
161 161 self._newfiles = set()
162 162
163 163 def __call__(self, f):
164 164 if f in self._newfiles:
165 165 return
166 166 fl = encoding.lower(f)
167 167 if fl in self._loweredfiles and f not in self._dirstate:
168 168 msg = _('possible case-folding collision for %s') % f
169 169 if self._abort:
170 170 raise util.Abort(msg)
171 171 self._ui.warn(_("warning: %s\n") % msg)
172 172 self._loweredfiles.add(fl)
173 173 self._newfiles.add(f)
174 174
175 175 class abstractvfs(object):
176 176 """Abstract base class; cannot be instantiated"""
177 177
178 178 def __init__(self, *args, **kwargs):
179 179 '''Prevent instantiation; don't call this from subclasses.'''
180 180 raise NotImplementedError('attempted instantiating ' + str(type(self)))
181 181
182 182 def tryread(self, path):
183 183 '''gracefully return an empty string for missing files'''
184 184 try:
185 185 return self.read(path)
186 186 except IOError, inst:
187 187 if inst.errno != errno.ENOENT:
188 188 raise
189 189 return ""
190 190
191 191 def open(self, path, mode="r", text=False, atomictemp=False):
192 192 self.open = self.__call__
193 193 return self.__call__(path, mode, text, atomictemp)
194 194
195 195 def read(self, path):
196 196 fp = self(path, 'rb')
197 197 try:
198 198 return fp.read()
199 199 finally:
200 200 fp.close()
201 201
202 202 def write(self, path, data):
203 203 fp = self(path, 'wb')
204 204 try:
205 205 return fp.write(data)
206 206 finally:
207 207 fp.close()
208 208
209 209 def append(self, path, data):
210 210 fp = self(path, 'ab')
211 211 try:
212 212 return fp.write(data)
213 213 finally:
214 214 fp.close()
215 215
216 216 def chmod(self, path, mode):
217 217 return os.chmod(self.join(path), mode)
218 218
219 219 def exists(self, path=None):
220 220 return os.path.exists(self.join(path))
221 221
222 222 def fstat(self, fp):
223 223 return util.fstat(fp)
224 224
225 225 def isdir(self, path=None):
226 226 return os.path.isdir(self.join(path))
227 227
228 228 def isfile(self, path=None):
229 229 return os.path.isfile(self.join(path))
230 230
231 231 def islink(self, path=None):
232 232 return os.path.islink(self.join(path))
233 233
234 234 def lexists(self, path=None):
235 235 return os.path.lexists(self.join(path))
236 236
237 237 def lstat(self, path=None):
238 238 return os.lstat(self.join(path))
239 239
240 240 def listdir(self, path=None):
241 241 return os.listdir(self.join(path))
242 242
243 243 def makedir(self, path=None, notindexed=True):
244 244 return util.makedir(self.join(path), notindexed)
245 245
246 246 def makedirs(self, path=None, mode=None):
247 247 return util.makedirs(self.join(path), mode)
248 248
249 249 def makelock(self, info, path):
250 250 return util.makelock(info, self.join(path))
251 251
252 252 def mkdir(self, path=None):
253 253 return os.mkdir(self.join(path))
254 254
255 255 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
256 256 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
257 257 dir=self.join(dir), text=text)
258 258 dname, fname = util.split(name)
259 259 if dir:
260 260 return fd, os.path.join(dir, fname)
261 261 else:
262 262 return fd, fname
263 263
264 264 def readdir(self, path=None, stat=None, skip=None):
265 265 return osutil.listdir(self.join(path), stat, skip)
266 266
267 267 def readlock(self, path):
268 268 return util.readlock(self.join(path))
269 269
270 270 def rename(self, src, dst):
271 271 return util.rename(self.join(src), self.join(dst))
272 272
273 273 def readlink(self, path):
274 274 return os.readlink(self.join(path))
275 275
276 276 def setflags(self, path, l, x):
277 277 return util.setflags(self.join(path), l, x)
278 278
279 279 def stat(self, path=None):
280 280 return os.stat(self.join(path))
281 281
282 282 def unlink(self, path=None):
283 283 return util.unlink(self.join(path))
284 284
285 285 def unlinkpath(self, path=None, ignoremissing=False):
286 286 return util.unlinkpath(self.join(path), ignoremissing)
287 287
288 288 def utime(self, path=None, t=None):
289 289 return os.utime(self.join(path), t)
290 290
291 291 class vfs(abstractvfs):
292 292 '''Operate files relative to a base directory
293 293
294 294 This class is used to hide the details of COW semantics and
295 295 remote file access from higher level code.
296 296 '''
297 297 def __init__(self, base, audit=True, expandpath=False, realpath=False):
298 298 if expandpath:
299 299 base = util.expandpath(base)
300 300 if realpath:
301 301 base = os.path.realpath(base)
302 302 self.base = base
303 303 self._setmustaudit(audit)
304 304 self.createmode = None
305 305 self._trustnlink = None
306 306
307 307 def _getmustaudit(self):
308 308 return self._audit
309 309
310 310 def _setmustaudit(self, onoff):
311 311 self._audit = onoff
312 312 if onoff:
313 313 self.audit = pathutil.pathauditor(self.base)
314 314 else:
315 315 self.audit = util.always
316 316
317 317 mustaudit = property(_getmustaudit, _setmustaudit)
318 318
319 319 @util.propertycache
320 320 def _cansymlink(self):
321 321 return util.checklink(self.base)
322 322
323 323 @util.propertycache
324 324 def _chmod(self):
325 325 return util.checkexec(self.base)
326 326
327 327 def _fixfilemode(self, name):
328 328 if self.createmode is None or not self._chmod:
329 329 return
330 330 os.chmod(name, self.createmode & 0666)
331 331
332 332 def __call__(self, path, mode="r", text=False, atomictemp=False):
333 333 if self._audit:
334 334 r = util.checkosfilename(path)
335 335 if r:
336 336 raise util.Abort("%s: %r" % (r, path))
337 337 self.audit(path)
338 338 f = self.join(path)
339 339
340 340 if not text and "b" not in mode:
341 341 mode += "b" # for that other OS
342 342
343 343 nlink = -1
344 344 if mode not in ('r', 'rb'):
345 345 dirname, basename = util.split(f)
346 346 # If basename is empty, then the path is malformed because it points
347 347 # to a directory. Let the posixfile() call below raise IOError.
348 348 if basename:
349 349 if atomictemp:
350 350 util.ensuredirs(dirname, self.createmode)
351 351 return util.atomictempfile(f, mode, self.createmode)
352 352 try:
353 353 if 'w' in mode:
354 354 util.unlink(f)
355 355 nlink = 0
356 356 else:
357 357 # nlinks() may behave differently for files on Windows
358 358 # shares if the file is open.
359 359 fd = util.posixfile(f)
360 360 nlink = util.nlinks(f)
361 361 if nlink < 1:
362 362 nlink = 2 # force mktempcopy (issue1922)
363 363 fd.close()
364 364 except (OSError, IOError), e:
365 365 if e.errno != errno.ENOENT:
366 366 raise
367 367 nlink = 0
368 368 util.ensuredirs(dirname, self.createmode)
369 369 if nlink > 0:
370 370 if self._trustnlink is None:
371 371 self._trustnlink = nlink > 1 or util.checknlink(f)
372 372 if nlink > 1 or not self._trustnlink:
373 373 util.rename(util.mktempcopy(f), f)
374 374 fp = util.posixfile(f, mode)
375 375 if nlink == 0:
376 376 self._fixfilemode(f)
377 377 return fp
378 378
379 379 def symlink(self, src, dst):
380 380 self.audit(dst)
381 381 linkname = self.join(dst)
382 382 try:
383 383 os.unlink(linkname)
384 384 except OSError:
385 385 pass
386 386
387 387 util.ensuredirs(os.path.dirname(linkname), self.createmode)
388 388
389 389 if self._cansymlink:
390 390 try:
391 391 os.symlink(src, linkname)
392 392 except OSError, err:
393 393 raise OSError(err.errno, _('could not symlink to %r: %s') %
394 394 (src, err.strerror), linkname)
395 395 else:
396 396 self.write(dst, src)
397 397
398 398 def join(self, path):
399 399 if path:
400 400 return os.path.join(self.base, path)
401 401 else:
402 402 return self.base
403 403
404 404 opener = vfs
405 405
406 406 class auditvfs(object):
407 407 def __init__(self, vfs):
408 408 self.vfs = vfs
409 409
410 410 def _getmustaudit(self):
411 411 return self.vfs.mustaudit
412 412
413 413 def _setmustaudit(self, onoff):
414 414 self.vfs.mustaudit = onoff
415 415
416 416 mustaudit = property(_getmustaudit, _setmustaudit)
417 417
418 418 class filtervfs(abstractvfs, auditvfs):
419 419 '''Wrapper vfs for filtering filenames with a function.'''
420 420
421 421 def __init__(self, vfs, filter):
422 422 auditvfs.__init__(self, vfs)
423 423 self._filter = filter
424 424
425 425 def __call__(self, path, *args, **kwargs):
426 426 return self.vfs(self._filter(path), *args, **kwargs)
427 427
428 428 def join(self, path):
429 429 if path:
430 430 return self.vfs.join(self._filter(path))
431 431 else:
432 432 return self.vfs.join(path)
433 433
434 434 filteropener = filtervfs
435 435
436 436 class readonlyvfs(abstractvfs, auditvfs):
437 437 '''Wrapper vfs preventing any writing.'''
438 438
439 439 def __init__(self, vfs):
440 440 auditvfs.__init__(self, vfs)
441 441
442 442 def __call__(self, path, mode='r', *args, **kw):
443 443 if mode not in ('r', 'rb'):
444 444 raise util.Abort('this vfs is read only')
445 445 return self.vfs(path, mode, *args, **kw)
446 446
447 447
448 448 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
449 449 '''yield every hg repository under path, always recursively.
450 450 The recurse flag will only control recursion into repo working dirs'''
451 451 def errhandler(err):
452 452 if err.filename == path:
453 453 raise err
454 454 samestat = getattr(os.path, 'samestat', None)
455 455 if followsym and samestat is not None:
456 456 def adddir(dirlst, dirname):
457 457 match = False
458 458 dirstat = os.stat(dirname)
459 459 for lstdirstat in dirlst:
460 460 if samestat(dirstat, lstdirstat):
461 461 match = True
462 462 break
463 463 if not match:
464 464 dirlst.append(dirstat)
465 465 return not match
466 466 else:
467 467 followsym = False
468 468
469 469 if (seen_dirs is None) and followsym:
470 470 seen_dirs = []
471 471 adddir(seen_dirs, path)
472 472 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
473 473 dirs.sort()
474 474 if '.hg' in dirs:
475 475 yield root # found a repository
476 476 qroot = os.path.join(root, '.hg', 'patches')
477 477 if os.path.isdir(os.path.join(qroot, '.hg')):
478 478 yield qroot # we have a patch queue repo here
479 479 if recurse:
480 480 # avoid recursing inside the .hg directory
481 481 dirs.remove('.hg')
482 482 else:
483 483 dirs[:] = [] # don't descend further
484 484 elif followsym:
485 485 newdirs = []
486 486 for d in dirs:
487 487 fname = os.path.join(root, d)
488 488 if adddir(seen_dirs, fname):
489 489 if os.path.islink(fname):
490 490 for hgname in walkrepos(fname, True, seen_dirs):
491 491 yield hgname
492 492 else:
493 493 newdirs.append(d)
494 494 dirs[:] = newdirs
495 495
496 496 def osrcpath():
497 497 '''return default os-specific hgrc search path'''
498 498 path = []
499 499 defaultpath = os.path.join(util.datapath, 'default.d')
500 500 if os.path.isdir(defaultpath):
501 501 for f, kind in osutil.listdir(defaultpath):
502 502 if f.endswith('.rc'):
503 503 path.append(os.path.join(defaultpath, f))
504 504 path.extend(systemrcpath())
505 505 path.extend(userrcpath())
506 506 path = [os.path.normpath(f) for f in path]
507 507 return path
508 508
509 509 _rcpath = None
510 510
511 511 def rcpath():
512 512 '''return hgrc search path. if env var HGRCPATH is set, use it.
513 513 for each item in path, if directory, use files ending in .rc,
514 514 else use item.
515 515 make HGRCPATH empty to only look in .hg/hgrc of current repo.
516 516 if no HGRCPATH, use default os-specific path.'''
517 517 global _rcpath
518 518 if _rcpath is None:
519 519 if 'HGRCPATH' in os.environ:
520 520 _rcpath = []
521 521 for p in os.environ['HGRCPATH'].split(os.pathsep):
522 522 if not p:
523 523 continue
524 524 p = util.expandpath(p)
525 525 if os.path.isdir(p):
526 526 for f, kind in osutil.listdir(p):
527 527 if f.endswith('.rc'):
528 528 _rcpath.append(os.path.join(p, f))
529 529 else:
530 530 _rcpath.append(p)
531 531 else:
532 532 _rcpath = osrcpath()
533 533 return _rcpath
534 534
535 535 def revsingle(repo, revspec, default='.'):
536 536 if not revspec and revspec != 0:
537 537 return repo[default]
538 538
539 539 l = revrange(repo, [revspec])
540 540 if not l:
541 541 raise util.Abort(_('empty revision set'))
542 542 return repo[l.last()]
543 543
544 544 def revpair(repo, revs):
545 545 if not revs:
546 546 return repo.dirstate.p1(), None
547 547
548 548 l = revrange(repo, revs)
549 549
550 550 if not l:
551 551 first = second = None
552 552 elif l.isascending():
553 553 first = l.min()
554 554 second = l.max()
555 555 elif l.isdescending():
556 556 first = l.max()
557 557 second = l.min()
558 558 else:
559 559 first = l.first()
560 560 second = l.last()
561 561
562 562 if first is None:
563 563 raise util.Abort(_('empty revision range'))
564 564
565 565 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
566 566 return repo.lookup(first), None
567 567
568 568 return repo.lookup(first), repo.lookup(second)
569 569
570 570 _revrangesep = ':'
571 571
572 572 def revrange(repo, revs):
573 573 """Yield revision as strings from a list of revision specifications."""
574 574
575 575 def revfix(repo, val, defval):
576 576 if not val and val != 0 and defval is not None:
577 577 return defval
578 578 return repo[val].rev()
579 579
580 580 seen, l = set(), revset.baseset([])
581 581 for spec in revs:
582 582 if l and not seen:
583 583 seen = set(l)
584 584 # attempt to parse old-style ranges first to deal with
585 585 # things like old-tag which contain query metacharacters
586 586 try:
587 587 if isinstance(spec, int):
588 588 seen.add(spec)
589 589 l = l + revset.baseset([spec])
590 590 continue
591 591
592 592 if _revrangesep in spec:
593 593 start, end = spec.split(_revrangesep, 1)
594 594 start = revfix(repo, start, 0)
595 595 end = revfix(repo, end, len(repo) - 1)
596 596 if end == nullrev and start < 0:
597 597 start = nullrev
598 598 rangeiter = repo.changelog.revs(start, end)
599 599 if not seen and not l:
600 600 # by far the most common case: revs = ["-1:0"]
601 601 l = revset.baseset(rangeiter)
602 602 # defer syncing seen until next iteration
603 603 continue
604 604 newrevs = set(rangeiter)
605 605 if seen:
606 606 newrevs.difference_update(seen)
607 607 seen.update(newrevs)
608 608 else:
609 609 seen = newrevs
610 610 l = l + revset.baseset(sorted(newrevs, reverse=start > end))
611 611 continue
612 612 elif spec and spec in repo: # single unquoted rev
613 613 rev = revfix(repo, spec, None)
614 614 if rev in seen:
615 615 continue
616 616 seen.add(rev)
617 617 l = l + revset.baseset([rev])
618 618 continue
619 619 except error.RepoLookupError:
620 620 pass
621 621
622 622 # fall through to new-style queries if old-style fails
623 623 m = revset.match(repo.ui, spec, repo)
624 624 if seen or l:
625 625 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
626 626 l = l + revset.baseset(dl)
627 627 seen.update(dl)
628 628 else:
629 629 l = m(repo, revset.spanset(repo))
630 630
631 631 return l
632 632
633 633 def expandpats(pats):
634 634 '''Expand bare globs when running on windows.
635 635 On posix we assume it already has already been done by sh.'''
636 636 if not util.expandglobs:
637 637 return list(pats)
638 638 ret = []
639 639 for kindpat in pats:
640 640 kind, pat = matchmod._patsplit(kindpat, None)
641 641 if kind is None:
642 642 try:
643 643 globbed = glob.glob(pat)
644 644 except re.error:
645 645 globbed = [pat]
646 646 if globbed:
647 647 ret.extend(globbed)
648 648 continue
649 649 ret.append(kindpat)
650 650 return ret
651 651
652 652 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
653 653 '''Return a matcher and the patterns that were used.
654 654 The matcher will warn about bad matches.'''
655 655 if pats == ("",):
656 656 pats = []
657 657 if not globbed and default == 'relpath':
658 658 pats = expandpats(pats or [])
659 659
660 660 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
661 661 default)
662 662 def badfn(f, msg):
663 663 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
664 664 m.bad = badfn
665 665 return m, pats
666 666
667 667 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
668 668 '''Return a matcher that will warn about bad matches.'''
669 669 return matchandpats(ctx, pats, opts, globbed, default)[0]
670 670
671 671 def matchall(repo):
672 672 '''Return a matcher that will efficiently match everything.'''
673 673 return matchmod.always(repo.root, repo.getcwd())
674 674
675 675 def matchfiles(repo, files):
676 676 '''Return a matcher that will efficiently match exactly these files.'''
677 677 return matchmod.exact(repo.root, repo.getcwd(), files)
678 678
679 679 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
680 680 if dry_run is None:
681 681 dry_run = opts.get('dry_run')
682 682 if similarity is None:
683 683 similarity = float(opts.get('similarity') or 0)
684 684 # we'd use status here, except handling of symlinks and ignore is tricky
685 685 m = match(repo[None], pats, opts)
686 686 rejected = []
687 687 m.bad = lambda x, y: rejected.append(x)
688 688
689 added, unknown, deleted, removed = _interestingfiles(repo, m)
689 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
690 690
691 unknownset = set(unknown)
691 unknownset = set(unknown + forgotten)
692 692 toprint = unknownset.copy()
693 693 toprint.update(deleted)
694 694 for abs in sorted(toprint):
695 695 if repo.ui.verbose or not m.exact(abs):
696 696 rel = m.rel(abs)
697 697 if abs in unknownset:
698 698 status = _('adding %s\n') % ((pats and rel) or abs)
699 699 else:
700 700 status = _('removing %s\n') % ((pats and rel) or abs)
701 701 repo.ui.status(status)
702 702
703 703 renames = _findrenames(repo, m, added + unknown, removed + deleted,
704 704 similarity)
705 705
706 706 if not dry_run:
707 _markchanges(repo, unknown, deleted, renames)
707 _markchanges(repo, unknown + forgotten, deleted, renames)
708 708
709 709 for f in rejected:
710 710 if f in m.files():
711 711 return 1
712 712 return 0
713 713
714 714 def marktouched(repo, files, similarity=0.0):
715 715 '''Assert that files have somehow been operated upon. files are relative to
716 716 the repo root.'''
717 717 m = matchfiles(repo, files)
718 718 rejected = []
719 719 m.bad = lambda x, y: rejected.append(x)
720 720
721 added, unknown, deleted, removed = _interestingfiles(repo, m)
721 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
722 722
723 723 if repo.ui.verbose:
724 unknownset = set(unknown)
724 unknownset = set(unknown + forgotten)
725 725 toprint = unknownset.copy()
726 726 toprint.update(deleted)
727 727 for abs in sorted(toprint):
728 728 if abs in unknownset:
729 729 status = _('adding %s\n') % abs
730 730 else:
731 731 status = _('removing %s\n') % abs
732 732 repo.ui.status(status)
733 733
734 734 renames = _findrenames(repo, m, added + unknown, removed + deleted,
735 735 similarity)
736 736
737 _markchanges(repo, unknown, deleted, renames)
737 _markchanges(repo, unknown + forgotten, deleted, renames)
738 738
739 739 for f in rejected:
740 740 if f in m.files():
741 741 return 1
742 742 return 0
743 743
744 744 def _interestingfiles(repo, matcher):
745 745 '''Walk dirstate with matcher, looking for files that addremove would care
746 746 about.
747 747
748 748 This is different from dirstate.status because it doesn't care about
749 749 whether files are modified or clean.'''
750 added, unknown, deleted, removed = [], [], [], []
750 added, unknown, deleted, removed, forgotten = [], [], [], [], []
751 751 audit_path = pathutil.pathauditor(repo.root)
752 752
753 753 ctx = repo[None]
754 754 dirstate = repo.dirstate
755 755 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
756 756 full=False)
757 757 for abs, st in walkresults.iteritems():
758 758 dstate = dirstate[abs]
759 759 if dstate == '?' and audit_path.check(abs):
760 760 unknown.append(abs)
761 761 elif dstate != 'r' and not st:
762 762 deleted.append(abs)
763 elif dstate == 'r' and st:
764 forgotten.append(abs)
763 765 # for finding renames
764 elif dstate == 'r':
766 elif dstate == 'r' and not st:
765 767 removed.append(abs)
766 768 elif dstate == 'a':
767 769 added.append(abs)
768 770
769 return added, unknown, deleted, removed
771 return added, unknown, deleted, removed, forgotten
770 772
771 773 def _findrenames(repo, matcher, added, removed, similarity):
772 774 '''Find renames from removed files to added ones.'''
773 775 renames = {}
774 776 if similarity > 0:
775 777 for old, new, score in similar.findrenames(repo, added, removed,
776 778 similarity):
777 779 if (repo.ui.verbose or not matcher.exact(old)
778 780 or not matcher.exact(new)):
779 781 repo.ui.status(_('recording removal of %s as rename to %s '
780 782 '(%d%% similar)\n') %
781 783 (matcher.rel(old), matcher.rel(new),
782 784 score * 100))
783 785 renames[new] = old
784 786 return renames
785 787
786 788 def _markchanges(repo, unknown, deleted, renames):
787 789 '''Marks the files in unknown as added, the files in deleted as removed,
788 790 and the files in renames as copied.'''
789 791 wctx = repo[None]
790 792 wlock = repo.wlock()
791 793 try:
792 794 wctx.forget(deleted)
793 795 wctx.add(unknown)
794 796 for new, old in renames.iteritems():
795 797 wctx.copy(old, new)
796 798 finally:
797 799 wlock.release()
798 800
799 801 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
800 802 """Update the dirstate to reflect the intent of copying src to dst. For
801 803 different reasons it might not end with dst being marked as copied from src.
802 804 """
803 805 origsrc = repo.dirstate.copied(src) or src
804 806 if dst == origsrc: # copying back a copy?
805 807 if repo.dirstate[dst] not in 'mn' and not dryrun:
806 808 repo.dirstate.normallookup(dst)
807 809 else:
808 810 if repo.dirstate[origsrc] == 'a' and origsrc == src:
809 811 if not ui.quiet:
810 812 ui.warn(_("%s has not been committed yet, so no copy "
811 813 "data will be stored for %s.\n")
812 814 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
813 815 if repo.dirstate[dst] in '?r' and not dryrun:
814 816 wctx.add([dst])
815 817 elif not dryrun:
816 818 wctx.copy(origsrc, dst)
817 819
818 820 def readrequires(opener, supported):
819 821 '''Reads and parses .hg/requires and checks if all entries found
820 822 are in the list of supported features.'''
821 823 requirements = set(opener.read("requires").splitlines())
822 824 missings = []
823 825 for r in requirements:
824 826 if r not in supported:
825 827 if not r or not r[0].isalnum():
826 828 raise error.RequirementError(_(".hg/requires file is corrupt"))
827 829 missings.append(r)
828 830 missings.sort()
829 831 if missings:
830 832 raise error.RequirementError(
831 833 _("repository requires features unknown to this Mercurial: %s")
832 834 % " ".join(missings),
833 835 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
834 836 " for more information"))
835 837 return requirements
836 838
837 839 class filecachesubentry(object):
838 840 def __init__(self, path, stat):
839 841 self.path = path
840 842 self.cachestat = None
841 843 self._cacheable = None
842 844
843 845 if stat:
844 846 self.cachestat = filecachesubentry.stat(self.path)
845 847
846 848 if self.cachestat:
847 849 self._cacheable = self.cachestat.cacheable()
848 850 else:
849 851 # None means we don't know yet
850 852 self._cacheable = None
851 853
852 854 def refresh(self):
853 855 if self.cacheable():
854 856 self.cachestat = filecachesubentry.stat(self.path)
855 857
856 858 def cacheable(self):
857 859 if self._cacheable is not None:
858 860 return self._cacheable
859 861
860 862 # we don't know yet, assume it is for now
861 863 return True
862 864
863 865 def changed(self):
864 866 # no point in going further if we can't cache it
865 867 if not self.cacheable():
866 868 return True
867 869
868 870 newstat = filecachesubentry.stat(self.path)
869 871
870 872 # we may not know if it's cacheable yet, check again now
871 873 if newstat and self._cacheable is None:
872 874 self._cacheable = newstat.cacheable()
873 875
874 876 # check again
875 877 if not self._cacheable:
876 878 return True
877 879
878 880 if self.cachestat != newstat:
879 881 self.cachestat = newstat
880 882 return True
881 883 else:
882 884 return False
883 885
884 886 @staticmethod
885 887 def stat(path):
886 888 try:
887 889 return util.cachestat(path)
888 890 except OSError, e:
889 891 if e.errno != errno.ENOENT:
890 892 raise
891 893
892 894 class filecacheentry(object):
893 895 def __init__(self, paths, stat=True):
894 896 self._entries = []
895 897 for path in paths:
896 898 self._entries.append(filecachesubentry(path, stat))
897 899
898 900 def changed(self):
899 901 '''true if any entry has changed'''
900 902 for entry in self._entries:
901 903 if entry.changed():
902 904 return True
903 905 return False
904 906
905 907 def refresh(self):
906 908 for entry in self._entries:
907 909 entry.refresh()
908 910
909 911 class filecache(object):
910 912 '''A property like decorator that tracks files under .hg/ for updates.
911 913
912 914 Records stat info when called in _filecache.
913 915
914 916 On subsequent calls, compares old stat info with new info, and recreates the
915 917 object when any of the files changes, updating the new stat info in
916 918 _filecache.
917 919
918 920 Mercurial either atomic renames or appends for files under .hg,
919 921 so to ensure the cache is reliable we need the filesystem to be able
920 922 to tell us if a file has been replaced. If it can't, we fallback to
921 923 recreating the object on every call (essentially the same behaviour as
922 924 propertycache).
923 925
924 926 '''
925 927 def __init__(self, *paths):
926 928 self.paths = paths
927 929
928 930 def join(self, obj, fname):
929 931 """Used to compute the runtime path of a cached file.
930 932
931 933 Users should subclass filecache and provide their own version of this
932 934 function to call the appropriate join function on 'obj' (an instance
933 935 of the class that its member function was decorated).
934 936 """
935 937 return obj.join(fname)
936 938
937 939 def __call__(self, func):
938 940 self.func = func
939 941 self.name = func.__name__
940 942 return self
941 943
942 944 def __get__(self, obj, type=None):
943 945 # do we need to check if the file changed?
944 946 if self.name in obj.__dict__:
945 947 assert self.name in obj._filecache, self.name
946 948 return obj.__dict__[self.name]
947 949
948 950 entry = obj._filecache.get(self.name)
949 951
950 952 if entry:
951 953 if entry.changed():
952 954 entry.obj = self.func(obj)
953 955 else:
954 956 paths = [self.join(obj, path) for path in self.paths]
955 957
956 958 # We stat -before- creating the object so our cache doesn't lie if
957 959 # a writer modified between the time we read and stat
958 960 entry = filecacheentry(paths, True)
959 961 entry.obj = self.func(obj)
960 962
961 963 obj._filecache[self.name] = entry
962 964
963 965 obj.__dict__[self.name] = entry.obj
964 966 return entry.obj
965 967
966 968 def __set__(self, obj, value):
967 969 if self.name not in obj._filecache:
968 970 # we add an entry for the missing value because X in __dict__
969 971 # implies X in _filecache
970 972 paths = [self.join(obj, path) for path in self.paths]
971 973 ce = filecacheentry(paths, False)
972 974 obj._filecache[self.name] = ce
973 975 else:
974 976 ce = obj._filecache[self.name]
975 977
976 978 ce.obj = value # update cached copy
977 979 obj.__dict__[self.name] = value # update copy returned by obj.x
978 980
979 981 def __delete__(self, obj):
980 982 try:
981 983 del obj.__dict__[self.name]
982 984 except KeyError:
983 985 raise AttributeError(self.name)
984 986
985 987 class dirs(object):
986 988 '''a multiset of directory names from a dirstate or manifest'''
987 989
988 990 def __init__(self, map, skip=None):
989 991 self._dirs = {}
990 992 addpath = self.addpath
991 993 if util.safehasattr(map, 'iteritems') and skip is not None:
992 994 for f, s in map.iteritems():
993 995 if s[0] != skip:
994 996 addpath(f)
995 997 else:
996 998 for f in map:
997 999 addpath(f)
998 1000
999 1001 def addpath(self, path):
1000 1002 dirs = self._dirs
1001 1003 for base in finddirs(path):
1002 1004 if base in dirs:
1003 1005 dirs[base] += 1
1004 1006 return
1005 1007 dirs[base] = 1
1006 1008
1007 1009 def delpath(self, path):
1008 1010 dirs = self._dirs
1009 1011 for base in finddirs(path):
1010 1012 if dirs[base] > 1:
1011 1013 dirs[base] -= 1
1012 1014 return
1013 1015 del dirs[base]
1014 1016
1015 1017 def __iter__(self):
1016 1018 return self._dirs.iterkeys()
1017 1019
1018 1020 def __contains__(self, d):
1019 1021 return d in self._dirs
1020 1022
1021 1023 if util.safehasattr(parsers, 'dirs'):
1022 1024 dirs = parsers.dirs
1023 1025
1024 1026 def finddirs(path):
1025 1027 pos = path.rfind('/')
1026 1028 while pos != -1:
1027 1029 yield path[:pos]
1028 1030 pos = path.rfind('/', 0, pos)
@@ -1,48 +1,57 b''
1 1 $ hg init rep
2 2 $ cd rep
3 3 $ mkdir dir
4 4 $ touch foo dir/bar
5 5 $ hg -v addremove
6 6 adding dir/bar
7 7 adding foo
8 8 $ hg -v commit -m "add 1"
9 9 dir/bar
10 10 foo
11 11 committed changeset 0:6f7f953567a2
12 12 $ cd dir/
13 13 $ touch ../foo_2 bar_2
14 14 $ hg -v addremove
15 15 adding dir/bar_2
16 16 adding foo_2
17 17 $ hg -v commit -m "add 2"
18 18 dir/bar_2
19 19 foo_2
20 20 committed changeset 1:e65414bf35c5
21 $ cd ../..
21 $ cd ..
22 $ hg forget foo
23 $ hg -v addremove
24 adding foo
25 $ cd ..
22 26
23 27 $ hg init sim
24 28 $ cd sim
25 29 $ echo a > a
26 30 $ echo a >> a
27 31 $ echo a >> a
28 32 $ echo c > c
29 33 $ hg commit -Ama
30 34 adding a
31 35 adding c
32 36 $ mv a b
33 37 $ rm c
34 38 $ echo d > d
35 39 $ hg addremove -n -s 50 # issue 1696
36 40 removing a
37 41 adding b
38 42 removing c
39 43 adding d
40 44 recording removal of a as rename to b (100% similar)
41 45 $ hg addremove -s 50
42 46 removing a
43 47 adding b
44 48 removing c
45 49 adding d
46 50 recording removal of a as rename to b (100% similar)
47 51 $ hg commit -mb
52 $ cp b c
53 $ hg forget b
54 $ hg addremove -s 50
55 adding b
56 adding c
48 57 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now