obslog: add a new flag to filter out non-local nodes
authorBoris Feld <boris.feld@octobus.net>
Tue, 22 May 2018 12:07:24 +0200
changeset 3884 16bec7609a08
parent 3883 ed460e7ee8aa
child 3885 3df8b6ecce2a
obslog: add a new flag to filter out non-local nodes
hgext3rd/evolve/obshistory.py
tests/test-evolve-obshistory.t
--- a/hgext3rd/evolve/obshistory.py	Thu Jul 26 18:28:44 2018 +0200
+++ b/hgext3rd/evolve/obshistory.py	Tue May 22 12:07:24 2018 +0200
@@ -47,7 +47,8 @@
     [('G', 'graph', True, _("show the revision DAG")),
      ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
      ('a', 'all', False, _('show all related changesets, not only precursors')),
-     ('p', 'patch', False, _('show the patch between two obs versions'))
+     ('p', 'patch', False, _('show the patch between two obs versions')),
+     ('f', 'filternonlocal', False, _('filter out non local commits')),
     ] + commands.formatteropts,
     _('hg olog [OPTION]... [REV]'))
 def cmdobshistory(ui, repo, *revs, **opts):
@@ -90,6 +91,50 @@
     revs.reverse()
     _debugobshistoryrevs(ui, repo, revs, opts)
 
+def _successorsandmarkers(repo, ctx):
+    """compute the raw data needed for computing obsfate
+    Returns a list of dict, one dict per successors set
+    """
+    ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
+
+    # closestsuccessors returns an empty list for pruned revisions, remap it
+    # into a list containing an empty list for future processing
+    if ssets == []:
+        ssets = [[]]
+
+    # Try to recover pruned markers
+    succsmap = repo.obsstore.successors
+    fullsuccessorsets = [] # successor set + markers
+    for sset in ssets:
+        if sset:
+            fullsuccessorsets.append(sset)
+        else:
+            # successorsset return an empty set() when ctx or one of its
+            # successors is pruned.
+            # In this case, walk the obs-markers tree again starting with ctx
+            # and find the relevant pruning obs-makers, the ones without
+            # successors.
+            # Having these markers allow us to compute some information about
+            # its fate, like who pruned this changeset and when.
+
+            # XXX we do not catch all prune markers (eg rewritten then pruned)
+            # (fix me later)
+            foundany = False
+            for mark in succsmap.get(ctx.node(), ()):
+                if not mark[1]:
+                    foundany = True
+                    sset = obsutil._succs()
+                    sset.markers.add(mark)
+                    fullsuccessorsets.append(sset)
+            if not foundany:
+                fullsuccessorsets.append(obsutil._succs())
+
+    values = []
+    for sset in fullsuccessorsets:
+        values.append({'successors': sset, 'markers': sset.markers})
+
+    return values
+
 class obsmarker_printer(compat.changesetprinter):
     """show (available) information about a node
 
@@ -104,6 +149,7 @@
             self._includediff = diffopts and diffopts.get('patch')
 
         self.template = diffopts and diffopts.get('template')
+        self.filter = diffopts and diffopts.get('filternonlocal')
 
     def show(self, ctx, copies=None, matchfn=None, **props):
         if self.buffered:
@@ -116,20 +162,33 @@
 
             _debugobshistorydisplaynode(fm, self.repo, changenode)
 
-            # Succs markers
-            succs = self.repo.obsstore.successors.get(changenode, ())
-            succs = sorted(succs)
-
             markerfm = fm.nested("markers")
 
-            for successor in succs:
-                _debugobshistorydisplaymarker(markerfm, successor,
-                                              ctx.node(), self.repo,
-                                              self._includediff)
+            # Succs markers
+            if self.filter is False:
+                succs = self.repo.obsstore.successors.get(changenode, ())
+                succs = sorted(succs)
+
+                for successor in succs:
+                    _debugobshistorydisplaymarker(markerfm, successor,
+                                                  ctx.node(), self.repo,
+                                                  self._includediff)
+
+            else:
+                r = _successorsandmarkers(self.repo, ctx)
+
+                for succset in sorted(r):
+                    markers = succset["markers"]
+                    if not markers:
+                        continue
+                    successors = succset["successors"]
+                    _debugobshistorydisplaysuccsandmarkers(markerfm, successors, markers, ctx.node(), self.repo, self._includediff)
+
             markerfm.end()
 
             markerfm.plain('\n')
             fm.end()
+
             self.hunk[ctx.node()] = self.ui.popbuffer()
         else:
             ### graph output is buffered only
@@ -142,12 +201,10 @@
         '''
         pass
 
-def patchavailable(node, repo, marker):
+def patchavailable(node, repo, successors):
     if node not in repo:
         return False, "context is not local"
 
-    successors = marker[1]
-
     if len(successors) == 0:
         return False, "no successors"
     elif len(successors) > 1:
@@ -235,7 +292,7 @@
             stack.pop()
     return False
 
-def _obshistorywalker(repo, revs, walksuccessors=False):
+def _obshistorywalker(repo, revs, walksuccessors=False, filternonlocal=False):
     """ Directly inspired by graphmod.dagwalker,
     walk the obs marker tree and yield
     (id, CHANGESET, ctx, [parentinfo]) tuples
@@ -288,9 +345,18 @@
             if cand in repo:
                 changectx = repo[cand]
             else:
-                changectx = missingchangectx(repo, cand)
+                if filternonlocal is False:
+                    changectx = missingchangectx(repo, cand)
+                else:
+                    continue
 
-            childrens = [(graphmod.PARENT, x) for x in nodeprec.get(cand, ())]
+            if filternonlocal is False:
+                relations = nodeprec.get(cand, ())
+            else:
+                relations = obsutil.closestpredecessors(repo, cand)
+            # print("RELATIONS", relations, list(closestpred))
+            childrens = [(graphmod.PARENT, x) for x in relations]
+            # print("YIELD", changectx, childrens)
             yield (cand, graphmod.CHANGESET, changectx, childrens)
 
 def _obshistorywalker_links(repo, revs, walksuccessors=False):
@@ -355,7 +421,7 @@
 
     displayer = obsmarker_printer(ui, repo.unfiltered(), matchfn, opts, buffered=True)
     edges = graphmod.asciiedges
-    walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False))
+    walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False), opts.get('filternonlocal', False))
     compat.displaygraph(ui, repo, walker, displayer, edges)
 
 def _debugobshistoryrevs(ui, repo, revs, opts):
@@ -497,7 +563,144 @@
 
     # Patch display
     if includediff is True:
-        _patchavailable = patchavailable(node, repo, marker)
+        _patchavailable = patchavailable(node, repo, marker[1])
+
+        if _patchavailable[0] is True:
+            succ = _patchavailable[1]
+
+            basectx = repo[node]
+            succctx = repo[succ]
+            # Description patch
+            descriptionpatch = getmarkerdescriptionpatch(repo,
+                                                         basectx.description(),
+                                                         succctx.description())
+
+            if descriptionpatch:
+                # add the diffheader
+                diffheader = "diff -r %s -r %s changeset-description\n" % \
+                             (basectx, succctx)
+                descriptionpatch = diffheader + descriptionpatch
+
+                def tolist(text):
+                    return [text]
+
+                fm.plain("\n")
+
+                for chunk, label in patch.difflabel(tolist, descriptionpatch):
+                    chunk = chunk.strip('\t')
+                    if chunk and chunk != '\n':
+                        fm.plain('    ')
+                    fm.write('desc-diff', '%s', chunk, label=label)
+
+            # Content patch
+            diffopts = patch.diffallopts(repo.ui, {})
+            matchfn = scmutil.matchall(repo)
+            firstline = True
+            for chunk, label in patch.diffui(repo, node, succ, matchfn,
+                                             changes=None, opts=diffopts,
+                                             prefix='', relroot=''):
+                if firstline:
+                    fm.plain('\n')
+                    firstline = False
+                if chunk and chunk != '\n':
+                    fm.plain('    ')
+                fm.write('patch', '%s', chunk, label=label)
+        else:
+            nopatch = "    (No patch available, %s)" % _patchavailable[1]
+            fm.plain("\n")
+            # TODO: should be in json too
+            fm.plain(nopatch)
+
+    fm.plain("\n")
+
+def _debugobshistorydisplaysuccsandmarkers(fm, succnodes, markers, node, repo, includediff=False):
+    """
+    This function is a duplication of _debugobshistorydisplaymarker modified
+    to accept multiple markers as input.
+    """
+    fm.startitem()
+    fm.plain('  ')
+
+    # Detect pruned revisions
+    verb = _successorsetverb(succnodes, markers)["verb"]
+
+    fm.write('verb', '%s', verb,
+             label="evolve.verb")
+
+    # Effect flag
+    metadata = [dict(marker[3]) for marker in markers]
+    ef1 = [data.get('ef1') for data in metadata]
+
+    effectflag = 0
+    for ef in ef1:
+        if ef:
+            effectflag |= int(ef)
+
+    if effectflag:
+        effect = []
+
+        # XXX should be a dict
+        if effectflag & DESCCHANGED:
+            effect.append('description')
+        if effectflag & METACHANGED:
+            effect.append('meta')
+        if effectflag & USERCHANGED:
+            effect.append('user')
+        if effectflag & DATECHANGED:
+            effect.append('date')
+        if effectflag & BRANCHCHANGED:
+            effect.append('branch')
+        if effectflag & PARENTCHANGED:
+            effect.append('parent')
+        if effectflag & DIFFCHANGED:
+            effect.append('content')
+
+        if effect:
+            fmteffect = fm.formatlist(effect, 'effect', sep=', ')
+            fm.write('effect', '(%s)', fmteffect)
+
+    if len(succnodes) > 0:
+        fm.plain(' as ')
+
+        shortsnodes = (nodemod.short(succnode) for succnode in sorted(succnodes))
+        nodes = fm.formatlist(shortsnodes, 'succnodes', sep=', ')
+        fm.write('succnodes', '%s', nodes,
+                 label="evolve.node")
+
+    # Operations
+    operations = obsutil.markersoperations(markers)
+    if operations:
+        fm.plain(' using ')
+        fm.write('operation', '%s', ", ".join(operations), label="evolve.operation")
+
+    fm.plain(' by ')
+
+    # Users
+    users = obsutil.markersusers(markers)
+    fm.write('user', '%s', ", ".join(users),
+             label="evolve.user")
+    fm.plain(' ')
+
+    # Dates
+    dates = obsutil.markersdates(markers)
+    if dates:
+        min_date = min(dates)
+        max_date = max(dates)
+
+        if min_date == max_date:
+            fm.write("date", "(at %s)", fm.formatdate(min_date), label="evolve.date")
+        else:
+            fm.write("date", "(between %s and %s)", fm.formatdate(min_date),
+                     fm.formatdate(max_date), label="evolve.date")
+
+    # initial support for showing note
+    # if metadata.get('note'):
+    #     fm.plain('\n    note: ')
+    #     fm.write('note', "%s", metadata['note'], label="evolve.note")
+
+    # Patch display
+    if includediff is True:
+        _patchavailable = patchavailable(node, repo, succnodes)
 
         if _patchavailable[0] is True:
             succ = _patchavailable[1]
--- a/tests/test-evolve-obshistory.t	Thu Jul 26 18:28:44 2018 +0200
+++ b/tests/test-evolve-obshistory.t	Tue May 22 12:07:24 2018 +0200
@@ -1744,6 +1744,20 @@
        rewritten(description) as fdf9bde5129a using amend by test (Thu Jan 01 00:00:00 1970 +0000)
          (No patch available, successor is unknown locally)
   
+
+  $ hg obslog 7a230b46bf61 --patch -f
+  o  7a230b46bf61 (2) A2
+  |
+  @  471f378eab4c (1) A0
+       reworded(description) as 7a230b46bf61 using amend by test (at Thu Jan 01 00:00:00 1970 +0000)
+         diff -r 471f378eab4c -r 7a230b46bf61 changeset-description
+         --- a/changeset-description
+         +++ b/changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
+  
   $ hg obslog 7a230b46bf61 --color=debug --patch
   o  [evolve.node|7a230b46bf61] [evolve.rev|(2)] [evolve.short_description|A2]
   |