[PATCH] in/out --summary [was Re: Mozilla: hg as a collaboration tool]

Alexis S. L. Carvalho alexis at cecm.usp.br
Tue Feb 12 17:44:47 CST 2008


(Ccing the devel list.)

Thus spake Jesse Glick:
> Jason Orendorff wrote:
> > So the answer is: look at "hg log" and find your most recent merge 
> > from trunk. Then "hg diff -r <trunk-parent-of-that-merge>".
> 
> Which is silly. There ought to be a command (out --summary?) which folds 
> all non-merge outgoing changesets and displays the result as a patch.

Maybe something like this?  Very lightly tested, but I think the main
problem is that when it can't figure out what revisions to diff, the
error messages look too much like regular in/out output.  Suggestions
are welcome.

These error messages can be shown in two basic cases (but maybe I didn't
notice some stranger case that is not handled):

- there's more than one head being pushed.  E.g. you're comparing these
  two DAGs (time flows from the left to the right):

  o --- o                 o --- o --- o
                                  \
                                   -- o

- you've merged two heads of the original repo.  E.g.:
 
  o --- o                 o --- o --- o
    \                       \       /
     -- o                    -- o --

(well, they don't have to be heads - it's enough if one is not an
ancestor of the other)

The first case can be disambiguated using --rev.  For the second case
I added a --base option.

This still needs some test(s).

Alexis


diff -r ea34059b89de mercurial/cmdutil.py
--- a/mercurial/cmdutil.py	Tue Feb 12 11:35:06 2008 +0100
+++ b/mercurial/cmdutil.py	Tue Feb 12 21:42:07 2008 -0200
@@ -1157,3 +1157,104 @@ def commit(ui, repo, commitfunc, pats, o
         return commitfunc(ui, repo, files, message, match, opts)
     except ValueError, inst:
         raise util.Abort(str(inst))
+
+def summaryrevs(repo, roots, opts={}):
+    '''returns (base, head) nodes that can be compared to summarize changes
+
+    See the docstring for summaryrevpair for information about the root
+    argument.
+
+    opts['base'] can be used to override the calculation of the base node.
+
+    opts['rev] can be used to override the calculation of the head node.
+    Note that opts['rev'] is expected to be a list with a single element.
+
+    '''
+    head = opts.get('rev')
+    if len(head) > 1:
+        raise util.Abort(_('please specify a single revision with --rev'))
+    elif head:
+        head = repo.lookup(head[0])
+
+    base = opts.get('base')
+    if base:
+        base = repo.lookup(base)
+
+    return summaryrevpair(repo, roots, base, head)
+
+def summaryrevpair(repo, roots, base=None, head=None):
+    '''returns (base, head) nodes that can be compared to summarize changes
+
+    roots is usually the list of nodes returned by findincoming or
+    findoutgoing.
+
+    The caller can specify base and head nodes that should be used.
+    '''
+    def show(revs):
+        displayer = show_changeset(ui, repo, {})
+        revs.sort()
+        for r in revs:
+            displayer.show(rev=r)
+
+    ui = repo.ui
+    cl = repo.changelog
+
+    if not base:
+        # The repo we're comparing with doesn't know anything about the
+        # nodes in the roots list and their descendants, but it already
+        # has all their parents.
+        # So we first collect the parents of all the nodes in that list.
+        # If there's only one non-null parent, we use it as the base.
+        # If there's more than one, we try to find one that is a descendant
+        # of all the others and use it as the base.  If there's no such
+        # parent, abort.
+        parents = {}
+        for n in roots:
+            r = cl.rev(n)
+            for p in cl.parentrevs(r):
+                parents[p] = 1
+        parents.pop(nullrev)
+        if len(parents) > 1:
+            seen = {}
+            for r in xrange(max(parents), min(parents) - 1, -1):
+                if r in seen and r in parents:
+                    del parents[r]
+                for p in cl.parentrevs(r):
+                    seen[p] = 1
+            if len(parents) > 1:
+                if not ui.quiet:
+                    ui.write("there's more than one possible base revision.\n")
+                    ui.write("please choose one using --base.\n")
+                    ui.write('base revisions found:\n')
+                    show(parents.keys())
+                raise util.Abort(_('unable to find a unique base revision'))
+        if not parents:
+            # other repo is empty
+            parents[nullrev] = 1
+        base = cl.node(parents.keys()[0])
+
+    if not head:
+        # just find all heads that descend from the base revision
+        heads = cl.heads(base)
+        if len(heads) > 1:
+            if not ui.quiet:
+                ui.write("there's more than one possible head revision.\n")
+                ui.write("please choose one using --rev.\n")
+                ui.write('head revisions found:\n')
+                show([cl.rev(n) for n in heads])
+            raise util.Abort(_('unable to find a unique head revision'))
+        head = heads[0]
+    else:
+        # sanity check: if a head revision was specified, it must be a
+        # descendant of whatever base revision we have.
+        seen = {}
+        baserev = cl.rev(base)
+        for r in xrange(cl.rev(head), baserev, -1):
+            for p in cl.parentrevs(r):
+                seen[p] = 1
+        if baserev not in seen:
+            raise util.Abort(_('the specified head revision is not a '
+                               'descendant of the base revision'))
+
+    return base, head
+
diff -r ea34059b89de mercurial/commands.py
--- a/mercurial/commands.py	Tue Feb 12 11:35:06 2008 +0100
+++ b/mercurial/commands.py	Tue Feb 12 21:42:07 2008 -0200
@@ -1561,6 +1561,10 @@ def incoming(ui, repo, source="default",
                 # use the created uncompressed bundlerepo
                 other = bundlerepo.bundlerepository(ui, repo.root, fname)
 
+        if opts.get('summary'):
+            base, head = cmdutil.summaryrevs(other, incoming, opts)
+            patch.diff(other, base, head, opts=patch.diffopts(ui, opts))
+            return
         o = other.changelog.nodesbetween(incoming, revs)[0]
         if opts['newest_first']:
             o.reverse()
@@ -1852,6 +1856,10 @@ def outgoing(ui, repo, dest=None, **opts
     if not o:
         ui.status(_("no changes found\n"))
         return 1
+    if opts.get('summary'):
+        base, head = cmdutil.summaryrevs(repo, o, opts)
+        patch.diff(repo, base, head, opts=patch.diffopts(ui, opts))
+        return
     o = repo.changelog.nodesbetween(o, revs)[0]
     if opts['newest_first']:
         o.reverse()
@@ -2906,6 +2914,8 @@ table = {
           ('p', 'patch', None, _('show patch')),
           ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
           ('', 'template', '', _('display with template')),
+          ('s', 'summary', None, _('display only a single diff containing all changes')),
+          ('', 'base', '', _('force a base revision for --summary')),
          ] + remoteopts,
          _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
            ' [--bundle FILENAME] [SOURCE]')),
@@ -2964,6 +2974,8 @@ table = {
           ('r', 'rev', [], _('a specific revision you would like to push')),
           ('n', 'newest-first', None, _('show newest record first')),
           ('', 'template', '', _('display with template')),
+          ('s', 'summary', None, _('display only a single diff containing all changes')),
+          ('', 'base', '', _('force a base revision for --summary')),
          ] + remoteopts,
          _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
     "^parents":


More information about the Mercurial-devel mailing list