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: