comparison hgext/evolve.py @ 587:8152fedbac65 stable

evolve: smarter code for divergent changeset This newer version properly handle split now. Code and test copied for future upstream one.
author Pierre-Yves David <pierre-yves.david@logilab.fr>
date Tue, 23 Oct 2012 16:36:29 +0200
parents f01721161532
children 89c8550019d0
comparison
equal deleted inserted replaced
586:f01721161532 587:8152fedbac65
535 """the set of rev trying to obsolete public revision""" 535 """the set of rev trying to obsolete public revision"""
536 divergent = set() 536 divergent = set()
537 obsstore = repo.obsstore 537 obsstore = repo.obsstore
538 newermap = {} 538 newermap = {}
539 for ctx in repo.set('(not public()) - obsolete()'): 539 for ctx in repo.set('(not public()) - obsolete()'):
540 prec = obsstore.successors.get(ctx.node(), ()) 540 mark = obsstore.successors.get(ctx.node(), ())
541 toprocess = set(prec) 541 toprocess = set(mark)
542 while toprocess: 542 while toprocess:
543 prec = toprocess.pop()[0] 543 prec = toprocess.pop()[0]
544 if prec not in newermap: 544 if prec not in newermap:
545 newermap[prec] = newerversion(repo, prec) 545 successorssets(repo, prec, newermap)
546 newer = [n for n in newermap[prec] if n] # filter kill 546 newer = [n for n in newermap[prec] if n]
547 if len(newer) > 1: 547 if len(newer) > 1:
548 divergent.add(ctx.rev()) 548 divergent.add(ctx.rev())
549 break 549 break
550 toprocess.update(obsstore.successors.get(prec, ())) 550 toprocess.update(obsstore.successors.get(prec, ()))
551 return divergent 551 return divergent
552 552
553 ### changectx method 553 ### changectx method
554 554
555 @eh.addattr(context.changectx, 'latecomer') 555 @eh.addattr(context.changectx, 'latecomer')
812 for s in seen: 812 for s in seen:
813 sr = nm.get(s) 813 sr = nm.get(s)
814 if sr is not None: 814 if sr is not None:
815 cs.add(sr) 815 cs.add(sr)
816 return cs 816 return cs
817
818 nodemod = node
819 def successorssets(repo, initialnode, cache=None):
820 """Return the newer version of an obsolete changeset"""
821
822 # prec -> markers mapping
823 markersfor = repo.obsstore.precursors
824
825 # Stack of node need to know the last successors set
826 toproceed = [initialnode]
827 # set version of toproceed for fast loop detection
828 stackedset = set(toproceed)
829 if cache is None:
830 cache = {}
831 while toproceed:
832 # work on the last node of the stack
833 node = toproceed[-1]
834 if node in cache:
835 # We already have a value for it.
836 # Keep working on something else.
837 stackedset.remove(toproceed.pop())
838 elif node not in markersfor:
839 # The node is not obsolete.
840 # This mean it is its own last successors.
841 if node in repo:
842 # We have a valid last successors.
843 cache[node] = [(node,)]
844 else:
845 # final obsolete version is unknown locally.
846 # Do not count that as a valid successors
847 cache[node] = []
848 else:
849 # <lss> stand for Last Successors Sets
850 # it contains the list of all last successors for the current node.
851 lss = []
852 for mark in markersfor[node]:
853 # <mlss> stand for Marker Last Successors Sets
854 # it contains the list of last successors set introduced by
855 # this marker.
856 mlss = [[]]
857 # iterate over possible multiple successors
858 for suc in mark[1]:
859 if suc not in cache:
860 # We do not know the last successors of that yet.
861 if suc in stackedset:
862 # Loop detected!
863 #
864 # we won't be able to ever compute a proper last
865 # successors the naive and simple approve is to
866 # consider it killed
867 cache[suc] = []
868 else:
869 # Add the successor to the stack and break the next
870 # iteration will work on this successors and the
871 # algorithm will eventually process the current
872 # node again.
873 toproceed.append(suc)
874 stackedset.add(suc)
875 break
876 # if we did not break, we can extend the possible set of
877 # last successors.
878 #
879 # I say "extends" because if the marker have multiple
880 # successors we have to generate
881 #
882 # if successors have multiple successors set (when ther are
883 # divergent themself), we do a cartesian product of
884 # possible successors set of already processed successors
885 # and newly obtains successors set.
886 newmlss = []
887 for prefix in mlss:
888 for suffix in cache[suc]:
889 newss = list(prefix)
890 for part in suffix:
891 # do not duplicated entry in successors set.
892 # first entry win.
893 if part not in newss:
894 newss.append(part)
895 newmlss.append(newss)
896 mlss = newmlss
897 else:
898 # note: mlss is still empty if the marker was a bare killing
899 # of this changeset
900 #
901 # We extends the list of all possible successors sets with
902 # successors set continuted by this marker
903 lss.extend(mlss)
904 # we use continue here to skip the break right bellow
905 continue
906 # propagate "nested for" break.
907 # if the nested for exited on break, it did not ran the else
908 # clause and didn't "continue
909 break
910 else:
911 # computation was succesful for *all* marker.
912 # Add computed successors set to the cache
913 # (will be poped from to proceeed) on the new iteration
914 #
915 # We remove successors set that are subset of another one
916 # this fil
917 candsucset = sorted(((len(ss), set(ss), ss) for ss in lss),
918 reverse=True)
919 finalsucset = []
920 for cl, cs, css in candsucset:
921 if not css:
922 # remove empty successors set
923 continue
924 for fs, fss in finalsucset:
925 if cs.issubset(fs):
926 break
927 else:
928 finalsucset.append((cs, css))
929 finalsucset = [s[1] for s in finalsucset]
930 finalsucset.reverse()
931 cache[node] = finalsucset
932 return cache[initialnode]
817 933
818 934
819 935
820 def newerversion(repo, obs): 936 def newerversion(repo, obs):
821 """Return the newer version of an obsolete changeset""" 937 """Return the newer version of an obsolete changeset"""
1679 This only return the first one. 1795 This only return the first one.
1680 1796
1681 XXX this woobly function won't survive XXX 1797 XXX this woobly function won't survive XXX
1682 """ 1798 """
1683 for base in ctx._repo.set('reverse(precursors(%d))', ctx): 1799 for base in ctx._repo.set('reverse(precursors(%d))', ctx):
1684 newer = newerversion(ctx._repo, base.node()) 1800 newer = successorssets(ctx._repo, base.node())
1685 # drop filter and solution including the original ctx 1801 # drop filter and solution including the original ctx
1686 newer = [n for n in newer if n and ctx.node() not in n] 1802 newer = [n for n in newer if n and ctx.node() not in n]
1687 if newer: 1803 if newer:
1688 return base, tuple(ctx._repo[o] for o in newer[0]) 1804 return base, tuple(ctx._repo[o] for o in newer[0])
1689 raise KeyError('Base seem unknown. This case is not handled yet.') 1805 raise KeyError('Base seem unknown. This case is not handled yet.')
2139 if repo.revs('. and %ld', revs): 2255 if repo.revs('. and %ld', revs):
2140 hg.update(repo, newid) 2256 hg.update(repo, newid)
2141 finally: 2257 finally:
2142 lockmod.release(lock, wlock) 2258 lockmod.release(lock, wlock)
2143 2259
2260 if 'debugsuccessorssets' not in commands.table:
2261
2262 @command('debugsuccessorssets',
2263 [],
2264 _('[REV]'))
2265 def debugsuccessorssets(ui, repo, *revs):
2266 """show set of successors for revision
2267
2268 Successors set of changeset A are a consistent group of revision that
2269 succeed to A. Successors set contains non-obsolete changeset only.
2270
2271 In most case a changeset A have zero (changeset pruned) or a single
2272 successors set that contains a single successors (changeset A replacement
2273 by A')
2274
2275 But splitted changeset will result with successors set containing more than
2276 a single element. Divergent rewritting will result in multiple successor
2277 set.
2278
2279 result is displayed as follows::
2280
2281 <rev1>
2282 <successors-1A>
2283 <rev2>
2284 <successors-2A>
2285 <successors-2B1> <successors-2B1> <successors-2B1>
2286
2287 here rev2 have two possible successors sets. One hold three elements.
2288
2289 add --debug if you want full size node id.
2290 """
2291 cache = {}
2292 s = str
2293 if ui.debug:
2294 def s(ctx):
2295 return ctx.hex()
2296 for rev in scmutil.revrange(repo, revs):
2297 ctx = repo[rev]
2298 if ui.debug():
2299 ui.write('%s\n'% ctx.hex())
2300 s = node.hex
2301 else:
2302 ui.write('%s\n'% ctx)
2303 s = node.short
2304 for ss in successorssets(repo, ctx.node(), cache):
2305 if ss:
2306 ui.write(' ')
2307 ui.write(s(ss[0]))
2308 for n in ss[1:]:
2309 ui.write(' ')
2310 ui.write(s(n))
2311 ui.write('\n')
2312 pass
2313
2144 2314
2145 @eh.wrapcommand('graft') 2315 @eh.wrapcommand('graft')
2146 def graftwrapper(orig, ui, repo, *revs, **kwargs): 2316 def graftwrapper(orig, ui, repo, *revs, **kwargs):
2147 kwargs = dict(kwargs) 2317 kwargs = dict(kwargs)
2148 revs = list(revs) + kwargs.get('rev', []) 2318 revs = list(revs) + kwargs.get('rev', [])