Mercurial > evolve
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', []) |