Mercurial > evolve
comparison hgext3rd/evolve/obshistory.py @ 3884:16bec7609a08
obslog: add a new flag to filter out non-local nodes
author | Boris Feld <boris.feld@octobus.net> |
---|---|
date | Tue, 22 May 2018 12:07:24 +0200 |
parents | ed460e7ee8aa |
children | 28824ad64a12 |
comparison
equal
deleted
inserted
replaced
3883:ed460e7ee8aa | 3884:16bec7609a08 |
---|---|
45 @eh.command( | 45 @eh.command( |
46 'obslog|olog', | 46 'obslog|olog', |
47 [('G', 'graph', True, _("show the revision DAG")), | 47 [('G', 'graph', True, _("show the revision DAG")), |
48 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')), | 48 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')), |
49 ('a', 'all', False, _('show all related changesets, not only precursors')), | 49 ('a', 'all', False, _('show all related changesets, not only precursors')), |
50 ('p', 'patch', False, _('show the patch between two obs versions')) | 50 ('p', 'patch', False, _('show the patch between two obs versions')), |
51 ('f', 'filternonlocal', False, _('filter out non local commits')), | |
51 ] + commands.formatteropts, | 52 ] + commands.formatteropts, |
52 _('hg olog [OPTION]... [REV]')) | 53 _('hg olog [OPTION]... [REV]')) |
53 def cmdobshistory(ui, repo, *revs, **opts): | 54 def cmdobshistory(ui, repo, *revs, **opts): |
54 """show the obsolescence history of the specified revisions | 55 """show the obsolescence history of the specified revisions |
55 | 56 |
88 return _debugobshistorygraph(ui, repo, revs, opts) | 89 return _debugobshistorygraph(ui, repo, revs, opts) |
89 | 90 |
90 revs.reverse() | 91 revs.reverse() |
91 _debugobshistoryrevs(ui, repo, revs, opts) | 92 _debugobshistoryrevs(ui, repo, revs, opts) |
92 | 93 |
94 def _successorsandmarkers(repo, ctx): | |
95 """compute the raw data needed for computing obsfate | |
96 Returns a list of dict, one dict per successors set | |
97 """ | |
98 ssets = obsutil.successorssets(repo, ctx.node(), closest=True) | |
99 | |
100 # closestsuccessors returns an empty list for pruned revisions, remap it | |
101 # into a list containing an empty list for future processing | |
102 if ssets == []: | |
103 ssets = [[]] | |
104 | |
105 # Try to recover pruned markers | |
106 succsmap = repo.obsstore.successors | |
107 fullsuccessorsets = [] # successor set + markers | |
108 for sset in ssets: | |
109 if sset: | |
110 fullsuccessorsets.append(sset) | |
111 else: | |
112 # successorsset return an empty set() when ctx or one of its | |
113 # successors is pruned. | |
114 # In this case, walk the obs-markers tree again starting with ctx | |
115 # and find the relevant pruning obs-makers, the ones without | |
116 # successors. | |
117 # Having these markers allow us to compute some information about | |
118 # its fate, like who pruned this changeset and when. | |
119 | |
120 # XXX we do not catch all prune markers (eg rewritten then pruned) | |
121 # (fix me later) | |
122 foundany = False | |
123 for mark in succsmap.get(ctx.node(), ()): | |
124 if not mark[1]: | |
125 foundany = True | |
126 sset = obsutil._succs() | |
127 sset.markers.add(mark) | |
128 fullsuccessorsets.append(sset) | |
129 if not foundany: | |
130 fullsuccessorsets.append(obsutil._succs()) | |
131 | |
132 values = [] | |
133 for sset in fullsuccessorsets: | |
134 values.append({'successors': sset, 'markers': sset.markers}) | |
135 | |
136 return values | |
137 | |
93 class obsmarker_printer(compat.changesetprinter): | 138 class obsmarker_printer(compat.changesetprinter): |
94 """show (available) information about a node | 139 """show (available) information about a node |
95 | 140 |
96 We display the node, description (if available) and various information | 141 We display the node, description (if available) and various information |
97 about obsolescence markers affecting it""" | 142 about obsolescence markers affecting it""" |
102 # Compat 4.6 | 147 # Compat 4.6 |
103 if not util.safehasattr(self, "_includediff"): | 148 if not util.safehasattr(self, "_includediff"): |
104 self._includediff = diffopts and diffopts.get('patch') | 149 self._includediff = diffopts and diffopts.get('patch') |
105 | 150 |
106 self.template = diffopts and diffopts.get('template') | 151 self.template = diffopts and diffopts.get('template') |
152 self.filter = diffopts and diffopts.get('filternonlocal') | |
107 | 153 |
108 def show(self, ctx, copies=None, matchfn=None, **props): | 154 def show(self, ctx, copies=None, matchfn=None, **props): |
109 if self.buffered: | 155 if self.buffered: |
110 self.ui.pushbuffer(labeled=True) | 156 self.ui.pushbuffer(labeled=True) |
111 | 157 |
114 _props = {"template": self.template} | 160 _props = {"template": self.template} |
115 fm = self.ui.formatter('debugobshistory', _props) | 161 fm = self.ui.formatter('debugobshistory', _props) |
116 | 162 |
117 _debugobshistorydisplaynode(fm, self.repo, changenode) | 163 _debugobshistorydisplaynode(fm, self.repo, changenode) |
118 | 164 |
165 markerfm = fm.nested("markers") | |
166 | |
119 # Succs markers | 167 # Succs markers |
120 succs = self.repo.obsstore.successors.get(changenode, ()) | 168 if self.filter is False: |
121 succs = sorted(succs) | 169 succs = self.repo.obsstore.successors.get(changenode, ()) |
122 | 170 succs = sorted(succs) |
123 markerfm = fm.nested("markers") | 171 |
124 | 172 for successor in succs: |
125 for successor in succs: | 173 _debugobshistorydisplaymarker(markerfm, successor, |
126 _debugobshistorydisplaymarker(markerfm, successor, | 174 ctx.node(), self.repo, |
127 ctx.node(), self.repo, | 175 self._includediff) |
128 self._includediff) | 176 |
177 else: | |
178 r = _successorsandmarkers(self.repo, ctx) | |
179 | |
180 for succset in sorted(r): | |
181 markers = succset["markers"] | |
182 if not markers: | |
183 continue | |
184 successors = succset["successors"] | |
185 _debugobshistorydisplaysuccsandmarkers(markerfm, successors, markers, ctx.node(), self.repo, self._includediff) | |
186 | |
129 markerfm.end() | 187 markerfm.end() |
130 | 188 |
131 markerfm.plain('\n') | 189 markerfm.plain('\n') |
132 fm.end() | 190 fm.end() |
191 | |
133 self.hunk[ctx.node()] = self.ui.popbuffer() | 192 self.hunk[ctx.node()] = self.ui.popbuffer() |
134 else: | 193 else: |
135 ### graph output is buffered only | 194 ### graph output is buffered only |
136 msg = 'cannot be used outside of the graphlog (yet)' | 195 msg = 'cannot be used outside of the graphlog (yet)' |
137 raise error.ProgrammingError(msg) | 196 raise error.ProgrammingError(msg) |
140 ''' changeset_printer has some logic around buffering data | 199 ''' changeset_printer has some logic around buffering data |
141 in self.headers that we don't use | 200 in self.headers that we don't use |
142 ''' | 201 ''' |
143 pass | 202 pass |
144 | 203 |
145 def patchavailable(node, repo, marker): | 204 def patchavailable(node, repo, successors): |
146 if node not in repo: | 205 if node not in repo: |
147 return False, "context is not local" | 206 return False, "context is not local" |
148 | |
149 successors = marker[1] | |
150 | 207 |
151 if len(successors) == 0: | 208 if len(successors) == 0: |
152 return False, "no successors" | 209 return False, "no successors" |
153 elif len(successors) > 1: | 210 elif len(successors) > 1: |
154 return False, "too many successors (%d)" % len(successors) | 211 return False, "too many successors (%d)" % len(successors) |
233 else: | 290 else: |
234 path_set.remove(path.pop()) | 291 path_set.remove(path.pop()) |
235 stack.pop() | 292 stack.pop() |
236 return False | 293 return False |
237 | 294 |
238 def _obshistorywalker(repo, revs, walksuccessors=False): | 295 def _obshistorywalker(repo, revs, walksuccessors=False, filternonlocal=False): |
239 """ Directly inspired by graphmod.dagwalker, | 296 """ Directly inspired by graphmod.dagwalker, |
240 walk the obs marker tree and yield | 297 walk the obs marker tree and yield |
241 (id, CHANGESET, ctx, [parentinfo]) tuples | 298 (id, CHANGESET, ctx, [parentinfo]) tuples |
242 """ | 299 """ |
243 | 300 |
286 | 343 |
287 # Add the right changectx class | 344 # Add the right changectx class |
288 if cand in repo: | 345 if cand in repo: |
289 changectx = repo[cand] | 346 changectx = repo[cand] |
290 else: | 347 else: |
291 changectx = missingchangectx(repo, cand) | 348 if filternonlocal is False: |
292 | 349 changectx = missingchangectx(repo, cand) |
293 childrens = [(graphmod.PARENT, x) for x in nodeprec.get(cand, ())] | 350 else: |
351 continue | |
352 | |
353 if filternonlocal is False: | |
354 relations = nodeprec.get(cand, ()) | |
355 else: | |
356 relations = obsutil.closestpredecessors(repo, cand) | |
357 # print("RELATIONS", relations, list(closestpred)) | |
358 childrens = [(graphmod.PARENT, x) for x in relations] | |
359 # print("YIELD", changectx, childrens) | |
294 yield (cand, graphmod.CHANGESET, changectx, childrens) | 360 yield (cand, graphmod.CHANGESET, changectx, childrens) |
295 | 361 |
296 def _obshistorywalker_links(repo, revs, walksuccessors=False): | 362 def _obshistorywalker_links(repo, revs, walksuccessors=False): |
297 """ Iterate the obs history tree starting from revs, traversing | 363 """ Iterate the obs history tree starting from revs, traversing |
298 each revision precursors recursively. | 364 each revision precursors recursively. |
353 if opts.get('patch'): | 419 if opts.get('patch'): |
354 matchfn = scmutil.matchall(repo) | 420 matchfn = scmutil.matchall(repo) |
355 | 421 |
356 displayer = obsmarker_printer(ui, repo.unfiltered(), matchfn, opts, buffered=True) | 422 displayer = obsmarker_printer(ui, repo.unfiltered(), matchfn, opts, buffered=True) |
357 edges = graphmod.asciiedges | 423 edges = graphmod.asciiedges |
358 walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False)) | 424 walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False), opts.get('filternonlocal', False)) |
359 compat.displaygraph(ui, repo, walker, displayer, edges) | 425 compat.displaygraph(ui, repo, walker, displayer, edges) |
360 | 426 |
361 def _debugobshistoryrevs(ui, repo, revs, opts): | 427 def _debugobshistoryrevs(ui, repo, revs, opts): |
362 """ Display the obsolescence history for revset | 428 """ Display the obsolescence history for revset |
363 """ | 429 """ |
495 fm.plain('\n note: ') | 561 fm.plain('\n note: ') |
496 fm.write('note', "%s", metadata['note'], label="evolve.note") | 562 fm.write('note', "%s", metadata['note'], label="evolve.note") |
497 | 563 |
498 # Patch display | 564 # Patch display |
499 if includediff is True: | 565 if includediff is True: |
500 _patchavailable = patchavailable(node, repo, marker) | 566 _patchavailable = patchavailable(node, repo, marker[1]) |
501 | 567 |
502 if _patchavailable[0] is True: | 568 if _patchavailable[0] is True: |
503 succ = _patchavailable[1] | 569 succ = _patchavailable[1] |
504 | 570 |
505 basectx = repo[node] | 571 basectx = repo[node] |
545 # TODO: should be in json too | 611 # TODO: should be in json too |
546 fm.plain(nopatch) | 612 fm.plain(nopatch) |
547 | 613 |
548 fm.plain("\n") | 614 fm.plain("\n") |
549 | 615 |
616 def _debugobshistorydisplaysuccsandmarkers(fm, succnodes, markers, node, repo, includediff=False): | |
617 """ | |
618 This function is a duplication of _debugobshistorydisplaymarker modified | |
619 to accept multiple markers as input. | |
620 """ | |
621 fm.startitem() | |
622 fm.plain(' ') | |
623 | |
624 # Detect pruned revisions | |
625 verb = _successorsetverb(succnodes, markers)["verb"] | |
626 | |
627 fm.write('verb', '%s', verb, | |
628 label="evolve.verb") | |
629 | |
630 # Effect flag | |
631 metadata = [dict(marker[3]) for marker in markers] | |
632 ef1 = [data.get('ef1') for data in metadata] | |
633 | |
634 effectflag = 0 | |
635 for ef in ef1: | |
636 if ef: | |
637 effectflag |= int(ef) | |
638 | |
639 if effectflag: | |
640 effect = [] | |
641 | |
642 # XXX should be a dict | |
643 if effectflag & DESCCHANGED: | |
644 effect.append('description') | |
645 if effectflag & METACHANGED: | |
646 effect.append('meta') | |
647 if effectflag & USERCHANGED: | |
648 effect.append('user') | |
649 if effectflag & DATECHANGED: | |
650 effect.append('date') | |
651 if effectflag & BRANCHCHANGED: | |
652 effect.append('branch') | |
653 if effectflag & PARENTCHANGED: | |
654 effect.append('parent') | |
655 if effectflag & DIFFCHANGED: | |
656 effect.append('content') | |
657 | |
658 if effect: | |
659 fmteffect = fm.formatlist(effect, 'effect', sep=', ') | |
660 fm.write('effect', '(%s)', fmteffect) | |
661 | |
662 if len(succnodes) > 0: | |
663 fm.plain(' as ') | |
664 | |
665 shortsnodes = (nodemod.short(succnode) for succnode in sorted(succnodes)) | |
666 nodes = fm.formatlist(shortsnodes, 'succnodes', sep=', ') | |
667 fm.write('succnodes', '%s', nodes, | |
668 label="evolve.node") | |
669 | |
670 # Operations | |
671 operations = obsutil.markersoperations(markers) | |
672 if operations: | |
673 fm.plain(' using ') | |
674 fm.write('operation', '%s', ", ".join(operations), label="evolve.operation") | |
675 | |
676 fm.plain(' by ') | |
677 | |
678 # Users | |
679 users = obsutil.markersusers(markers) | |
680 fm.write('user', '%s', ", ".join(users), | |
681 label="evolve.user") | |
682 fm.plain(' ') | |
683 | |
684 # Dates | |
685 dates = obsutil.markersdates(markers) | |
686 if dates: | |
687 min_date = min(dates) | |
688 max_date = max(dates) | |
689 | |
690 if min_date == max_date: | |
691 fm.write("date", "(at %s)", fm.formatdate(min_date), label="evolve.date") | |
692 else: | |
693 fm.write("date", "(between %s and %s)", fm.formatdate(min_date), | |
694 fm.formatdate(max_date), label="evolve.date") | |
695 | |
696 # initial support for showing note | |
697 # if metadata.get('note'): | |
698 # fm.plain('\n note: ') | |
699 # fm.write('note', "%s", metadata['note'], label="evolve.note") | |
700 | |
701 # Patch display | |
702 if includediff is True: | |
703 _patchavailable = patchavailable(node, repo, succnodes) | |
704 | |
705 if _patchavailable[0] is True: | |
706 succ = _patchavailable[1] | |
707 | |
708 basectx = repo[node] | |
709 succctx = repo[succ] | |
710 # Description patch | |
711 descriptionpatch = getmarkerdescriptionpatch(repo, | |
712 basectx.description(), | |
713 succctx.description()) | |
714 | |
715 if descriptionpatch: | |
716 # add the diffheader | |
717 diffheader = "diff -r %s -r %s changeset-description\n" % \ | |
718 (basectx, succctx) | |
719 descriptionpatch = diffheader + descriptionpatch | |
720 | |
721 def tolist(text): | |
722 return [text] | |
723 | |
724 fm.plain("\n") | |
725 | |
726 for chunk, label in patch.difflabel(tolist, descriptionpatch): | |
727 chunk = chunk.strip('\t') | |
728 if chunk and chunk != '\n': | |
729 fm.plain(' ') | |
730 fm.write('desc-diff', '%s', chunk, label=label) | |
731 | |
732 # Content patch | |
733 diffopts = patch.diffallopts(repo.ui, {}) | |
734 matchfn = scmutil.matchall(repo) | |
735 firstline = True | |
736 for chunk, label in patch.diffui(repo, node, succ, matchfn, | |
737 changes=None, opts=diffopts, | |
738 prefix='', relroot=''): | |
739 if firstline: | |
740 fm.plain('\n') | |
741 firstline = False | |
742 if chunk and chunk != '\n': | |
743 fm.plain(' ') | |
744 fm.write('patch', '%s', chunk, label=label) | |
745 else: | |
746 nopatch = " (No patch available, %s)" % _patchavailable[1] | |
747 fm.plain("\n") | |
748 # TODO: should be in json too | |
749 fm.plain(nopatch) | |
750 | |
751 fm.plain("\n") | |
752 | |
550 # logic around storing and using effect flags | 753 # logic around storing and using effect flags |
551 DESCCHANGED = 1 << 0 # action changed the description | 754 DESCCHANGED = 1 << 0 # action changed the description |
552 METACHANGED = 1 << 1 # action change the meta | 755 METACHANGED = 1 << 1 # action change the meta |
553 PARENTCHANGED = 1 << 2 # action change the parent | 756 PARENTCHANGED = 1 << 2 # action change the parent |
554 DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset | 757 DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset |