--- 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]