comparison hgext/obsolete.py @ 334:fb83210bce32 stable

obsolete: move to official binary format
author Pierre-Yves David <pierre-yves.david@logilab.fr>
date Tue, 03 Jul 2012 12:58:55 +0200
parents 36e2016d6563
children c27a465daef2 6b92f8d5ae58
comparison
equal deleted inserted replaced
332:36e2016d6563 334:fb83210bce32
83 83
84 from mercurial.i18n import _ 84 from mercurial.i18n import _
85 85
86 import base64 86 import base64
87 import json 87 import json
88
89 import struct
90 from mercurial import util, base85
91
92 _pack = struct.pack
93 _unpack = struct.unpack
88 94
89 from mercurial import util 95 from mercurial import util
90 from mercurial import context 96 from mercurial import context
91 from mercurial import revset 97 from mercurial import revset
92 from mercurial import scmutil 98 from mercurial import scmutil
189 195
190 def _precursors(repo, s): 196 def _precursors(repo, s):
191 """Precursor of a changeset""" 197 """Precursor of a changeset"""
192 cs = set() 198 cs = set()
193 nm = repo.changelog.nodemap 199 nm = repo.changelog.nodemap
194 markerbysubj = repo.obsoletestore.subjects 200 markerbysubj = repo.obsstore.successors
195 for r in s: 201 for r in s:
196 for p in markerbysubj.get(repo[r].node(), ()): 202 for p in markerbysubj.get(repo[r].node(), ()):
197 pr = nm.get(p['object']) 203 pr = nm.get(p[0])
198 if pr is not None: 204 if pr is not None:
199 cs.add(pr) 205 cs.add(pr)
200 return cs 206 return cs
201 207
202 def revsetprecursors(repo, subset, x): 208 def revsetprecursors(repo, subset, x):
207 213
208 def _allprecursors(repo, s): # XXX we need a better naming 214 def _allprecursors(repo, s): # XXX we need a better naming
209 """transitive precursors of a subset""" 215 """transitive precursors of a subset"""
210 toproceed = [repo[r].node() for r in s] 216 toproceed = [repo[r].node() for r in s]
211 seen = set() 217 seen = set()
212 allsubjects = repo.obsoletestore.subjects 218 allsubjects = repo.obsstore.successors
213 while toproceed: 219 while toproceed:
214 nc = toproceed.pop() 220 nc = toproceed.pop()
215 for mark in allsubjects.get(nc, ()): 221 for mark in allsubjects.get(nc, ()):
216 np = mark['object'] 222 np = mark[0]
217 if np not in seen: 223 if np not in seen:
218 seen.add(np) 224 seen.add(np)
219 toproceed.append(np) 225 toproceed.append(np)
220 nm = repo.changelog.nodemap 226 nm = repo.changelog.nodemap
221 cs = set() 227 cs = set()
233 239
234 def _successors(repo, s): 240 def _successors(repo, s):
235 """Successors of a changeset""" 241 """Successors of a changeset"""
236 cs = set() 242 cs = set()
237 nm = repo.changelog.nodemap 243 nm = repo.changelog.nodemap
238 markerbyobj = repo.obsoletestore.objects 244 markerbyobj = repo.obsstore.precursors
239 for r in s: 245 for r in s:
240 for p in markerbyobj.get(repo[r].node(), ()): 246 for p in markerbyobj.get(repo[r].node(), ()):
241 for sub in p['subjects']: 247 for sub in p[1]:
242 sr = nm.get(sub) 248 sr = nm.get(sub)
243 if sr is not None: 249 if sr is not None:
244 cs.add(sr) 250 cs.add(sr)
245 return cs 251 return cs
246 252
252 258
253 def _allsuccessors(repo, s): # XXX we need a better naming 259 def _allsuccessors(repo, s): # XXX we need a better naming
254 """transitive successors of a subset""" 260 """transitive successors of a subset"""
255 toproceed = [repo[r].node() for r in s] 261 toproceed = [repo[r].node() for r in s]
256 seen = set() 262 seen = set()
257 allobjects = repo.obsoletestore.objects 263 allobjects = repo.obsstore.precursors
258 while toproceed: 264 while toproceed:
259 nc = toproceed.pop() 265 nc = toproceed.pop()
260 for mark in allobjects.get(nc, ()): 266 for mark in allobjects.get(nc, ()):
261 for sub in mark['subjects']: 267 for sub in mark[1]:
262 if sub not in seen: 268 if sub not in seen:
263 seen.add(sub) 269 seen.add(sub)
264 toproceed.append(sub) 270 toproceed.append(sub)
265 nm = repo.changelog.nodemap 271 nm = repo.changelog.nodemap
266 cs = set() 272 cs = set()
405 pass # rebase not found 411 pass # rebase not found
406 412
407 # Pushkey mechanism for mutable 413 # Pushkey mechanism for mutable
408 ######################################### 414 #########################################
409 415
410 def pushobsolete(repo, key, old, raw): 416 def listmarkers(repo):
411 """push obsolete relation through pushkey""" 417 """List markers over pushkey"""
412 assert key == "markers" 418 if not repo.obsstore:
413 l = repo.lock() 419 return {}
420 data = repo.obsstore._writemarkers()
421 return {'dump': base85.b85encode(data)}
422
423 def pushmarker(repo, key, old, new):
424 """Push markers over pushkey"""
425 if key != 'dump':
426 repo.ui.warn(_('unknown key: %r') % key)
427 return 0
428 if old:
429 repo.ui.warn(_('unexpected old value') % key)
430 return 0
431 data = base85.b85decode(new)
432 lock = repo.lock()
414 try: 433 try:
415 tmp = StringIO() 434 repo.obsstore.mergemarkers(data)
416 tmp.write(raw)
417 tmp.seek(0)
418 repo.obsoletestore.load(tmp)
419 repo.obsoletestore._dirty = True # XXX meh
420 return 1 435 return 1
421 finally: 436 finally:
422 l.release() 437 lock.release()
423 438
424 def listobsolete(repo): 439 pushkey.register('obsolete', pushmarker, listmarkers)
425 """dump all obsolete relation in
426
427 XXX this have be improved"""
428 tmp = StringIO()
429 repo.obsoletestore.save(tmp)
430 return {'markers': base64.b64encode(tmp.getvalue())}
431
432 pushkey.register('obsolete', pushobsolete, listobsolete)
433 440
434 ### Discovery wrapping 441 ### Discovery wrapping
435 ############################# 442 #############################
436 443
437 class blist(list, object): 444 class blist(list, object):
497 for branch, nodes in oldbm.iteritems(): 504 for branch, nodes in oldbm.iteritems():
498 nodes = list(nodes) 505 nodes = list(nodes)
499 new = set() 506 new = set()
500 while nodes: 507 while nodes:
501 n = nodes.pop() 508 n = nodes.pop()
502 if n in repo.obsoletestore.objects: 509 if n in repo.obsstore.precursors:
503 markers = repo.obsoletestore.objects[n] 510 markers = repo.obsstore.precursors[n]
504 for mark in markers: 511 for mark in markers:
505 for newernode in mark['subjects']: 512 for newernode in mark[1]:
506 if newernode is not None: 513 if newernode is not None:
507 nodes.append(newernode) 514 nodes.append(newernode)
508 else: 515 else:
509 new.add(n) 516 new.add(n)
510 if new: 517 if new:
561 @command('debugconvertobsolete', [], '') 568 @command('debugconvertobsolete', [], '')
562 def cmddebugconvertobsolete(ui, repo): 569 def cmddebugconvertobsolete(ui, repo):
563 """import markers from an .hg/obsolete-relations file""" 570 """import markers from an .hg/obsolete-relations file"""
564 cnt = 0 571 cnt = 0
565 l = repo.lock() 572 l = repo.lock()
573 some = False
566 try: 574 try:
567 repo._importoldobsolete = True 575 repo._importoldobsolete = True
568 store = repo.obsoletestore 576 store = repo.obsstore
577 ### very first format
569 try: 578 try:
570 f = repo.opener('obsolete-relations') 579 f = repo.opener('obsolete-relations')
571 try: 580 try:
581 some = True
572 for line in f: 582 for line in f:
573 subhex, objhex = line.split() 583 subhex, objhex = line.split()
574 sub = bin(subhex) 584 suc = bin(subhex)
575 obj = bin(objhex) 585 prec = bin(objhex)
576 newmarker = { 586 sucs = (suc==nullid) and [] or [suc]
577 'subjects': (sub==nullid) and [] or [sub], 587 meta = {
578 'object': obj, 588 'date': '%i %i' % util.makedate(),
579 'date': util.makedate(),
580 'user': ui.username(), 589 'user': ui.username(),
581 'reason': 'import from older format.',
582 } 590 }
583 store.new(newmarker) 591 store.create(prec, sucs, 0, meta)
584 store._dirty = True
585 cnt += 1 592 cnt += 1
586 finally: 593 finally:
587 f.close() 594 f.close()
588 util.unlink(repo.join('obsolete-relations')) 595 util.unlink(repo.join('obsolete-relations'))
589 except IOError: 596 except IOError:
590 ui.warn('nothing to do\n')
591 pass 597 pass
598 ### second (json) format
599 data = repo.sopener.tryread('obsoletemarkers')
600 if data:
601 some = True
602 for oldmark in json.loads(data):
603 del oldmark['id'] # dropped for now
604 del oldmark['reason'] # unused until then
605 oldmark['subjects'] = [bin(n) for n in oldmark['subjects']]
606 oldmark['object'] = bin(oldmark['object'])
607 oldmark['date'] = '%i %i' % tuple(oldmark['date'])
608 store.create(oldmark.pop('object'),
609 oldmark.pop('subjects'),
610 0, oldmark)
611 cnt += 1
612 util.unlink(repo.sjoin('obsoletemarkers'))
592 finally: 613 finally:
593 del repo._importoldobsolete 614 del repo._importoldobsolete
594 l.release() 615 l.release()
616 if not some:
617 ui.warn('nothing to do\n')
595 ui.status('%i obsolete marker converted\n' % cnt) 618 ui.status('%i obsolete marker converted\n' % cnt)
596 619
597 @command('debugsuccessors', [], '') 620 @command('debugsuccessors', [], '')
598 def cmddebugsuccessors(ui, repo): 621 def cmddebugsuccessors(ui, repo):
599 """dump obsolete changesets and their successors 622 """dump obsolete changesets and their successors
601 Each line matches an existing marker, the first identifier is the 624 Each line matches an existing marker, the first identifier is the
602 obsolete changeset identifier, followed by it successors. 625 obsolete changeset identifier, followed by it successors.
603 """ 626 """
604 lock = repo.lock() 627 lock = repo.lock()
605 try: 628 try:
606 allsuccessors = repo.obsoletestore.objects 629 allsuccessors = repo.obsstore.precursors
607 for old in sorted(allsuccessors): 630 for old in sorted(allsuccessors):
608 successors = [sorted(m['subjects']) for m in allsuccessors[old]] 631 successors = [sorted(m[1]) for m in allsuccessors[old]]
609 for i, group in enumerate(sorted(successors)): 632 for i, group in enumerate(sorted(successors)):
610 ui.write('%s' % short(old)) 633 ui.write('%s' % short(old))
611 for new in group: 634 for new in group:
612 ui.write(' %s' % short(new)) 635 ui.write(' %s' % short(new))
613 ui.write('\n') 636 ui.write('\n')
631 oldnode = old.node() 654 oldnode = old.node()
632 new = orig(ui, repo, commitfunc, old, *args, **kwargs) 655 new = orig(ui, repo, commitfunc, old, *args, **kwargs)
633 if new != oldnode: 656 if new != oldnode:
634 lock = repo.lock() 657 lock = repo.lock()
635 try: 658 try:
636 newmarker = { 659 meta = {
637 'subjects': [new], 660 'subjects': [new],
638 'object': oldnode, 661 'object': oldnode,
639 'date': util.makedate(), 662 'date': util.makedate(),
640 'user': ui.username(), 663 'user': ui.username(),
641 'reason': 'commit --amend', 664 'reason': 'commit --amend',
642 } 665 }
643 repo.obsoletestore.new(newmarker) 666 repo.obsstore.create(oldnode, [new], 0, meta)
644 repo._clearobsoletecache() 667 repo._clearobsoletecache()
645 repo._turn_extinct_secret() 668 repo._turn_extinct_secret()
646 finally: 669 finally:
647 lock.release() 670 lock.release()
648 return new 671 return new
699 def newerversion(repo, obs): 722 def newerversion(repo, obs):
700 """Return the newer version of an obsolete changeset""" 723 """Return the newer version of an obsolete changeset"""
701 toproceed = set([(obs,)]) 724 toproceed = set([(obs,)])
702 # XXX known optimization available 725 # XXX known optimization available
703 newer = set() 726 newer = set()
704 objectrels = repo.obsoletestore.objects 727 objectrels = repo.obsstore.precursors
705 while toproceed: 728 while toproceed:
706 current = toproceed.pop() 729 current = toproceed.pop()
707 assert len(current) <= 1, 'splitting not handled yet. %r' % current 730 assert len(current) <= 1, 'splitting not handled yet. %r' % current
708 if current: 731 if current:
709 n, = current 732 n, = current
710 if n in objectrels: 733 if n in objectrels:
711 markers = objectrels[n] 734 markers = objectrels[n]
712 for mark in markers: 735 for mark in markers:
713 toproceed.add(tuple(mark['subjects'])) 736 toproceed.add(tuple(mark[1]))
714 else: 737 else:
715 newer.add(tuple(current)) 738 newer.add(tuple(current))
716 else: 739 else:
717 newer.add(()) 740 newer.add(())
718 return sorted(newer) 741 return sorted(newer)
738 else: 761 else:
739 a.update(str(marker[key])) 762 a.update(str(marker[key]))
740 a.update('\0') 763 a.update('\0')
741 return a.digest() 764 return a.digest()
742 765
743 class obsoletestore(object): 766 # mercurial backport
744 """Store obsolete relations 767
745 768 def encodemeta(meta):
746 Relation are stored in three mapping. All mapping have "obsolete markers" 769 """Return encoded metadata string to string mapping.
747 as values:: 770
748 771 Assume no ':' in key and no '\0' in both key and value."""
749 {'id': "unique id of the obsolete marker" 772 for key, value in meta.iteritems():
750 'subjects': "0-N newer version of changeset in "object" (as ordered list) 773 if ':' in key or '\0' in key:
751 'object': "old and obsolete version" 774 raise ValueError("':' and '\0' are forbidden in metadata key'")
752 'date': "When was this marker created ?" 775 if '\0' in value:
753 'user': "Who did that ?" 776 raise ValueError("':' are forbidden in metadata value'")
754 'reason': "Why was it done" 777 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
755 } 778
756 779 def decodemeta(data):
757 Three keys exists 780 """Return string to string dictionary from encoded version."""
758 781 d = {}
759 :self._markers: "id" -> marker 782 for l in data.split('\0'):
760 783 if l:
761 :self.subjects: "subject" -> set(marker) 784 key, value = l.split(':')
762 785 d[key] = value
763 :self.objects: "object" -> set(marker) 786 return d
787
788 # data used for parsing and writing
789 _fmversion = 0
790 _fmfixed = '>BIB20s'
791 _fmnode = '20s'
792 _fmfsize = struct.calcsize(_fmfixed)
793 _fnodesize = struct.calcsize(_fmnode)
794
795 def _readmarkers(data):
796 """Read and enumerate markers from raw data"""
797 off = 0
798 diskversion = _unpack('>B', data[off:off + 1])[0]
799 off += 1
800 if diskversion != _fmversion:
801 raise util.Abort(_('parsing obsolete marker: unknown version %r')
802 % diskversion)
803
804 # Loop on markers
805 l = len(data)
806 while off + _fmfsize <= l:
807 # read fixed part
808 cur = data[off:off + _fmfsize]
809 off += _fmfsize
810 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
811 # read replacement
812 sucs = ()
813 if nbsuc:
814 s = (_fnodesize * nbsuc)
815 cur = data[off:off + s]
816 sucs = _unpack(_fmnode * nbsuc, cur)
817 off += s
818 # read metadata
819 # (metadata will be decoded on demand)
820 metadata = data[off:off + mdsize]
821 if len(metadata) != mdsize:
822 raise util.Abort(_('parsing obsolete marker: metadata is too '
823 'short, %d bytes expected, got %d')
824 % (len(metadata), mdsize))
825 off += mdsize
826 yield (pre, sucs, flags, metadata)
827
828 class obsstore(object):
829 """Store obsolete markers
830
831 Markers can be accessed with two mappings:
832 - precursors: old -> set(new)
833 - successors: new -> set(old)
764 """ 834 """
765 835
766 def __init__(self): 836 def __init__(self):
767 self._markers = {} 837 self._all = []
768 self.subjects = {} 838 # new markers to serialize
769 self.objects = {} 839 self._new = []
770 self._dirty = False # should be on repo 840 self.precursors = {}
771 841 self.successors = {}
772 def new(self, marker): 842
773 """Add a *new* marker to the store. computing it's ID""" 843 def __iter__(self):
774 mid = marker['id'] = markerid(marker) 844 return iter(self._all)
775 self._insert(marker) 845
776 self._dirty = True 846 def __nonzero__(self):
777 return mid 847 return bool(self._all)
778 848
779 def _insert(self, marker): 849 def create(self, prec, succs=(), flag=0, metadata=None):
780 if marker['id'] not in self._markers: 850 """obsolete: add a new obsolete marker
781 self._markers[marker['id']] = marker 851
782 add2set(self.objects, marker['object'], marker) 852 * ensuring it is hashable
783 for subj in marker['subjects']: 853 * check mandatory metadata
784 add2set(self.subjects, subj, marker) 854 * encode metadata
785 855 """
786 def save(self, stream): 856 if metadata is None:
787 markers = [] 857 metadata = {}
788 for mark in self._markers.itervalues(): 858 if len(prec) != 20:
789 jmark = mark.copy() 859 raise ValueError(prec)
790 jmark['id'] = hex(jmark['id']) 860 for succ in succs:
791 jmark['subjects'] = [hex(n) for n in jmark['subjects']] 861 if len(succ) != 20:
792 jmark['object'] = hex(jmark['object']) 862 raise ValueError(prec)
793 markers.append(jmark) 863 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
794 json.dump(markers, stream, indent=4) 864 self.add(marker)
795 865
796 def load(self, stream): 866 def add(self, marker):
797 for mark in json.load(stream): 867 """Add a new marker to the store
798 mark['id'] = bin(mark['id']) 868
799 mark['subjects'] = [bin(n) for n in mark['subjects']] 869 This marker still needs to be written to disk"""
800 mark['object'] = bin(mark['object']) 870 self._new.append(marker)
801 self._insert(mark) 871 self._load(marker)
802 872
803 def writeobsolete(repo): 873 def loadmarkers(self, data):
804 """wire obsolete data on disk""" 874 """Load all markers in data, mark them as known."""
805 f = repo.sopener('obsoletemarkers', 'w', atomictemp=True) 875 for marker in _readmarkers(data):
806 try: 876 self._load(marker)
807 repo.obsoletestore.save(f) 877
808 repo._dirty = False 878 def mergemarkers(self, data):
809 finally: 879 other = set(_readmarkers(data))
810 f.close() 880 local = set(self._all)
881 new = other - local
882 for marker in new:
883 self.add(marker)
884
885 def flushmarkers(self, stream):
886 """Write all markers to a stream
887
888 After this operation, "new" markers are considered "known"."""
889 self._writemarkers(stream)
890 self._new[:] = []
891
892 def _load(self, marker):
893 self._all.append(marker)
894 pre, sucs = marker[:2]
895 self.precursors.setdefault(pre, set()).add(marker)
896 for suc in sucs:
897 self.successors.setdefault(suc, set()).add(marker)
898
899 def _writemarkers(self, stream=None):
900 # Kept separate from flushmarkers(), it will be reused for
901 # markers exchange.
902 if stream is None:
903 final = []
904 w = final.append
905 else:
906 w = stream.write
907 w(_pack('>B', _fmversion))
908 for marker in self._all:
909 pre, sucs, flags, metadata = marker
910 nbsuc = len(sucs)
911 format = _fmfixed + (_fmnode * nbsuc)
912 data = [nbsuc, len(metadata), flags, pre]
913 data.extend(sucs)
914 w(_pack(format, *data))
915 w(metadata)
916 if stream is None:
917 return ''.join(final)
811 918
812 919
813 ### repo subclassing 920 ### repo subclassing
814 ############################# 921 #############################
815 922
833 940
834 ### Public method 941 ### Public method
835 def obsoletedby(self, node): 942 def obsoletedby(self, node):
836 """return the set of node that make <node> obsolete (obj)""" 943 """return the set of node that make <node> obsolete (obj)"""
837 others = set() 944 others = set()
838 for marker in self.obsoletestore.objects.get(node, []): 945 for marker in self.obsstore.precursors.get(node, []):
839 others.update(marker['subjects']) 946 others.update(marker[1])
840 return others 947 return others
841 948
842 def obsolete(self, node): 949 def obsolete(self, node):
843 """return the set of node that <node> make obsolete (sub)""" 950 """return the set of node that <node> make obsolete (sub)"""
844 return set(marker['object'] for marker in self.obsoletestore.subjects.get(node, [])) 951 return set(marker[0] for marker in self.obsstore.successors.get(node, []))
845 952
846 @util.propertycache 953 @storecache('obsstore')
847 def obsoletestore(self): 954 def obsstore(self):
848 if not getattr(self, '_importoldobsolete', False): 955 if not getattr(self, '_importoldobsolete', False):
849 try: 956 data = repo.opener.tryread('obsolete-relations')
850 f = self.opener('obsolete-relations') 957 if not data:
851 f.close() 958 data = repo.sopener.tryread('obsoletemarkers')
959 if data:
852 raise util.Abort('old format of obsolete marker detected!\n' 960 raise util.Abort('old format of obsolete marker detected!\n'
853 'run `hg debugconvertobsolete` once.') 961 'run `hg debugconvertobsolete` once.')
854 except IOError: 962 store = obsstore()
855 pass 963 data = self.sopener.tryread('obsstore')
856 store = obsoletestore() 964 if data:
857 try: 965 store.loadmarkers(data)
858 f = self.sopener('obsoletemarkers')
859 store.load(f)
860 except IOError:
861 pass
862 return store 966 return store
863 967
864 @util.propertycache 968 @util.propertycache
865 def _obsoleteset(self): 969 def _obsoleteset(self):
866 """the set of obsolete revision""" 970 """the set of obsolete revision"""
867 obs = set() 971 obs = set()
868 nm = self.changelog.nodemap 972 nm = self.changelog.nodemap
869 for obj in self.obsoletestore.objects: 973 for obj in self.obsstore.precursors:
870 try: # /!\api change in Hg 2.2 (e8d37b78acfb22ae2c1fb126c2)/!\ 974 try: # /!\api change in Hg 2.2 (e8d37b78acfb22ae2c1fb126c2)/!\
871 rev = nm.get(obj) 975 rev = nm.get(obj)
872 except TypeError: #XXX to remove while breaking Hg 2.1 support 976 except TypeError: #XXX to remove while breaking Hg 2.1 support
873 rev = nm.get(obj, None) 977 rev = nm.get(obj, None)
874 if rev is not None: 978 if rev is not None:
927 self.ui.warn( 1031 self.ui.warn(
928 _("%(sub)s try to obsolete immutable changeset %(obj)s\n") 1032 _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
929 % {'sub': short(sub), 'obj': short(obj)}) 1033 % {'sub': short(sub), 'obj': short(obj)})
930 lock = self.lock() 1034 lock = self.lock()
931 try: 1035 try:
932 newmarker = { 1036 meta = {
933 'subjects': (sub==nullid) and [] or [sub],
934 'object': obj,
935 'date': util.makedate(), 1037 'date': util.makedate(),
936 'user': ui.username(), 1038 'user': ui.username(),
937 'reason': 'unknown', 1039 'reason': 'unknown',
938 } 1040 }
939 mid = self.obsoletestore.new(newmarker) 1041 subs = (sub == nullid) and [] or [sub]
1042 mid = self.obsstore.create(obj, subs, 0, meta)
940 self._clearobsoletecache() 1043 self._clearobsoletecache()
941 self._turn_extinct_secret() 1044 self._turn_extinct_secret()
942 return mid 1045 return mid
943 finally: 1046 finally:
944 lock.release() 1047 lock.release()
966 def lock(self, *args, **kwargs): 1069 def lock(self, *args, **kwargs):
967 l = olock(*args, **kwargs) 1070 l = olock(*args, **kwargs)
968 if not getattr(l.releasefn, 'obspatched', False): 1071 if not getattr(l.releasefn, 'obspatched', False):
969 oreleasefn = l.releasefn 1072 oreleasefn = l.releasefn
970 def releasefn(*args, **kwargs): 1073 def releasefn(*args, **kwargs):
971 if self.obsoletestore._dirty: 1074 if 'obsstore' in vars(self) and self.obsstore._new:
972 writeobsolete(self) 1075 f = self.sopener('obsstore', 'wb', atomictemp=True)
1076 try:
1077 self.obsstore.flushmarkers(f)
1078 f.close()
1079 except: # re-raises
1080 f.discard()
1081 raise
973 oreleasefn(*args, **kwargs) 1082 oreleasefn(*args, **kwargs)
974 releasefn.obspatched = True 1083 releasefn.obspatched = True
975 l.releasefn = releasefn 1084 l.releasefn = releasefn
976 return l 1085 return l
977
978 def _readobsrels(self):
979 """Read obsolete relation on disk"""
980 # XXX handle lock
981 try:
982 f = self.opener('obsolete-relations')
983 try:
984 return _obsdeserialise(f)
985 finally:
986 f.close()
987 except IOError:
988 return {}
989 1086
990 1087
991 ### pull // push support 1088 ### pull // push support
992 1089
993 def pull(self, remote, *args, **kwargs): 1090 def pull(self, remote, *args, **kwargs):
994 """wrapper around push that push obsolete relation""" 1091 """wrapper around push that push obsolete relation"""
995 l = repo.lock() 1092 l = repo.lock()
996 try: 1093 try:
997 result = opull(remote, *args, **kwargs) 1094 result = opull(remote, *args, **kwargs)
998 if 'obsolete' in remote.listkeys('namespaces'): 1095 remoteobs = remote.listkeys('obsolete')
999 tmp = StringIO() 1096 if 'dump' in remoteobs:
1000 rels = remote.listkeys('obsolete')['markers'] 1097 data = base85.b85decode(remoteobs['dump'])
1001 tmp.write(base64.b64decode(rels)) 1098 self.obsstore.mergemarkers(data)
1002 tmp.seek(0)
1003 repo.obsoletestore.load(tmp)
1004 repo.obsoletestore._dirty = True # XXX meh
1005 self._clearobsoletecache() 1099 self._clearobsoletecache()
1006 self._turn_extinct_secret() 1100 self._turn_extinct_secret()
1007 return result 1101 return result
1008 finally: 1102 finally:
1009 l.release() 1103 l.release()
1010 1104
1011 def push(self, remote, *args, **opts): 1105 def push(self, remote, *args, **opts):
1012 """wrapper around pull that pull obsolete relation""" 1106 """wrapper around pull that pull obsolete relation"""
1013 self._turn_extinct_secret() 1107 self._turn_extinct_secret()
1014 result = opush(remote, *args, **opts) 1108 result = opush(remote, *args, **opts)
1015 if 'obsolete' in remote.listkeys('namespaces'): 1109 if 'obsolete' in self.listkeys('namespaces') and self.obsstore:
1016 tmp = StringIO() 1110 data = self.obsstore._writemarkers()
1017 self.obsoletestore.save(tmp) 1111 r = remote.pushkey('obsolete', 'dump', '',
1018 remote.pushkey('obsolete', 'markers', '', tmp.getvalue()) 1112 base85.b85encode(data))
1113 if not r:
1114 self.ui.warn(_('failed to push obsolete markers!\n'))
1019 self._turn_extinct_secret() 1115 self._turn_extinct_secret()
1020 1116
1021 return result 1117 return result
1022 1118
1023 1119
1024 ### rollback support 1120 ### rollback support
1025 1121
1026 # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\ 1122 # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\
1027 if util.safehasattr(repo, '_journalfiles'): # Hg 2.2 1123 if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
1028 def _journalfiles(self): 1124 def _journalfiles(self):
1029 return o_journalfiles() + (self.sjoin('journal.obsoletemarkers'),) 1125 return o_journalfiles() + (self.sjoin('journal.obsstore'),)
1030 1126
1031 def _writejournal(self, desc): 1127 def _writejournal(self, desc):
1032 """wrapped version of _writejournal that save obsolete data""" 1128 """wrapped version of _writejournal that save obsolete data"""
1033 o_writejournal(desc) 1129 o_writejournal(desc)
1034 filename = 'obsoletemarkers' 1130 filename = 'obsstore'
1035 filepath = self.sjoin(filename) 1131 filepath = self.sjoin(filename)
1036 if os.path.exists(filepath): 1132 if os.path.exists(filepath):
1037 journalname = 'journal.' + filename 1133 journalname = 'journal.' + filename
1038 journalpath = self.sjoin(journalname) 1134 journalpath = self.sjoin(journalname)
1039 util.copyfile(filepath, journalpath) 1135 util.copyfile(filepath, journalpath)
1040 1136
1041 else: # XXX removing this bloc will break Hg 2.1 support 1137 else: # XXX removing this bloc will break Hg 2.1 support
1042 def _writejournal(self, desc): 1138 def _writejournal(self, desc):
1043 """wrapped version of _writejournal that save obsolete data""" 1139 """wrapped version of _writejournal that save obsolete data"""
1044 entries = list(o_writejournal(desc)) 1140 entries = list(o_writejournal(desc))
1045 filename = 'obsoletemarkers' 1141 filename = 'obsstore'
1046 filepath = self.sjoin(filename) 1142 filepath = self.sjoin(filename)
1047 if os.path.exists(filepath): 1143 if os.path.exists(filepath):
1048 journalname = 'journal.' + filename 1144 journalname = 'journal.' + filename
1049 journalpath = self.sjoin(journalname) 1145 journalpath = self.sjoin(journalname)
1050 util.copyfile(filepath, journalpath) 1146 util.copyfile(filepath, journalpath)
1053 1149
1054 def _rollback(self, dryrun, force): 1150 def _rollback(self, dryrun, force):
1055 """wrapped version of _rollback that restore obsolete data""" 1151 """wrapped version of _rollback that restore obsolete data"""
1056 ret = o_rollback(dryrun, force) 1152 ret = o_rollback(dryrun, force)
1057 if not (ret or dryrun): #rollback did not failed 1153 if not (ret or dryrun): #rollback did not failed
1058 src = self.sjoin('undo.obsoletemarkers') 1154 src = self.sjoin('undo.obsstore')
1059 dst = self.sjoin('obsoletemarkers') 1155 dst = self.sjoin('obsstore')
1060 if os.path.exists(src): 1156 if os.path.exists(src):
1061 util.rename(src, dst) 1157 util.rename(src, dst)
1062 elif os.path.exists(dst): 1158 elif os.path.exists(dst):
1063 # If no state was saved because the file did not existed before. 1159 # If no state was saved because the file did not existed before.
1064 os.unlink(dst) 1160 os.unlink(dst)
1065 # invalidate cache 1161 # invalidate cache
1066 self.__dict__.pop('obsoletestore', None) 1162 self.__dict__.pop('obsstore', None)
1067 return ret 1163 return ret
1068 1164
1069 @storecache('00changelog.i') 1165 @storecache('00changelog.i')
1070 def changelog(self): 1166 def changelog(self):
1071 # << copy pasted from mercurial source 1167 # << copy pasted from mercurial source