52 # bumped version and fix the situation, breaking the transitivity of |
50 # bumped version and fix the situation, breaking the transitivity of |
53 # "bumped" here. |
51 # "bumped" here. |
54 bumpedfix = 1 |
52 bumpedfix = 1 |
55 usingsha256 = 2 |
53 usingsha256 = 2 |
56 |
54 |
|
55 |
57 class marker(object): |
56 class marker(object): |
58 """Wrap obsolete marker raw data""" |
57 """Wrap obsolete marker raw data""" |
59 |
58 |
60 def __init__(self, repo, data): |
59 def __init__(self, repo, data): |
61 # the repo argument will be used to create changectx in later version |
60 # the repo argument will be used to create changectx in later version |
92 return self._data[4] |
91 return self._data[4] |
93 |
92 |
94 def flags(self): |
93 def flags(self): |
95 """The flags field of the marker""" |
94 """The flags field of the marker""" |
96 return self._data[2] |
95 return self._data[2] |
|
96 |
97 |
97 |
98 def getmarkers(repo, nodes=None, exclusive=False): |
98 def getmarkers(repo, nodes=None, exclusive=False): |
99 """returns markers known in a repository |
99 """returns markers known in a repository |
100 |
100 |
101 If <nodes> is specified, only markers "relevant" to those nodes are are |
101 If <nodes> is specified, only markers "relevant" to those nodes are are |
108 rawmarkers = repo.obsstore.relevantmarkers(nodes) |
108 rawmarkers = repo.obsstore.relevantmarkers(nodes) |
109 |
109 |
110 for markerdata in rawmarkers: |
110 for markerdata in rawmarkers: |
111 yield marker(repo, markerdata) |
111 yield marker(repo, markerdata) |
112 |
112 |
|
113 |
113 def closestpredecessors(repo, nodeid): |
114 def closestpredecessors(repo, nodeid): |
114 """yield the list of next predecessors pointing on visible changectx nodes |
115 """yield the list of next predecessors pointing on visible changectx nodes |
115 |
116 |
116 This function respect the repoview filtering, filtered revision will be |
117 This function respect the repoview filtering, filtered revision will be |
117 considered missing. |
118 considered missing. |
135 |
136 |
136 if precnodeid in repo: |
137 if precnodeid in repo: |
137 yield precnodeid |
138 yield precnodeid |
138 else: |
139 else: |
139 stack.append(precnodeid) |
140 stack.append(precnodeid) |
|
141 |
140 |
142 |
141 def allpredecessors(obsstore, nodes, ignoreflags=0): |
143 def allpredecessors(obsstore, nodes, ignoreflags=0): |
142 """Yield node for every precursors of <nodes>. |
144 """Yield node for every precursors of <nodes>. |
143 |
145 |
144 Some precursors may be unknown locally. |
146 Some precursors may be unknown locally. |
180 for suc in mark[1]: |
183 for suc in mark[1]: |
181 if suc not in seen: |
184 if suc not in seen: |
182 seen.add(suc) |
185 seen.add(suc) |
183 remaining.add(suc) |
186 remaining.add(suc) |
184 |
187 |
|
188 |
185 def _filterprunes(markers): |
189 def _filterprunes(markers): |
186 """return a set with no prune markers""" |
190 """return a set with no prune markers""" |
187 return set(m for m in markers if m[1]) |
191 return set(m for m in markers if m[1]) |
|
192 |
188 |
193 |
189 def exclusivemarkers(repo, nodes): |
194 def exclusivemarkers(repo, nodes): |
190 """set of markers relevant to "nodes" but no other locally-known nodes |
195 """set of markers relevant to "nodes" but no other locally-known nodes |
191 |
196 |
192 This function compute the set of markers "exclusive" to a locally-known |
197 This function compute the set of markers "exclusive" to a locally-known |
305 seennodes.add(prec) |
310 seennodes.add(prec) |
306 stack.append(prec) |
311 stack.append(prec) |
307 |
312 |
308 return exclmarkers |
313 return exclmarkers |
309 |
314 |
|
315 |
310 def foreground(repo, nodes): |
316 def foreground(repo, nodes): |
311 """return all nodes in the "foreground" of other node |
317 """return all nodes in the "foreground" of other node |
312 |
318 |
313 The foreground of a revision is anything reachable using parent -> children |
319 The foreground of a revision is anything reachable using parent -> children |
314 or precursor -> successor relation. It is very similar to "descendant" but |
320 or precursor -> successor relation. It is very similar to "descendant" but |
331 succs.update(allsuccessors(repo.obsstore, mutable)) |
337 succs.update(allsuccessors(repo.obsstore, mutable)) |
332 known = (n for n in succs if n in nm) |
338 known = (n for n in succs if n in nm) |
333 foreground = set(repo.set('%ln::', known)) |
339 foreground = set(repo.set('%ln::', known)) |
334 return set(c.node() for c in foreground) |
340 return set(c.node() for c in foreground) |
335 |
341 |
|
342 |
336 # effectflag field |
343 # effectflag field |
337 # |
344 # |
338 # Effect-flag is a 1-byte bit field used to store what changed between a |
345 # Effect-flag is a 1-byte bit field used to store what changed between a |
339 # changeset and its successor(s). |
346 # changeset and its successor(s). |
340 # |
347 # |
348 # `effect-flags` set to off by default. |
355 # `effect-flags` set to off by default. |
349 # |
356 # |
350 |
357 |
351 EFFECTFLAGFIELD = "ef1" |
358 EFFECTFLAGFIELD = "ef1" |
352 |
359 |
353 DESCCHANGED = 1 << 0 # action changed the description |
360 DESCCHANGED = 1 << 0 # action changed the description |
354 METACHANGED = 1 << 1 # action change the meta |
361 METACHANGED = 1 << 1 # action change the meta |
355 DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset |
362 DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset |
356 PARENTCHANGED = 1 << 2 # action change the parent |
363 PARENTCHANGED = 1 << 2 # action change the parent |
357 USERCHANGED = 1 << 4 # the user changed |
364 USERCHANGED = 1 << 4 # the user changed |
358 DATECHANGED = 1 << 5 # the date changed |
365 DATECHANGED = 1 << 5 # the date changed |
359 BRANCHCHANGED = 1 << 6 # the branch changed |
366 BRANCHCHANGED = 1 << 6 # the branch changed |
360 |
367 |
361 METABLACKLIST = [ |
368 METABLACKLIST = [ |
362 re.compile('^branch$'), |
369 re.compile('^branch$'), |
363 re.compile('^.*-source$'), |
370 re.compile('^.*-source$'), |
364 re.compile('^.*_source$'), |
371 re.compile('^.*_source$'), |
365 re.compile('^source$'), |
372 re.compile('^source$'), |
366 ] |
373 ] |
367 |
374 |
|
375 |
368 def metanotblacklisted(metaitem): |
376 def metanotblacklisted(metaitem): |
369 """ Check that the key of a meta item (extrakey, extravalue) does not |
377 """ Check that the key of a meta item (extrakey, extravalue) does not |
370 match at least one of the blacklist pattern |
378 match at least one of the blacklist pattern |
371 """ |
379 """ |
372 metakey = metaitem[0] |
380 metakey = metaitem[0] |
373 |
381 |
374 return not any(pattern.match(metakey) for pattern in METABLACKLIST) |
382 return not any(pattern.match(metakey) for pattern in METABLACKLIST) |
|
383 |
375 |
384 |
376 def _prepare_hunk(hunk): |
385 def _prepare_hunk(hunk): |
377 """Drop all information but the username and patch""" |
386 """Drop all information but the username and patch""" |
378 cleanhunk = [] |
387 cleanhunk = [] |
379 for line in hunk.splitlines(): |
388 for line in hunk.splitlines(): |
381 if line.startswith(b'@@'): |
390 if line.startswith(b'@@'): |
382 line = b'@@\n' |
391 line = b'@@\n' |
383 cleanhunk.append(line) |
392 cleanhunk.append(line) |
384 return cleanhunk |
393 return cleanhunk |
385 |
394 |
|
395 |
386 def _getdifflines(iterdiff): |
396 def _getdifflines(iterdiff): |
387 """return a cleaned up lines""" |
397 """return a cleaned up lines""" |
388 lines = next(iterdiff, None) |
398 lines = next(iterdiff, None) |
389 |
399 |
390 if lines is None: |
400 if lines is None: |
391 return lines |
401 return lines |
392 |
402 |
393 return _prepare_hunk(lines) |
403 return _prepare_hunk(lines) |
|
404 |
394 |
405 |
395 def _cmpdiff(leftctx, rightctx): |
406 def _cmpdiff(leftctx, rightctx): |
396 """return True if both ctx introduce the "same diff" |
407 """return True if both ctx introduce the "same diff" |
397 |
408 |
398 This is a first and basic implementation, with many shortcoming. |
409 This is a first and basic implementation, with many shortcoming. |
459 # Check if the diff has changed |
471 # Check if the diff has changed |
460 if not _cmpdiff(source, changectx): |
472 if not _cmpdiff(source, changectx): |
461 effects |= DIFFCHANGED |
473 effects |= DIFFCHANGED |
462 |
474 |
463 return effects |
475 return effects |
|
476 |
464 |
477 |
465 def getobsoleted(repo, tr): |
478 def getobsoleted(repo, tr): |
466 """return the set of pre-existing revisions obsoleted by a transaction""" |
479 """return the set of pre-existing revisions obsoleted by a transaction""" |
467 torev = repo.unfiltered().changelog.nodemap.get |
480 torev = repo.unfiltered().changelog.nodemap.get |
468 phase = repo._phasecache.phase |
481 phase = repo._phasecache.phase |
482 continue |
495 continue |
483 if set(succsmarkers(node) or []).issubset(addedmarkers): |
496 if set(succsmarkers(node) or []).issubset(addedmarkers): |
484 obsoleted.add(rev) |
497 obsoleted.add(rev) |
485 return obsoleted |
498 return obsoleted |
486 |
499 |
|
500 |
487 class _succs(list): |
501 class _succs(list): |
488 """small class to represent a successors with some metadata about it""" |
502 """small class to represent a successors with some metadata about it""" |
489 |
503 |
490 def __init__(self, *args, **kwargs): |
504 def __init__(self, *args, **kwargs): |
491 super(_succs, self).__init__(*args, **kwargs) |
505 super(_succs, self).__init__(*args, **kwargs) |
501 # immutable |
515 # immutable |
502 return set(self) |
516 return set(self) |
503 |
517 |
504 def canmerge(self, other): |
518 def canmerge(self, other): |
505 return self._set.issubset(other._set) |
519 return self._set.issubset(other._set) |
|
520 |
506 |
521 |
507 def successorssets(repo, initialnode, closest=False, cache=None): |
522 def successorssets(repo, initialnode, closest=False, cache=None): |
508 """Return set of all latest successors of initial nodes |
523 """Return set of all latest successors of initial nodes |
509 |
524 |
510 The successors set of a changeset A are the group of revisions that succeed |
525 The successors set of a changeset A are the group of revisions that succeed |
609 # |
624 # |
610 current = toproceed[-1] |
625 current = toproceed[-1] |
611 |
626 |
612 # case 2 condition is a bit hairy because of closest, |
627 # case 2 condition is a bit hairy because of closest, |
613 # we compute it on its own |
628 # we compute it on its own |
614 case2condition = ((current not in succmarkers) |
629 case2condition = (current not in succmarkers) or ( |
615 or (closest and current != initialnode |
630 closest and current != initialnode and current in repo |
616 and current in repo)) |
631 ) |
617 |
632 |
618 if current in cache: |
633 if current in cache: |
619 # case (1): We already know the successors sets |
634 # case (1): We already know the successors sets |
620 stackedset.remove(toproceed.pop()) |
635 stackedset.remove(toproceed.pop()) |
621 elif case2condition: |
636 elif case2condition: |
718 markss = productresult |
733 markss = productresult |
719 succssets.extend(markss) |
734 succssets.extend(markss) |
720 # remove duplicated and subset |
735 # remove duplicated and subset |
721 seen = [] |
736 seen = [] |
722 final = [] |
737 final = [] |
723 candidates = sorted((s for s in succssets if s), |
738 candidates = sorted( |
724 key=len, reverse=True) |
739 (s for s in succssets if s), key=len, reverse=True |
|
740 ) |
725 for cand in candidates: |
741 for cand in candidates: |
726 for seensuccs in seen: |
742 for seensuccs in seen: |
727 if cand.canmerge(seensuccs): |
743 if cand.canmerge(seensuccs): |
728 seensuccs.markers.update(cand.markers) |
744 seensuccs.markers.update(cand.markers) |
729 break |
745 break |
730 else: |
746 else: |
731 final.append(cand) |
747 final.append(cand) |
732 seen.append(cand) |
748 seen.append(cand) |
733 final.reverse() # put small successors set first |
749 final.reverse() # put small successors set first |
734 cache[current] = final |
750 cache[current] = final |
735 return cache[initialnode] |
751 return cache[initialnode] |
|
752 |
736 |
753 |
737 def successorsandmarkers(repo, ctx): |
754 def successorsandmarkers(repo, ctx): |
738 """compute the raw data needed for computing obsfate |
755 """compute the raw data needed for computing obsfate |
739 Returns a list of dict, one dict per successors set |
756 Returns a list of dict, one dict per successors set |
740 """ |
757 """ |
748 if ssets == []: |
765 if ssets == []: |
749 ssets = [[]] |
766 ssets = [[]] |
750 |
767 |
751 # Try to recover pruned markers |
768 # Try to recover pruned markers |
752 succsmap = repo.obsstore.successors |
769 succsmap = repo.obsstore.successors |
753 fullsuccessorsets = [] # successor set + markers |
770 fullsuccessorsets = [] # successor set + markers |
754 for sset in ssets: |
771 for sset in ssets: |
755 if sset: |
772 if sset: |
756 fullsuccessorsets.append(sset) |
773 fullsuccessorsets.append(sset) |
757 else: |
774 else: |
758 # successorsset return an empty set() when ctx or one of its |
775 # successorsset return an empty set() when ctx or one of its |
779 for sset in fullsuccessorsets: |
796 for sset in fullsuccessorsets: |
780 values.append({'successors': sset, 'markers': sset.markers}) |
797 values.append({'successors': sset, 'markers': sset.markers}) |
781 |
798 |
782 return values |
799 return values |
783 |
800 |
|
801 |
784 def _getobsfate(successorssets): |
802 def _getobsfate(successorssets): |
785 """ Compute a changeset obsolescence fate based on its successorssets. |
803 """ Compute a changeset obsolescence fate based on its successorssets. |
786 Successors can be the tipmost ones or the immediate ones. This function |
804 Successors can be the tipmost ones or the immediate ones. This function |
787 return values are not meant to be shown directly to users, it is meant to |
805 return values are not meant to be shown directly to users, it is meant to |
788 be used by internal functions only. |
806 be used by internal functions only. |
805 if len(successors) == 1: |
823 if len(successors) == 1: |
806 return 'superseded' |
824 return 'superseded' |
807 else: |
825 else: |
808 return 'superseded_split' |
826 return 'superseded_split' |
809 |
827 |
|
828 |
810 def obsfateverb(successorset, markers): |
829 def obsfateverb(successorset, markers): |
811 """ Return the verb summarizing the successorset and potentially using |
830 """ Return the verb summarizing the successorset and potentially using |
812 information from the markers |
831 information from the markers |
813 """ |
832 """ |
814 if not successorset: |
833 if not successorset: |
817 verb = 'rewritten' |
836 verb = 'rewritten' |
818 else: |
837 else: |
819 verb = 'split' |
838 verb = 'split' |
820 return verb |
839 return verb |
821 |
840 |
|
841 |
822 def markersdates(markers): |
842 def markersdates(markers): |
823 """returns the list of dates for a list of markers |
843 """returns the list of dates for a list of markers |
824 """ |
844 """ |
825 return [m[4] for m in markers] |
845 return [m[4] for m in markers] |
826 |
846 |
|
847 |
827 def markersusers(markers): |
848 def markersusers(markers): |
828 """ Returns a sorted list of markers users without duplicates |
849 """ Returns a sorted list of markers users without duplicates |
829 """ |
850 """ |
830 markersmeta = [dict(m[3]) for m in markers] |
851 markersmeta = [dict(m[3]) for m in markers] |
831 users = set(encoding.tolocal(meta['user']) for meta in markersmeta |
852 users = set( |
832 if meta.get('user')) |
853 encoding.tolocal(meta['user']) |
|
854 for meta in markersmeta |
|
855 if meta.get('user') |
|
856 ) |
833 |
857 |
834 return sorted(users) |
858 return sorted(users) |
|
859 |
835 |
860 |
836 def markersoperations(markers): |
861 def markersoperations(markers): |
837 """ Returns a sorted list of markers operations without duplicates |
862 """ Returns a sorted list of markers operations without duplicates |
838 """ |
863 """ |
839 markersmeta = [dict(m[3]) for m in markers] |
864 markersmeta = [dict(m[3]) for m in markers] |
840 operations = set(meta.get('operation') for meta in markersmeta |
865 operations = set( |
841 if meta.get('operation')) |
866 meta.get('operation') for meta in markersmeta if meta.get('operation') |
|
867 ) |
842 |
868 |
843 return sorted(operations) |
869 return sorted(operations) |
|
870 |
844 |
871 |
845 def obsfateprinter(ui, repo, successors, markers, formatctx): |
872 def obsfateprinter(ui, repo, successors, markers, formatctx): |
846 """ Build a obsfate string for a single successorset using all obsfate |
873 """ Build a obsfate string for a single successorset using all obsfate |
847 related function defined in obsutil |
874 related function defined in obsutil |
848 """ |
875 """ |
898 filteredmsgtable = { |
925 filteredmsgtable = { |
899 "pruned": _("hidden revision '%s' is pruned"), |
926 "pruned": _("hidden revision '%s' is pruned"), |
900 "diverged": _("hidden revision '%s' has diverged"), |
927 "diverged": _("hidden revision '%s' has diverged"), |
901 "superseded": _("hidden revision '%s' was rewritten as: %s"), |
928 "superseded": _("hidden revision '%s' was rewritten as: %s"), |
902 "superseded_split": _("hidden revision '%s' was split as: %s"), |
929 "superseded_split": _("hidden revision '%s' was split as: %s"), |
903 "superseded_split_several": _("hidden revision '%s' was split as: %s and " |
930 "superseded_split_several": _( |
904 "%d more"), |
931 "hidden revision '%s' was split as: %s and " "%d more" |
|
932 ), |
905 } |
933 } |
|
934 |
906 |
935 |
907 def _getfilteredreason(repo, changeid, ctx): |
936 def _getfilteredreason(repo, changeid, ctx): |
908 """return a human-friendly string on why a obsolete changeset is hidden |
937 """return a human-friendly string on why a obsolete changeset is hidden |
909 """ |
938 """ |
910 successors = successorssets(repo, ctx.node()) |
939 successors = successorssets(repo, ctx.node()) |
931 firstsuccessors = ', '.join(succs[:2]) |
960 firstsuccessors = ', '.join(succs[:2]) |
932 remainingnumber = len(succs) - 2 |
961 remainingnumber = len(succs) - 2 |
933 |
962 |
934 args = (changeid, firstsuccessors, remainingnumber) |
963 args = (changeid, firstsuccessors, remainingnumber) |
935 return filteredmsgtable['superseded_split_several'] % args |
964 return filteredmsgtable['superseded_split_several'] % args |
|
965 |
936 |
966 |
937 def divergentsets(repo, ctx): |
967 def divergentsets(repo, ctx): |
938 """Compute sets of commits divergent with a given one""" |
968 """Compute sets of commits divergent with a given one""" |
939 cache = {} |
969 cache = {} |
940 base = {} |
970 base = {} |
949 continue |
979 continue |
950 if tuple(nsuccset) in base: |
980 if tuple(nsuccset) in base: |
951 # we already know the latest base for this divergency |
981 # we already know the latest base for this divergency |
952 continue |
982 continue |
953 base[tuple(nsuccset)] = n |
983 base[tuple(nsuccset)] = n |
954 return [{'divergentnodes': divset, 'commonpredecessor': b} |
984 return [ |
955 for divset, b in base.iteritems()] |
985 {'divergentnodes': divset, 'commonpredecessor': b} |
|
986 for divset, b in base.iteritems() |
|
987 ] |
|
988 |
956 |
989 |
957 def whyunstable(repo, ctx): |
990 def whyunstable(repo, ctx): |
958 result = [] |
991 result = [] |
959 if ctx.orphan(): |
992 if ctx.orphan(): |
960 for parent in ctx.parents(): |
993 for parent in ctx.parents(): |
962 if parent.orphan(): |
995 if parent.orphan(): |
963 kind = 'orphan' |
996 kind = 'orphan' |
964 elif parent.obsolete(): |
997 elif parent.obsolete(): |
965 kind = 'obsolete' |
998 kind = 'obsolete' |
966 if kind is not None: |
999 if kind is not None: |
967 result.append({'instability': 'orphan', |
1000 result.append( |
968 'reason': '%s parent' % kind, |
1001 { |
969 'node': parent.hex()}) |
1002 'instability': 'orphan', |
|
1003 'reason': '%s parent' % kind, |
|
1004 'node': parent.hex(), |
|
1005 } |
|
1006 ) |
970 if ctx.phasedivergent(): |
1007 if ctx.phasedivergent(): |
971 predecessors = allpredecessors(repo.obsstore, [ctx.node()], |
1008 predecessors = allpredecessors( |
972 ignoreflags=bumpedfix) |
1009 repo.obsstore, [ctx.node()], ignoreflags=bumpedfix |
973 immutable = [repo[p] for p in predecessors |
1010 ) |
974 if p in repo and not repo[p].mutable()] |
1011 immutable = [ |
|
1012 repo[p] for p in predecessors if p in repo and not repo[p].mutable() |
|
1013 ] |
975 for predecessor in immutable: |
1014 for predecessor in immutable: |
976 result.append({'instability': 'phase-divergent', |
1015 result.append( |
977 'reason': 'immutable predecessor', |
1016 { |
978 'node': predecessor.hex()}) |
1017 'instability': 'phase-divergent', |
|
1018 'reason': 'immutable predecessor', |
|
1019 'node': predecessor.hex(), |
|
1020 } |
|
1021 ) |
979 if ctx.contentdivergent(): |
1022 if ctx.contentdivergent(): |
980 dsets = divergentsets(repo, ctx) |
1023 dsets = divergentsets(repo, ctx) |
981 for dset in dsets: |
1024 for dset in dsets: |
982 divnodes = [repo[n] for n in dset['divergentnodes']] |
1025 divnodes = [repo[n] for n in dset['divergentnodes']] |
983 result.append({'instability': 'content-divergent', |
1026 result.append( |
984 'divergentnodes': divnodes, |
1027 { |
985 'reason': 'predecessor', |
1028 'instability': 'content-divergent', |
986 'node': nodemod.hex(dset['commonpredecessor'])}) |
1029 'divergentnodes': divnodes, |
|
1030 'reason': 'predecessor', |
|
1031 'node': nodemod.hex(dset['commonpredecessor']), |
|
1032 } |
|
1033 ) |
987 return result |
1034 return result |