[PATCH RFC] underway: extension/command for displaying in progress work
Jun Wu
quark at fb.com
Wed Aug 24 10:24:42 EDT 2016
For smartlog, I think the direction is to make it just:
hg log -G -r 'smartlog()'
i.e. no need for a separate "smartlog" command. If the issue is only about
choosing changesets for the graph, is it already solved using the revset
layer? Is a new revset function more flexible?
cc Martijn who may have interest in this topic.
Excerpts from Gregory Szorc's message of 2016-08-21 15:08:24 -0700:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc at gmail.com>
> # Date 1471816945 25200
> # Sun Aug 21 15:02:25 2016 -0700
> # Node ID a9363f9f7e2f2f36e56a8902291a7a3bf1bb2350
> # Parent 997e8cf4d0a29d28759e38659736cb3d1cf9ef3f
> underway: extension/command for displaying in progress work
>
> It is common for developers to want to see a snapshot of "in progress"
> work in their repository. Commands like `hg bookmarks`, `hg heads`,
> `hg branches`, and even `hg qseries` do an OK job of answering this
> question. But the output from these commands is overly simple: they
> typically only show lists of changesets with no context to note their
> position in the DAG (which is often necessary for performing operations
> like `hg rebase`), the number of unfinished changesets, etc.
>
> `hg wip` (from
> http://jordi.inversethought.com/blog/customising-mercurial-like-a-pro/ )
> and `hg smartlog` (from
> https://bitbucket.org/facebook/hg-experimental/ ) have both attempted to
> solve the problem of "show me a DAG view of in progress work." And,
> my experience supporting Mercurial users at Mozilla (where we encourage
> the use of `hg wip`) tells me that developers *really* like this
> command and functionality. If multiple entities have implemented nearly
> the same thing, that's a sign there is a need for a feature in core
> Mercurial. FWIW, I recall mpm giving verbal approval for adding such
> a feature during a previous sprint.
>
> This commit introduces the "underway" extension and command. The command
> is effectively a glorified wrapper around `hg log -G` with a
> semi-complicated revset query that shows underway/unfinished/in-progress
> changesets and other "important" changesets (namely DAG heads and the
> working directory).
>
> I looked at the synonyms listed at
> http://www.thesaurus.com/browse/in%20progress?s=t and felt "underway"
> was the most appopriate. Here are reasons I ruled out alternatives:
>
> * "wip" is not an intuitive name and therefore has discovery problems,
> especially for non-English speakers.
> * "smartlog" is also not intuitive because "smart" isn't descriptive.
> Also, "what makes it 'smart'?' Why can't `hg log` be "smart" by
> default?
> * "inprogress" was tempting, but I was worried about the prefix naming
> collision with "incoming."
> * "unfinished" was also tempting but I don't like introducing an "un"
> prefixed command without the corresponding command lacking the
> prefix (the presence of the prefix implies that the opposite
> operation/command is possible).
>
> TODO
>
> * More concise template output. IMO one of the big advantages of `hg wip` is
> its concise template that allows you to see a lot about the DAG shape
> without having to excessively scroll. I would like for the command
> to use a new, less verbose, template by default.
> * Rename and/or document the options to control revset behavior.
> * Should we add an "--underway" flag to `hg log` and let users install
> their own command aliases? (IMO `hg log` already has an argument
> count/complexity problem.)
>
> diff --git a/hgext/underway.py b/hgext/underway.py
> new file mode 100644
> --- /dev/null
> +++ b/hgext/underway.py
> @@ -0,0 +1,108 @@
> +# underway.py - Show commits that are underway/in-progress
> +#
> +# Copyright 2016 Gregory Szorc <gregory.szorc at gmail.com>
> +#
> +# This software may be used and distributed according to the terms of the
> +# GNU General Public License version 2 or any later version.
> +
> +from __future__ import absolute_import
> +
> +from mercurial.node import nullrev
> +from mercurial import (
> + cmdutil,
> + commands,
> + registrar,
> + revset,
> +)
> +from hgext import (
> + pager,
> +)
> +
> +# Note for extension authors: ONLY specify testedwith = 'internal' for
> +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
> +# be specifying the version(s) of Mercurial they are tested with, or
> +# leave the attribute unspecified.
> +testedwith = 'internal'
> +
> +cmdtable = {}
> +command = cmdutil.command(cmdtable)
> +
> +revsetpredicate = registrar.revsetpredicate()
> +
> +pager.attended.append('underway')
> +
> + at revsetpredicate('_underway([commitage[, headage]])')
> +def underwayrevset(repo, subset, x):
> + """Changesets that are still mutable and other relevant commits."""
> + args = revset.getargsdict(x, 'underway', 'commitage headage')
> + if 'commitage' not in args:
> + args['commitage'] = None
> + if 'headage' not in args:
> + args['headage'] = None
> +
> + # We assume the only caller of this revset adds a topographical sort
> + # on the return. This means there is no benefit to making the revset
> + # lazy since the topographical sort needs to consume all revs.
> +
> + # Mutable changesets (non-public) are the most important changesets to
> + # return. ``not public()`` will also pull in obsolete changesets if
> + # there is a non-obsolete changeset with obsolete ancestors. We explicitly
> + # exclude obsolete changesets from this query. The expansion below to
> + # pull in parents of returned changesets will add the first obsolete
> + # ancestor, adding sufficient context to the returned set.
> + rs = 'not public() and not obsolete()'
> + rsargs = []
> + if args['commitage']:
> + rs += ' and date(%s)'
> + rsargs.append(args['commitage'][1])
> + mutable = repo.revs(rs, *rsargs)
> + relevant = mutable
> +
> + # Add parents of mutable changesets to provide context.
> + relevant += repo.revs('parents(%ld)', mutable)
> +
> + # We also pull in (public) heads if they a) aren't closing a branch b) are
> + # recent.
> + rs = 'head() and not closed()'
> + rsargs = []
> + if args['headage']:
> + rs += ' and date(%s)'
> + rsargs.append(args['headage'][1])
> + relevant += repo.revs(rs, *rsargs)
> +
> + # And add the changeset the working directory is based on.
> + wdirrev = repo['.'].rev()
> + if wdirrev != nullrev:
> + relevant += revset.baseset(data=(wdirrev,))
> +
> + return subset & relevant
> +
> + at command('underway', commands.templateopts)
> +def underway(ui, repo, **opts):
> + """show changesets whose development is in progress
> +
> + This command will print a graphical view of mutable and "important"
> + changesets. Specifically, it shows changesets that are:
> +
> + * mutable
> + * parents of mutable changesets
> + * heads that don't close branches
> + * what the working directory is based on
> +
> + The purpose of this command is to give an overview of unfinished work
> + (defined as mutable, non-public changesets) and provide additional
> + context to help finish that work.
> + """
> + commitage = ui.configint('underway', 'maxcommitage', 0)
> + headage = ui.configint('underway', 'maxheadage', 14)
> +
> + rsargs = {
> + 'headage': '-%d' % headage
> + }
> + if commitage:
> + rsargs['commitage'] = '-%d' % commitage
> +
> + args = ', '.join('%s="%s"' % (k, v) for k, v in rsargs.items())
> + rs = 'sort(_underway(%s), topo)' % args
> +
> + return cmdutil.graphlog(ui, repo, rev=[rs], **opts)
> diff --git a/tests/test-underway.t b/tests/test-underway.t
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-underway.t
> @@ -0,0 +1,335 @@
> + $ cat > testmocks.py << EOF
> + > import os
> + > from mercurial import util
> + > origmakedate = util.makedate
> + > def mockmakedate(timestamp=None):
> + > if 'FAKETIME' in os.environ:
> + > return int(os.environ['FAKETIME']), 0
> + > return origmakedate(timestamp)
> + > util.makedate = mockmakedate
> + > EOF
> +
> + $ cat >> $HGRCPATH << EOF
> + > [extensions]
> + > underway =
> + > testmocks = $TESTTMP/testmocks.py
> + > EOF
> +
> +`hg underway` works on an empty repo
> +
> + $ hg init repo0
> + $ cd repo0
> + $ hg underway
> +
> +Single, unpublished changeset
> +
> + $ touch file0
> + $ hg -q commit -A -m file0
> + $ hg underway
> + @ changeset: 0:d26a60f4f448
> + tag: tip
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: file0
> +
> +
> +Single, unpublished changeset, no working directory
> +
> + $ hg -q up -r null
> + $ hg underway
> + o changeset: 0:d26a60f4f448
> + tag: tip
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: file0
> +
> +
> +Single, published changeset, no working directory
> +
> + $ hg phase --public -r 0
> + $ hg underway
> +
> +Single, published changeset, checked out
> +
> + $ hg -q up -r 0
> + $ hg underway
> + @ changeset: 0:d26a60f4f448
> + tag: tip
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: file0
> +
> + $ cd ..
> +
> +Now test repos with multiple heads
> +
> + $ hg init repo1
> + $ cd repo1
> + $ touch file0
> + $ hg -q commit -A -m initial
> + $ touch file1
> + $ hg -q commit -A -m 'head 1 commit 1'
> + $ touch file2
> + $ hg -q commit -A -m 'head 1 commit 2'
> + $ hg -q up -r 0
> + $ touch file3
> + $ hg -q commit -A -m 'head 2 commit 1'
> + $ touch file4
> + $ hg -q commit -A -m 'head 2 commit 2'
> +
> +All changesets are draft so all should be displayed
> +
> + $ hg underway
> + @ changeset: 4:566e972f7665
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 2
> + |
> + o changeset: 3:06edf01d2aac
> + | parent: 0:ab3e79bd1841
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 1
> + |
> + | o changeset: 2:e240c80f4191
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | summary: head 1 commit 2
> + | |
> + | o changeset: 1:b9f0938ca7db
> + |/ user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 1
> + |
> + o changeset: 0:ab3e79bd1841
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: initial
> +
> +
> +Marking the root as public should still show since it is a parent of non-public
> +
> + $ hg phase --public -r 0
> + $ hg underway
> + @ changeset: 4:566e972f7665
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 2
> + |
> + o changeset: 3:06edf01d2aac
> + | parent: 0:ab3e79bd1841
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 1
> + |
> + | o changeset: 2:e240c80f4191
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | summary: head 1 commit 2
> + | |
> + | o changeset: 1:b9f0938ca7db
> + |/ user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 1
> + |
> + o changeset: 0:ab3e79bd1841
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: initial
> +
> +
> +Marking the first head as public should hide it since it is old
> +(command assumes current time by default)
> +
> + $ hg phase --public -r e240c80f4191
> + $ hg underway
> + @ changeset: 4:566e972f7665
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 2
> + |
> + o changeset: 3:06edf01d2aac
> + | parent: 0:ab3e79bd1841
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 1
> + |
> + o changeset: 0:ab3e79bd1841
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: initial
> +
> +
> +Faking the current time should reveal first head since it is within relevance window
> +
> + $ FAKETIME=0 hg underway
> + @ changeset: 4:566e972f7665
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 2
> + |
> + o changeset: 3:06edf01d2aac
> + | parent: 0:ab3e79bd1841
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 1
> + |
> + | o changeset: 2:e240c80f4191
> + |/ user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 2
> + |
> + o changeset: 0:ab3e79bd1841
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: initial
> +
> +
> + $ FAKETIME=864000 hg underway
> + @ changeset: 4:566e972f7665
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 2
> + |
> + o changeset: 3:06edf01d2aac
> + | parent: 0:ab3e79bd1841
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 1
> + |
> + | o changeset: 2:e240c80f4191
> + |/ user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 2
> + |
> + o changeset: 0:ab3e79bd1841
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: initial
> +
> +
> +Making the first commit of head 2 public should hide root
> +
> + $ hg phase --public -r 06edf01d2aac
> + $ hg underway
> + @ changeset: 4:566e972f7665
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 2 commit 2
> + |
> + o changeset: 3:06edf01d2aac
> + | parent: 0:ab3e79bd1841
> + ~ user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: head 2 commit 1
> +
> +Making everything public should only show working directory
> +
> + $ hg phase --public -r 566e972f7665
> + $ hg underway
> + @ changeset: 4:566e972f7665
> + | tag: tip
> + ~ user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: head 2 commit 2
> +
> +
> + $ hg -q up -r e240c80f4191
> + $ hg underway
> + @ changeset: 2:e240c80f4191
> + | user: test
> + ~ date: Thu Jan 01 00:00:00 1970 +0000
> + summary: head 1 commit 2
> +
> +If no working directory, nothing is shown
> +
> + $ hg -q up -r null
> + $ hg underway
> +
> +Setting fake time will show the public heads only
> +
> + $ FAKETIME=0 hg underway
> + o changeset: 4:566e972f7665
> + | tag: tip
> + ~ user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: head 2 commit 2
> +
> + o changeset: 2:e240c80f4191
> + | user: test
> + ~ date: Thu Jan 01 00:00:00 1970 +0000
> + summary: head 1 commit 2
> +
> +
> +Modifying the max head age time works
> +
> + $ FAKETIME=864000 hg --config underway.maxheadage=5 underway
> +
> + $ cd ..
> +
> +Now test display when obsolete changesets are involved
> +
> + $ hg init obs
> + $ cd obs
> + $ cat >> .hg/hgrc << EOF
> + > [experimental]
> + > evolution = all
> + > EOF
> +
> + $ touch file0
> + $ hg -q commit -A -m initial
> + $ touch file1
> + $ hg -q commit -A -m 'head 1 commit 1'
> + $ touch file2
> + $ hg -q commit -A -m 'head 1 commit 2'
> + $ touch file3
> + $ hg -q commit -A -m 'head 1 commit 3'
> + $ hg -q up -r null
> +
> + $ hg phase --public -r 0
> + $ hg underway
> + o changeset: 3:e3e108a93016
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 3
> + |
> + o changeset: 2:e240c80f4191
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 2
> + |
> + o changeset: 1:b9f0938ca7db
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 1
> + |
> + o changeset: 0:ab3e79bd1841
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: initial
> +
> +
> +Only the first obsolete ancestor should be displayed (commit 2)
> +
> + $ hg debugobsolete b9f0938ca7db03fff32d91ca50504c0f54d14d1c
> + $ hg debugobsolete e240c80f41919c6ae8c86643f9661d50cb37201f
> +
> + $ hg underway
> + o changeset: 3:e3e108a93016
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: head 1 commit 3
> + |
> + x changeset: 2:e240c80f4191
> + | user: test
> + ~ date: Thu Jan 01 00:00:00 1970 +0000
> + summary: head 1 commit 2
> +
More information about the Mercurial-devel
mailing list