Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/localrepo.py @ 6876:077f1e637cd8
Merge with stable
Simplify the copy search algorithm
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Sun, 10 Aug 2008 18:38:43 -0500 |
parents | 0d714a48ab53 80e51429cb9a |
children | 11229144aa01 |
comparison
equal
deleted
inserted
replaced
6875:0d714a48ab53 | 6876:077f1e637cd8 |
---|---|
7 | 7 |
8 from node import bin, hex, nullid, nullrev, short | 8 from node import bin, hex, nullid, nullrev, short |
9 from i18n import _ | 9 from i18n import _ |
10 import repo, changegroup | 10 import repo, changegroup |
11 import changelog, dirstate, filelog, manifest, context, weakref | 11 import changelog, dirstate, filelog, manifest, context, weakref |
12 import lock, transaction, stat, errno, ui | 12 import lock, transaction, stat, errno, ui, store |
13 import os, revlog, time, util, extensions, hook, inspect | 13 import os, revlog, time, util, extensions, hook, inspect |
14 import match as match_ | |
14 | 15 |
15 class localrepository(repo.repository): | 16 class localrepository(repo.repository): |
16 capabilities = util.set(('lookup', 'changegroupsubset')) | 17 capabilities = util.set(('lookup', 'changegroupsubset')) |
17 supported = ('revlogv1', 'store') | 18 supported = ('revlogv1', 'store') |
18 | 19 |
57 # check them | 58 # check them |
58 for r in requirements: | 59 for r in requirements: |
59 if r not in self.supported: | 60 if r not in self.supported: |
60 raise repo.RepoError(_("requirement '%s' not supported") % r) | 61 raise repo.RepoError(_("requirement '%s' not supported") % r) |
61 | 62 |
62 # setup store | 63 self.store = store.store(requirements, self.path) |
63 if "store" in requirements: | 64 |
64 self.encodefn = util.encodefilename | 65 self.spath = self.store.path |
65 self.decodefn = util.decodefilename | 66 self.sopener = self.store.opener |
66 self.spath = os.path.join(self.path, "store") | 67 self.sjoin = self.store.join |
67 else: | 68 self._createmode = self.store.createmode |
68 self.encodefn = lambda x: x | 69 self.opener.createmode = self.store.createmode |
69 self.decodefn = lambda x: x | |
70 self.spath = self.path | |
71 | |
72 try: | |
73 # files in .hg/ will be created using this mode | |
74 mode = os.stat(self.spath).st_mode | |
75 # avoid some useless chmods | |
76 if (0777 & ~util._umask) == (0777 & mode): | |
77 mode = None | |
78 except OSError: | |
79 mode = None | |
80 | |
81 self._createmode = mode | |
82 self.opener.createmode = mode | |
83 sopener = util.opener(self.spath) | |
84 sopener.createmode = mode | |
85 self.sopener = util.encodedopener(sopener, self.encodefn) | |
86 | 70 |
87 self.ui = ui.ui(parentui=parentui) | 71 self.ui = ui.ui(parentui=parentui) |
88 try: | 72 try: |
89 self.ui.readconfig(self.join("hgrc"), self.root) | 73 self.ui.readconfig(self.join("hgrc"), self.root) |
90 extensions.loadall(self.ui) | 74 extensions.loadall(self.ui) |
114 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root) | 98 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root) |
115 return self.dirstate | 99 return self.dirstate |
116 else: | 100 else: |
117 raise AttributeError, name | 101 raise AttributeError, name |
118 | 102 |
103 def __getitem__(self, changeid): | |
104 if changeid == None: | |
105 return context.workingctx(self) | |
106 return context.changectx(self, changeid) | |
107 | |
108 def __nonzero__(self): | |
109 return True | |
110 | |
111 def __len__(self): | |
112 return len(self.changelog) | |
113 | |
114 def __iter__(self): | |
115 for i in xrange(len(self)): | |
116 yield i | |
117 | |
119 def url(self): | 118 def url(self): |
120 return 'file:' + self.root | 119 return 'file:' + self.root |
121 | 120 |
122 def hook(self, name, throw=False, **args): | 121 def hook(self, name, throw=False, **args): |
123 return hook.hook(self.ui, self, name, throw, **args) | 122 return hook.hook(self.ui, self, name, throw, **args) |
144 def writetags(fp, names, munge, prevtags): | 143 def writetags(fp, names, munge, prevtags): |
145 fp.seek(0, 2) | 144 fp.seek(0, 2) |
146 if prevtags and prevtags[-1] != '\n': | 145 if prevtags and prevtags[-1] != '\n': |
147 fp.write('\n') | 146 fp.write('\n') |
148 for name in names: | 147 for name in names: |
149 fp.write('%s %s\n' % (hex(node), munge and munge(name) or name)) | 148 m = munge and munge(name) or name |
149 if self._tagstypecache and name in self._tagstypecache: | |
150 old = self.tagscache.get(name, nullid) | |
151 fp.write('%s %s\n' % (hex(old), m)) | |
152 fp.write('%s %s\n' % (hex(node), m)) | |
150 fp.close() | 153 fp.close() |
151 | 154 |
152 prevtags = '' | 155 prevtags = '' |
153 if local: | 156 if local: |
154 try: | 157 try: |
300 self._tagstypecache = {} | 303 self._tagstypecache = {} |
301 for k,nh in globaltags.items(): | 304 for k,nh in globaltags.items(): |
302 n = nh[0] | 305 n = nh[0] |
303 if n != nullid: | 306 if n != nullid: |
304 self.tagscache[k] = n | 307 self.tagscache[k] = n |
305 self._tagstypecache[k] = tagtypes[k] | 308 self._tagstypecache[k] = tagtypes[k] |
306 self.tagscache['tip'] = self.changelog.tip() | 309 self.tagscache['tip'] = self.changelog.tip() |
307 | |
308 return self.tagscache | 310 return self.tagscache |
309 | 311 |
310 def tagtype(self, tagname): | 312 def tagtype(self, tagname): |
311 ''' | 313 ''' |
312 return the type of the given tag. result can be: | 314 return the type of the given tag. result can be: |
324 heads = self.heads() | 326 heads = self.heads() |
325 heads.reverse() | 327 heads.reverse() |
326 last = {} | 328 last = {} |
327 ret = [] | 329 ret = [] |
328 for node in heads: | 330 for node in heads: |
329 c = self.changectx(node) | 331 c = self[node] |
330 rev = c.rev() | 332 rev = c.rev() |
331 try: | 333 try: |
332 fnode = c.filenode('.hgtags') | 334 fnode = c.filenode('.hgtags') |
333 except revlog.LookupError: | 335 except revlog.LookupError: |
334 continue | 336 continue |
345 try: | 347 try: |
346 r = self.changelog.rev(n) | 348 r = self.changelog.rev(n) |
347 except: | 349 except: |
348 r = -2 # sort to the beginning of the list if unknown | 350 r = -2 # sort to the beginning of the list if unknown |
349 l.append((r, t, n)) | 351 l.append((r, t, n)) |
350 l.sort() | 352 return [(t, n) for r, t, n in util.sort(l)] |
351 return [(t, n) for r, t, n in l] | |
352 | 353 |
353 def nodetags(self, node): | 354 def nodetags(self, node): |
354 '''return the tags associated with a node''' | 355 '''return the tags associated with a node''' |
355 if not self.nodetagscache: | 356 if not self.nodetagscache: |
356 self.nodetagscache = {} | 357 self.nodetagscache = {} |
357 for t, n in self.tags().items(): | 358 for t, n in self.tags().items(): |
358 self.nodetagscache.setdefault(n, []).append(t) | 359 self.nodetagscache.setdefault(n, []).append(t) |
359 return self.nodetagscache.get(node, []) | 360 return self.nodetagscache.get(node, []) |
360 | 361 |
361 def _branchtags(self, partial, lrev): | 362 def _branchtags(self, partial, lrev): |
362 tiprev = self.changelog.count() - 1 | 363 tiprev = len(self) - 1 |
363 if lrev != tiprev: | 364 if lrev != tiprev: |
364 self._updatebranchcache(partial, lrev+1, tiprev+1) | 365 self._updatebranchcache(partial, lrev+1, tiprev+1) |
365 self._writebranchcache(partial, self.changelog.tip(), tiprev) | 366 self._writebranchcache(partial, self.changelog.tip(), tiprev) |
366 | 367 |
367 return partial | 368 return partial |
402 return {}, nullid, nullrev | 403 return {}, nullid, nullrev |
403 | 404 |
404 try: | 405 try: |
405 last, lrev = lines.pop(0).split(" ", 1) | 406 last, lrev = lines.pop(0).split(" ", 1) |
406 last, lrev = bin(last), int(lrev) | 407 last, lrev = bin(last), int(lrev) |
407 if not (lrev < self.changelog.count() and | 408 if lrev >= len(self) or self[lrev].node() != last: |
408 self.changelog.node(lrev) == last): # sanity check | |
409 # invalidate the cache | 409 # invalidate the cache |
410 raise ValueError('invalidating branch cache (tip differs)') | 410 raise ValueError('invalidating branch cache (tip differs)') |
411 for l in lines: | 411 for l in lines: |
412 if not l: continue | 412 if not l: continue |
413 node, label = l.split(" ", 1) | 413 node, label = l.split(" ", 1) |
430 except (IOError, OSError): | 430 except (IOError, OSError): |
431 pass | 431 pass |
432 | 432 |
433 def _updatebranchcache(self, partial, start, end): | 433 def _updatebranchcache(self, partial, start, end): |
434 for r in xrange(start, end): | 434 for r in xrange(start, end): |
435 c = self.changectx(r) | 435 c = self[r] |
436 b = c.branch() | 436 b = c.branch() |
437 partial[b] = c.node() | 437 partial[b] = c.node() |
438 | 438 |
439 def lookup(self, key): | 439 def lookup(self, key): |
440 if key == '.': | 440 if key == '.': |
441 key, second = self.dirstate.parents() | 441 return self.dirstate.parents()[0] |
442 if key == nullid: | |
443 raise repo.RepoError(_("no revision checked out")) | |
444 if second != nullid: | |
445 self.ui.warn(_("warning: working directory has two parents, " | |
446 "tag '.' uses the first\n")) | |
447 elif key == 'null': | 442 elif key == 'null': |
448 return nullid | 443 return nullid |
449 n = self.changelog._match(key) | 444 n = self.changelog._match(key) |
450 if n: | 445 if n: |
451 return n | 446 return n |
467 return True | 462 return True |
468 | 463 |
469 def join(self, f): | 464 def join(self, f): |
470 return os.path.join(self.path, f) | 465 return os.path.join(self.path, f) |
471 | 466 |
472 def sjoin(self, f): | |
473 f = self.encodefn(f) | |
474 return os.path.join(self.spath, f) | |
475 | |
476 def wjoin(self, f): | 467 def wjoin(self, f): |
477 return os.path.join(self.root, f) | 468 return os.path.join(self.root, f) |
469 | |
470 def rjoin(self, f): | |
471 return os.path.join(self.root, util.pconvert(f)) | |
478 | 472 |
479 def file(self, f): | 473 def file(self, f): |
480 if f[0] == '/': | 474 if f[0] == '/': |
481 f = f[1:] | 475 f = f[1:] |
482 return filelog.filelog(self.sopener, f) | 476 return filelog.filelog(self.sopener, f) |
483 | 477 |
484 def changectx(self, changeid=None): | 478 def changectx(self, changeid): |
485 return context.changectx(self, changeid) | 479 return self[changeid] |
486 | |
487 def workingctx(self): | |
488 return context.workingctx(self) | |
489 | 480 |
490 def parents(self, changeid=None): | 481 def parents(self, changeid=None): |
491 ''' | 482 '''get list of changectxs for parents of changeid''' |
492 get list of changectxs for parents of changeid or working directory | 483 return self[changeid].parents() |
493 ''' | |
494 if changeid is None: | |
495 pl = self.dirstate.parents() | |
496 else: | |
497 n = self.changelog.lookup(changeid) | |
498 pl = self.changelog.parents(n) | |
499 if pl[1] == nullid: | |
500 return [self.changectx(pl[0])] | |
501 return [self.changectx(pl[0]), self.changectx(pl[1])] | |
502 | 484 |
503 def filectx(self, path, changeid=None, fileid=None): | 485 def filectx(self, path, changeid=None, fileid=None): |
504 """changeid can be a changeset revision, node, or tag. | 486 """changeid can be a changeset revision, node, or tag. |
505 fileid can be a file revision or node.""" | 487 fileid can be a file revision or node.""" |
506 return context.filectx(self, path, changeid, fileid) | 488 return context.filectx(self, path, changeid, fileid) |
674 self.dirstate.invalidate, _('working directory of %s') % | 656 self.dirstate.invalidate, _('working directory of %s') % |
675 self.origroot) | 657 self.origroot) |
676 self._wlockref = weakref.ref(l) | 658 self._wlockref = weakref.ref(l) |
677 return l | 659 return l |
678 | 660 |
679 def filecommit(self, fn, manifest1, manifest2, linkrev, tr, changelist): | 661 def filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist): |
680 """ | 662 """ |
681 commit an individual file as part of a larger transaction | 663 commit an individual file as part of a larger transaction |
682 """ | 664 """ |
683 | 665 |
684 t = self.wread(fn) | 666 fn = fctx.path() |
667 t = fctx.data() | |
685 fl = self.file(fn) | 668 fl = self.file(fn) |
686 fp1 = manifest1.get(fn, nullid) | 669 fp1 = manifest1.get(fn, nullid) |
687 fp2 = manifest2.get(fn, nullid) | 670 fp2 = manifest2.get(fn, nullid) |
688 | 671 |
689 meta = {} | 672 meta = {} |
690 cf = self.dirstate.copied(fn) | 673 cp = fctx.renamed() |
691 if cf and cf != fn: | 674 if cp and cp[0] != fn: |
692 # Mark the new revision of this file as a copy of another | 675 # Mark the new revision of this file as a copy of another |
693 # file. This copy data will effectively act as a parent | 676 # file. This copy data will effectively act as a parent |
694 # of this new revision. If this is a merge, the first | 677 # of this new revision. If this is a merge, the first |
695 # parent will be the nullid (meaning "look up the copy data") | 678 # parent will be the nullid (meaning "look up the copy data") |
696 # and the second one will be the other parent. For example: | 679 # and the second one will be the other parent. For example: |
706 # 0 --- 1 --- 3 rev4 reverts the content change from rev2 | 689 # 0 --- 1 --- 3 rev4 reverts the content change from rev2 |
707 # \ / merging rev3 and rev4 should use bar@rev2 | 690 # \ / merging rev3 and rev4 should use bar@rev2 |
708 # \- 2 --- 4 as the merge base | 691 # \- 2 --- 4 as the merge base |
709 # | 692 # |
710 | 693 |
694 cf = cp[0] | |
711 cr = manifest1.get(cf) | 695 cr = manifest1.get(cf) |
712 nfp = fp2 | 696 nfp = fp2 |
713 | 697 |
714 if manifest2: # branch merge | 698 if manifest2: # branch merge |
715 if fp2 == nullid: # copied on remote side | 699 if fp2 == nullid: # copied on remote side |
719 | 703 |
720 # find source in nearest ancestor if we've lost track | 704 # find source in nearest ancestor if we've lost track |
721 if not cr: | 705 if not cr: |
722 self.ui.debug(_(" %s: searching for copy revision for %s\n") % | 706 self.ui.debug(_(" %s: searching for copy revision for %s\n") % |
723 (fn, cf)) | 707 (fn, cf)) |
724 p1 = self.dirstate.parents()[0] | 708 for a in self['.'].ancestors(): |
725 rev = self.changelog.rev(p1) | 709 if cf in a: |
726 seen = {-1:None} | 710 cr = a[cf].filenode() |
727 visit = [rev] | 711 break |
728 while visit: | |
729 for p in self.changelog.parentrevs(visit.pop(0)): | |
730 if p not in seen: | |
731 seen[p] = True | |
732 visit.append(p) | |
733 ctx = self.changectx(p) | |
734 if cf in ctx: | |
735 cr = ctx[cf].filenode() | |
736 break | |
737 | 712 |
738 self.ui.debug(_(" %s: copy %s:%s\n") % (fn, cf, hex(cr))) | 713 self.ui.debug(_(" %s: copy %s:%s\n") % (fn, cf, hex(cr))) |
739 meta["copy"] = cf | 714 meta["copy"] = cf |
740 meta["copyrev"] = hex(cr) | 715 meta["copyrev"] = hex(cr) |
741 fp1, fp2 = nullid, nfp | 716 fp1, fp2 = nullid, nfp |
759 p1, p2 = self.dirstate.parents() | 734 p1, p2 = self.dirstate.parents() |
760 return self.commit(files=files, text=text, user=user, date=date, | 735 return self.commit(files=files, text=text, user=user, date=date, |
761 p1=p1, p2=p2, extra=extra, empty_ok=True) | 736 p1=p1, p2=p2, extra=extra, empty_ok=True) |
762 | 737 |
763 def commit(self, files=None, text="", user=None, date=None, | 738 def commit(self, files=None, text="", user=None, date=None, |
764 match=util.always, force=False, force_editor=False, | 739 match=None, force=False, force_editor=False, |
765 p1=None, p2=None, extra={}, empty_ok=False): | 740 p1=None, p2=None, extra={}, empty_ok=False): |
766 wlock = lock = tr = None | 741 wlock = lock = None |
767 valid = 0 # don't save the dirstate if this isn't set | |
768 if files: | 742 if files: |
769 files = util.unique(files) | 743 files = util.unique(files) |
770 try: | 744 try: |
771 wlock = self.wlock() | 745 wlock = self.wlock() |
772 lock = self.lock() | 746 lock = self.lock() |
773 commit = [] | |
774 remove = [] | |
775 changed = [] | |
776 use_dirstate = (p1 is None) # not rawcommit | 747 use_dirstate = (p1 is None) # not rawcommit |
777 extra = extra.copy() | |
778 | 748 |
779 if use_dirstate: | 749 if use_dirstate: |
750 p1, p2 = self.dirstate.parents() | |
751 update_dirstate = True | |
752 | |
753 if (not force and p2 != nullid and | |
754 (match and (match.files() or match.anypats()))): | |
755 raise util.Abort(_('cannot partially commit a merge ' | |
756 '(do not specify files or patterns)')) | |
757 | |
780 if files: | 758 if files: |
759 modified, removed = [], [] | |
781 for f in files: | 760 for f in files: |
782 s = self.dirstate[f] | 761 s = self.dirstate[f] |
783 if s in 'nma': | 762 if s in 'nma': |
784 commit.append(f) | 763 modified.append(f) |
785 elif s == 'r': | 764 elif s == 'r': |
786 remove.append(f) | 765 removed.append(f) |
787 else: | 766 else: |
788 self.ui.warn(_("%s not tracked!\n") % f) | 767 self.ui.warn(_("%s not tracked!\n") % f) |
768 changes = [modified, [], removed, [], []] | |
789 else: | 769 else: |
790 changes = self.status(match=match)[:5] | 770 changes = self.status(match=match) |
791 modified, added, removed, deleted, unknown = changes | |
792 commit = modified + added | |
793 remove = removed | |
794 else: | |
795 commit = files | |
796 | |
797 if use_dirstate: | |
798 p1, p2 = self.dirstate.parents() | |
799 update_dirstate = True | |
800 | |
801 if (not force and p2 != nullid and | |
802 (files or match != util.always)): | |
803 raise util.Abort(_('cannot partially commit a merge ' | |
804 '(do not specify files or patterns)')) | |
805 else: | 771 else: |
806 p1, p2 = p1, p2 or nullid | 772 p1, p2 = p1, p2 or nullid |
807 update_dirstate = (self.dirstate.parents()[0] == p1) | 773 update_dirstate = (self.dirstate.parents()[0] == p1) |
808 | 774 changes = [files, [], [], [], []] |
775 | |
776 wctx = context.workingctx(self, (p1, p2), text, user, date, | |
777 extra, changes) | |
778 return self._commitctx(wctx, force, force_editor, empty_ok, | |
779 use_dirstate, update_dirstate) | |
780 finally: | |
781 del lock, wlock | |
782 | |
783 def commitctx(self, ctx): | |
784 wlock = lock = None | |
785 try: | |
786 wlock = self.wlock() | |
787 lock = self.lock() | |
788 return self._commitctx(ctx, force=True, force_editor=False, | |
789 empty_ok=True, use_dirstate=False, | |
790 update_dirstate=False) | |
791 finally: | |
792 del lock, wlock | |
793 | |
794 def _commitctx(self, wctx, force=False, force_editor=False, empty_ok=False, | |
795 use_dirstate=True, update_dirstate=True): | |
796 tr = None | |
797 valid = 0 # don't save the dirstate if this isn't set | |
798 try: | |
799 commit = util.sort(wctx.modified() + wctx.added()) | |
800 remove = wctx.removed() | |
801 extra = wctx.extra().copy() | |
802 branchname = extra['branch'] | |
803 user = wctx.user() | |
804 text = wctx.description() | |
805 | |
806 p1, p2 = [p.node() for p in wctx.parents()] | |
809 c1 = self.changelog.read(p1) | 807 c1 = self.changelog.read(p1) |
810 c2 = self.changelog.read(p2) | 808 c2 = self.changelog.read(p2) |
811 m1 = self.manifest.read(c1[0]).copy() | 809 m1 = self.manifest.read(c1[0]).copy() |
812 m2 = self.manifest.read(c2[0]) | 810 m2 = self.manifest.read(c2[0]) |
813 | |
814 if use_dirstate: | |
815 branchname = self.workingctx().branch() | |
816 try: | |
817 branchname = branchname.decode('UTF-8').encode('UTF-8') | |
818 except UnicodeDecodeError: | |
819 raise util.Abort(_('branch name not in UTF-8!')) | |
820 else: | |
821 branchname = "" | |
822 | 811 |
823 if use_dirstate: | 812 if use_dirstate: |
824 oldname = c1[5].get("branch") # stored in UTF-8 | 813 oldname = c1[5].get("branch") # stored in UTF-8 |
825 if (not commit and not remove and not force and p2 == nullid | 814 if (not commit and not remove and not force and p2 == nullid |
826 and branchname == oldname): | 815 and branchname == oldname): |
836 tr = self.transaction() | 825 tr = self.transaction() |
837 trp = weakref.proxy(tr) | 826 trp = weakref.proxy(tr) |
838 | 827 |
839 # check in files | 828 # check in files |
840 new = {} | 829 new = {} |
841 linkrev = self.changelog.count() | 830 changed = [] |
842 commit.sort() | 831 linkrev = len(self) |
843 is_exec = util.execfunc(self.root, m1.execf) | |
844 is_link = util.linkfunc(self.root, m1.linkf) | |
845 for f in commit: | 832 for f in commit: |
846 self.ui.note(f + "\n") | 833 self.ui.note(f + "\n") |
847 try: | 834 try: |
848 new[f] = self.filecommit(f, m1, m2, linkrev, trp, changed) | 835 fctx = wctx.filectx(f) |
849 new_exec = is_exec(f) | 836 newflags = fctx.flags() |
850 new_link = is_link(f) | 837 new[f] = self.filecommit(fctx, m1, m2, linkrev, trp, changed) |
851 if ((not changed or changed[-1] != f) and | 838 if ((not changed or changed[-1] != f) and |
852 m2.get(f) != new[f]): | 839 m2.get(f) != new[f]): |
853 # mention the file in the changelog if some | 840 # mention the file in the changelog if some |
854 # flag changed, even if there was no content | 841 # flag changed, even if there was no content |
855 # change. | 842 # change. |
856 old_exec = m1.execf(f) | 843 if m1.flags(f) != newflags: |
857 old_link = m1.linkf(f) | |
858 if old_exec != new_exec or old_link != new_link: | |
859 changed.append(f) | 844 changed.append(f) |
860 m1.set(f, new_exec, new_link) | 845 m1.set(f, newflags) |
861 if use_dirstate: | 846 if use_dirstate: |
862 self.dirstate.normal(f) | 847 self.dirstate.normal(f) |
863 | 848 |
864 except (OSError, IOError): | 849 except (OSError, IOError): |
865 if use_dirstate: | 850 if use_dirstate: |
868 else: | 853 else: |
869 remove.append(f) | 854 remove.append(f) |
870 | 855 |
871 # update manifest | 856 # update manifest |
872 m1.update(new) | 857 m1.update(new) |
873 remove.sort() | |
874 removed = [] | 858 removed = [] |
875 | 859 |
876 for f in remove: | 860 for f in util.sort(remove): |
877 if f in m1: | 861 if f in m1: |
878 del m1[f] | 862 del m1[f] |
879 removed.append(f) | 863 removed.append(f) |
880 elif f in m2: | 864 elif f in m2: |
881 removed.append(f) | 865 removed.append(f) |
882 mn = self.manifest.add(m1, trp, linkrev, c1[0], c2[0], | 866 mn = self.manifest.add(m1, trp, linkrev, c1[0], c2[0], |
883 (new, removed)) | 867 (new, removed)) |
884 | 868 |
885 # add changeset | 869 # add changeset |
886 new = new.keys() | |
887 new.sort() | |
888 | |
889 user = user or self.ui.username() | |
890 if (not empty_ok and not text) or force_editor: | 870 if (not empty_ok and not text) or force_editor: |
891 edittext = [] | 871 edittext = [] |
892 if text: | 872 if text: |
893 edittext.append(text) | 873 edittext.append(text) |
894 edittext.append("") | 874 edittext.append("") |
909 olddir = os.getcwd() | 889 olddir = os.getcwd() |
910 os.chdir(self.root) | 890 os.chdir(self.root) |
911 text = self.ui.edit("\n".join(edittext), user) | 891 text = self.ui.edit("\n".join(edittext), user) |
912 os.chdir(olddir) | 892 os.chdir(olddir) |
913 | 893 |
914 if branchname: | |
915 extra["branch"] = branchname | |
916 | |
917 lines = [line.rstrip() for line in text.rstrip().splitlines()] | 894 lines = [line.rstrip() for line in text.rstrip().splitlines()] |
918 while lines and not lines[0]: | 895 while lines and not lines[0]: |
919 del lines[0] | 896 del lines[0] |
920 if not lines and use_dirstate: | 897 if not lines and use_dirstate: |
921 raise util.Abort(_("empty commit message")) | 898 raise util.Abort(_("empty commit message")) |
922 text = '\n'.join(lines) | 899 text = '\n'.join(lines) |
923 | 900 |
924 n = self.changelog.add(mn, changed + removed, text, trp, p1, p2, | 901 n = self.changelog.add(mn, changed + removed, text, trp, p1, p2, |
925 user, date, extra) | 902 user, wctx.date(), extra) |
926 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, | 903 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, |
927 parent2=xp2) | 904 parent2=xp2) |
928 tr.close() | 905 tr.close() |
929 | 906 |
930 if self.branchcache: | 907 if self.branchcache: |
940 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) | 917 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) |
941 return n | 918 return n |
942 finally: | 919 finally: |
943 if not valid: # don't save our updated dirstate | 920 if not valid: # don't save our updated dirstate |
944 self.dirstate.invalidate() | 921 self.dirstate.invalidate() |
945 del tr, lock, wlock | 922 del tr |
946 | 923 |
947 def walk(self, node=None, files=[], match=util.always, badmatch=None): | 924 def walk(self, match, node=None): |
948 ''' | 925 ''' |
949 walk recursively through the directory tree or a given | 926 walk recursively through the directory tree or a given |
950 changeset, finding all files matched by the match | 927 changeset, finding all files matched by the match |
951 function | 928 function |
952 | |
953 results are yielded in a tuple (src, filename), where src | |
954 is one of: | |
955 'f' the file was found in the directory tree | |
956 'm' the file was only in the dirstate and not in the tree | |
957 'b' file was not found and matched badmatch | |
958 ''' | 929 ''' |
959 | 930 return self[node].walk(match) |
960 if node: | 931 |
961 fdict = dict.fromkeys(files) | 932 def status(self, node1='.', node2=None, match=None, |
962 # for dirstate.walk, files=['.'] means "walk the whole tree". | 933 ignored=False, clean=False, unknown=False): |
963 # follow that here, too | |
964 fdict.pop('.', None) | |
965 mdict = self.manifest.read(self.changelog.read(node)[0]) | |
966 mfiles = mdict.keys() | |
967 mfiles.sort() | |
968 for fn in mfiles: | |
969 for ffn in fdict: | |
970 # match if the file is the exact name or a directory | |
971 if ffn == fn or fn.startswith("%s/" % ffn): | |
972 del fdict[ffn] | |
973 break | |
974 if match(fn): | |
975 yield 'm', fn | |
976 ffiles = fdict.keys() | |
977 ffiles.sort() | |
978 for fn in ffiles: | |
979 if badmatch and badmatch(fn): | |
980 if match(fn): | |
981 yield 'b', fn | |
982 else: | |
983 self.ui.warn(_('%s: No such file in rev %s\n') | |
984 % (self.pathto(fn), short(node))) | |
985 else: | |
986 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch): | |
987 yield src, fn | |
988 | |
989 def status(self, node1=None, node2=None, files=[], match=util.always, | |
990 list_ignored=False, list_clean=False, list_unknown=True): | |
991 """return status of files between two nodes or node and working directory | 934 """return status of files between two nodes or node and working directory |
992 | 935 |
993 If node1 is None, use the first dirstate parent instead. | 936 If node1 is None, use the first dirstate parent instead. |
994 If node2 is None, compare node1 with working directory. | 937 If node2 is None, compare node1 with working directory. |
995 """ | 938 """ |
996 | 939 |
997 def fcmp(fn, getnode): | 940 def mfmatches(ctx): |
998 t1 = self.wread(fn) | 941 mf = ctx.manifest().copy() |
999 return self.file(fn).cmp(getnode(fn), t1) | |
1000 | |
1001 def mfmatches(node): | |
1002 change = self.changelog.read(node) | |
1003 mf = self.manifest.read(change[0]).copy() | |
1004 for fn in mf.keys(): | 942 for fn in mf.keys(): |
1005 if not match(fn): | 943 if not match(fn): |
1006 del mf[fn] | 944 del mf[fn] |
1007 return mf | 945 return mf |
1008 | 946 |
1009 modified, added, removed, deleted, unknown = [], [], [], [], [] | 947 ctx1 = self[node1] |
1010 ignored, clean = [], [] | 948 ctx2 = self[node2] |
1011 | 949 working = ctx2 == self[None] |
1012 compareworking = False | 950 parentworking = working and ctx1 == self['.'] |
1013 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]): | 951 match = match or match_.always(self.root, self.getcwd()) |
1014 compareworking = True | 952 listignored, listclean, listunknown = ignored, clean, unknown |
1015 | 953 |
1016 if not compareworking: | 954 if working: # we need to scan the working dir |
1017 # read the manifest from node1 before the manifest from node2, | 955 s = self.dirstate.status(match, listignored, listclean, listunknown) |
1018 # so that we'll hit the manifest cache if we're going through | 956 cmp, modified, added, removed, deleted, unknown, ignored, clean = s |
1019 # all the revisions in parent->child order. | 957 |
1020 mf1 = mfmatches(node1) | 958 # check for any possibly clean files |
1021 | 959 if parentworking and cmp: |
1022 # are we comparing the working directory? | 960 fixup = [] |
1023 if not node2: | 961 # do a full compare of any files that might have changed |
1024 (lookup, modified, added, removed, deleted, unknown, | 962 for f in cmp: |
1025 ignored, clean) = self.dirstate.status(files, match, | 963 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f) |
1026 list_ignored, list_clean, | 964 or ctx1[f].cmp(ctx2[f].data())): |
1027 list_unknown) | 965 modified.append(f) |
1028 | 966 else: |
1029 # are we comparing working dir against its parent? | 967 fixup.append(f) |
1030 if compareworking: | 968 |
1031 if lookup: | 969 if listclean: |
1032 fixup = [] | 970 clean += fixup |
1033 # do a full compare of any files that might have changed | 971 |
1034 ctx = self.changectx() | 972 # update dirstate for files that are actually clean |
1035 mexec = lambda f: 'x' in ctx.fileflags(f) | 973 if fixup: |
1036 mlink = lambda f: 'l' in ctx.fileflags(f) | 974 wlock = None |
1037 is_exec = util.execfunc(self.root, mexec) | 975 try: |
1038 is_link = util.linkfunc(self.root, mlink) | |
1039 def flags(f): | |
1040 return is_link(f) and 'l' or is_exec(f) and 'x' or '' | |
1041 for f in lookup: | |
1042 if (f not in ctx or flags(f) != ctx.fileflags(f) | |
1043 or ctx[f].cmp(self.wread(f))): | |
1044 modified.append(f) | |
1045 else: | |
1046 fixup.append(f) | |
1047 if list_clean: | |
1048 clean.append(f) | |
1049 | |
1050 # update dirstate for files that are actually clean | |
1051 if fixup: | |
1052 wlock = None | |
1053 try: | 976 try: |
1054 try: | 977 wlock = self.wlock(False) |
1055 wlock = self.wlock(False) | 978 for f in fixup: |
1056 except lock.LockException: | 979 self.dirstate.normal(f) |
1057 pass | 980 except lock.LockException: |
1058 if wlock: | 981 pass |
1059 for f in fixup: | 982 finally: |
1060 self.dirstate.normal(f) | 983 del wlock |
1061 finally: | 984 |
1062 del wlock | 985 if not parentworking: |
1063 else: | 986 mf1 = mfmatches(ctx1) |
987 if working: | |
1064 # we are comparing working dir against non-parent | 988 # we are comparing working dir against non-parent |
1065 # generate a pseudo-manifest for the working dir | 989 # generate a pseudo-manifest for the working dir |
1066 # XXX: create it in dirstate.py ? | 990 mf2 = mfmatches(self['.']) |
1067 mf2 = mfmatches(self.dirstate.parents()[0]) | 991 for f in cmp + modified + added: |
1068 is_exec = util.execfunc(self.root, mf2.execf) | 992 mf2[f] = None |
1069 is_link = util.linkfunc(self.root, mf2.linkf) | 993 mf2.set(f, ctx2.flags(f)) |
1070 for f in lookup + modified + added: | |
1071 mf2[f] = "" | |
1072 mf2.set(f, is_exec(f), is_link(f)) | |
1073 for f in removed: | 994 for f in removed: |
1074 if f in mf2: | 995 if f in mf2: |
1075 del mf2[f] | 996 del mf2[f] |
1076 | 997 else: |
1077 else: | 998 # we are comparing two revisions |
1078 # we are comparing two revisions | 999 deleted, unknown, ignored = [], [], [] |
1079 mf2 = mfmatches(node2) | 1000 mf2 = mfmatches(ctx2) |
1080 | 1001 |
1081 if not compareworking: | |
1082 # flush lists from dirstate before comparing manifests | |
1083 modified, added, clean = [], [], [] | 1002 modified, added, clean = [], [], [] |
1084 | 1003 for fn in mf2: |
1085 # make sure to sort the files so we talk to the disk in a | |
1086 # reasonable order | |
1087 mf2keys = mf2.keys() | |
1088 mf2keys.sort() | |
1089 getnode = lambda fn: mf1.get(fn, nullid) | |
1090 for fn in mf2keys: | |
1091 if fn in mf1: | 1004 if fn in mf1: |
1092 if (mf1.flags(fn) != mf2.flags(fn) or | 1005 if (mf1.flags(fn) != mf2.flags(fn) or |
1093 (mf1[fn] != mf2[fn] and | 1006 (mf1[fn] != mf2[fn] and |
1094 (mf2[fn] != "" or fcmp(fn, getnode)))): | 1007 (mf2[fn] or ctx1[fn].cmp(ctx2[fn].data())))): |
1095 modified.append(fn) | 1008 modified.append(fn) |
1096 elif list_clean: | 1009 elif listclean: |
1097 clean.append(fn) | 1010 clean.append(fn) |
1098 del mf1[fn] | 1011 del mf1[fn] |
1099 else: | 1012 else: |
1100 added.append(fn) | 1013 added.append(fn) |
1101 | |
1102 removed = mf1.keys() | 1014 removed = mf1.keys() |
1103 | 1015 |
1104 # sort and return results: | 1016 r = modified, added, removed, deleted, unknown, ignored, clean |
1105 for l in modified, added, removed, deleted, unknown, ignored, clean: | 1017 [l.sort() for l in r] |
1106 l.sort() | 1018 return r |
1107 return (modified, added, removed, deleted, unknown, ignored, clean) | |
1108 | 1019 |
1109 def add(self, list): | 1020 def add(self, list): |
1110 wlock = self.wlock() | 1021 wlock = self.wlock() |
1111 try: | 1022 try: |
1112 rejected = [] | 1023 rejected = [] |
1207 | 1118 |
1208 def heads(self, start=None): | 1119 def heads(self, start=None): |
1209 heads = self.changelog.heads(start) | 1120 heads = self.changelog.heads(start) |
1210 # sort the output in rev descending order | 1121 # sort the output in rev descending order |
1211 heads = [(-self.changelog.rev(h), h) for h in heads] | 1122 heads = [(-self.changelog.rev(h), h) for h in heads] |
1212 heads.sort() | 1123 return [n for (r, n) in util.sort(heads)] |
1213 return [n for (r, n) in heads] | 1124 |
1214 | 1125 def branchheads(self, branch=None, start=None): |
1215 def branchheads(self, branch, start=None): | 1126 if branch is None: |
1127 branch = self[None].branch() | |
1216 branches = self.branchtags() | 1128 branches = self.branchtags() |
1217 if branch not in branches: | 1129 if branch not in branches: |
1218 return [] | 1130 return [] |
1219 # The basic algorithm is this: | 1131 # The basic algorithm is this: |
1220 # | 1132 # |
1248 ancestors = set(self.changelog.parentrevs(heads[0])) | 1160 ancestors = set(self.changelog.parentrevs(heads[0])) |
1249 for rev in xrange(heads[0] - 1, nullrev, -1): | 1161 for rev in xrange(heads[0] - 1, nullrev, -1): |
1250 if rev in ancestors: | 1162 if rev in ancestors: |
1251 ancestors.update(self.changelog.parentrevs(rev)) | 1163 ancestors.update(self.changelog.parentrevs(rev)) |
1252 ancestors.remove(rev) | 1164 ancestors.remove(rev) |
1253 elif self.changectx(rev).branch() == branch: | 1165 elif self[rev].branch() == branch: |
1254 heads.append(rev) | 1166 heads.append(rev) |
1255 ancestors.update(self.changelog.parentrevs(rev)) | 1167 ancestors.update(self.changelog.parentrevs(rev)) |
1256 heads = [self.changelog.node(rev) for rev in heads] | 1168 heads = [self.changelog.node(rev) for rev in heads] |
1257 if start is not None: | 1169 if start is not None: |
1258 heads = self.changelog.nodesbetween([start], heads)[2] | 1170 heads = self.changelog.nodesbetween([start], heads)[2] |
1663 # We don't know which manifests are missing yet | 1575 # We don't know which manifests are missing yet |
1664 msng_mnfst_set = {} | 1576 msng_mnfst_set = {} |
1665 # Nor do we know which filenodes are missing. | 1577 # Nor do we know which filenodes are missing. |
1666 msng_filenode_set = {} | 1578 msng_filenode_set = {} |
1667 | 1579 |
1668 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex | 1580 junk = mnfst.index[len(mnfst) - 1] # Get around a bug in lazyindex |
1669 junk = None | 1581 junk = None |
1670 | 1582 |
1671 # A changeset always belongs to itself, so the changenode lookup | 1583 # A changeset always belongs to itself, so the changenode lookup |
1672 # function for a changenode is identity. | 1584 # function for a changenode is identity. |
1673 def identity(x): | 1585 def identity(x): |
1858 if isinstance(fname, int): | 1770 if isinstance(fname, int): |
1859 continue | 1771 continue |
1860 add_extra_nodes(fname, | 1772 add_extra_nodes(fname, |
1861 msng_filenode_set.setdefault(fname, {})) | 1773 msng_filenode_set.setdefault(fname, {})) |
1862 changedfiles[fname] = 1 | 1774 changedfiles[fname] = 1 |
1863 changedfiles = changedfiles.keys() | |
1864 changedfiles.sort() | |
1865 # Go through all our files in order sorted by name. | 1775 # Go through all our files in order sorted by name. |
1866 for fname in changedfiles: | 1776 for fname in util.sort(changedfiles): |
1867 filerevlog = self.file(fname) | 1777 filerevlog = self.file(fname) |
1868 if filerevlog.count() == 0: | 1778 if not len(filerevlog): |
1869 raise util.Abort(_("empty or missing revlog for %s") % fname) | 1779 raise util.Abort(_("empty or missing revlog for %s") % fname) |
1870 # Toss out the filenodes that the recipient isn't really | 1780 # Toss out the filenodes that the recipient isn't really |
1871 # missing. | 1781 # missing. |
1872 if fname in msng_filenode_set: | 1782 if fname in msng_filenode_set: |
1873 prune_filenodes(fname, filerevlog) | 1783 prune_filenodes(fname, filerevlog) |
1914 self.changegroupinfo(nodes, source) | 1824 self.changegroupinfo(nodes, source) |
1915 | 1825 |
1916 def identity(x): | 1826 def identity(x): |
1917 return x | 1827 return x |
1918 | 1828 |
1919 def gennodelst(revlog): | 1829 def gennodelst(log): |
1920 for r in xrange(0, revlog.count()): | 1830 for r in log: |
1921 n = revlog.node(r) | 1831 n = log.node(r) |
1922 if revlog.linkrev(n) in revset: | 1832 if log.linkrev(n) in revset: |
1923 yield n | 1833 yield n |
1924 | 1834 |
1925 def changed_file_collector(changedfileset): | 1835 def changed_file_collector(changedfileset): |
1926 def collect_changed_files(clnode): | 1836 def collect_changed_files(clnode): |
1927 c = cl.read(clnode) | 1837 c = cl.read(clnode) |
1939 changedfiles = {} | 1849 changedfiles = {} |
1940 | 1850 |
1941 for chnk in cl.group(nodes, identity, | 1851 for chnk in cl.group(nodes, identity, |
1942 changed_file_collector(changedfiles)): | 1852 changed_file_collector(changedfiles)): |
1943 yield chnk | 1853 yield chnk |
1944 changedfiles = changedfiles.keys() | |
1945 changedfiles.sort() | |
1946 | 1854 |
1947 mnfst = self.manifest | 1855 mnfst = self.manifest |
1948 nodeiter = gennodelst(mnfst) | 1856 nodeiter = gennodelst(mnfst) |
1949 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)): | 1857 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)): |
1950 yield chnk | 1858 yield chnk |
1951 | 1859 |
1952 for fname in changedfiles: | 1860 for fname in util.sort(changedfiles): |
1953 filerevlog = self.file(fname) | 1861 filerevlog = self.file(fname) |
1954 if filerevlog.count() == 0: | 1862 if not len(filerevlog): |
1955 raise util.Abort(_("empty or missing revlog for %s") % fname) | 1863 raise util.Abort(_("empty or missing revlog for %s") % fname) |
1956 nodeiter = gennodelst(filerevlog) | 1864 nodeiter = gennodelst(filerevlog) |
1957 nodeiter = list(nodeiter) | 1865 nodeiter = list(nodeiter) |
1958 if nodeiter: | 1866 if nodeiter: |
1959 yield changegroup.chunkheader(len(fname)) | 1867 yield changegroup.chunkheader(len(fname)) |
1978 - less heads than before: -1-removed heads (-2..-n) | 1886 - less heads than before: -1-removed heads (-2..-n) |
1979 - number of heads stays the same: 1 | 1887 - number of heads stays the same: 1 |
1980 """ | 1888 """ |
1981 def csmap(x): | 1889 def csmap(x): |
1982 self.ui.debug(_("add changeset %s\n") % short(x)) | 1890 self.ui.debug(_("add changeset %s\n") % short(x)) |
1983 return cl.count() | 1891 return len(cl) |
1984 | 1892 |
1985 def revmap(x): | 1893 def revmap(x): |
1986 return cl.rev(x) | 1894 return cl.rev(x) |
1987 | 1895 |
1988 if not source: | 1896 if not source: |
2001 tr = self.transaction() | 1909 tr = self.transaction() |
2002 try: | 1910 try: |
2003 trp = weakref.proxy(tr) | 1911 trp = weakref.proxy(tr) |
2004 # pull off the changeset group | 1912 # pull off the changeset group |
2005 self.ui.status(_("adding changesets\n")) | 1913 self.ui.status(_("adding changesets\n")) |
2006 cor = cl.count() - 1 | 1914 cor = len(cl) - 1 |
2007 chunkiter = changegroup.chunkiter(source) | 1915 chunkiter = changegroup.chunkiter(source) |
2008 if cl.addgroup(chunkiter, csmap, trp, 1) is None and not emptyok: | 1916 if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok: |
2009 raise util.Abort(_("received changelog group is empty")) | 1917 raise util.Abort(_("received changelog group is empty")) |
2010 cnr = cl.count() - 1 | 1918 cnr = len(cl) - 1 |
2011 changesets = cnr - cor | 1919 changesets = cnr - cor |
2012 | 1920 |
2013 # pull off the manifest group | 1921 # pull off the manifest group |
2014 self.ui.status(_("adding manifests\n")) | 1922 self.ui.status(_("adding manifests\n")) |
2015 chunkiter = changegroup.chunkiter(source) | 1923 chunkiter = changegroup.chunkiter(source) |
2025 f = changegroup.getchunk(source) | 1933 f = changegroup.getchunk(source) |
2026 if not f: | 1934 if not f: |
2027 break | 1935 break |
2028 self.ui.debug(_("adding %s revisions\n") % f) | 1936 self.ui.debug(_("adding %s revisions\n") % f) |
2029 fl = self.file(f) | 1937 fl = self.file(f) |
2030 o = fl.count() | 1938 o = len(fl) |
2031 chunkiter = changegroup.chunkiter(source) | 1939 chunkiter = changegroup.chunkiter(source) |
2032 if fl.addgroup(chunkiter, revmap, trp) is None: | 1940 if fl.addgroup(chunkiter, revmap, trp) is None: |
2033 raise util.Abort(_("received file revlog group is empty")) | 1941 raise util.Abort(_("received file revlog group is empty")) |
2034 revisions += fl.count() - o | 1942 revisions += len(fl) - o |
2035 files += 1 | 1943 files += 1 |
2036 | 1944 |
2037 # make changelog see real files again | 1945 # make changelog see real files again |
2038 cl.finalize(trp) | 1946 cl.finalize(trp) |
2039 | 1947 |
2137 | 2045 |
2138 if stream and not heads and remote.capable('stream'): | 2046 if stream and not heads and remote.capable('stream'): |
2139 return self.stream_in(remote) | 2047 return self.stream_in(remote) |
2140 return self.pull(remote, heads) | 2048 return self.pull(remote, heads) |
2141 | 2049 |
2050 def storefiles(self): | |
2051 '''get all *.i and *.d files in the store | |
2052 | |
2053 Returns (list of (filename, size), total_bytes)''' | |
2054 | |
2055 lock = None | |
2056 try: | |
2057 self.ui.debug('scanning\n') | |
2058 entries = [] | |
2059 total_bytes = 0 | |
2060 # get consistent snapshot of repo, lock during scan | |
2061 lock = self.lock() | |
2062 for name, size in self.store.walk(): | |
2063 entries.append((name, size)) | |
2064 total_bytes += size | |
2065 return entries, total_bytes | |
2066 finally: | |
2067 del lock | |
2068 | |
2142 # used to avoid circular references so destructors work | 2069 # used to avoid circular references so destructors work |
2143 def aftertrans(files): | 2070 def aftertrans(files): |
2144 renamefiles = [tuple(t) for t in files] | 2071 renamefiles = [tuple(t) for t in files] |
2145 def a(): | 2072 def a(): |
2146 for src, dest in renamefiles: | 2073 for src, dest in renamefiles: |