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