# HG changeset patch # User Boris Feld # Date 1526983644 -7200 # Node ID 16bec7609a082081028d8c648e581fc8db2235b9 # Parent ed460e7ee8aaf1bd2fd638b1413b9bbec25e012d obslog: add a new flag to filter out non-local nodes diff -r ed460e7ee8aa -r 16bec7609a08 hgext3rd/evolve/obshistory.py --- 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] diff -r ed460e7ee8aa -r 16bec7609a08 tests/test-evolve-obshistory.t --- 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] |