Pierre-Yves David

Core contributor to Mercurial, initiator and maintainer of ChangesetEvolution and Octobus a Mercurial consultancy company.

IRC: marmoute

twitter: marmoute

work: Octobus

Notes and Opinions about User Experience Design

Combining feature by adding `hg command --flags`

It is often tempting to combine command that are often used together through a flag. For example hg pull and hg update are often used in combination, so the hg pull --update flag was added, same applied to hg pull --rebase.

I think that this pattern should be added with care. The main risk is that the new flag will end up pulling a lot of unrelated complexity and associated flags within the original commands.

In the hg pull --update/--rebase examples this meands that --update special action (like merging files) to be dealt with by hg pull which previous did not need the context. Merging changeset comes with new config (like the one to select the merge tools, continue and abort, etc.). You can end up multiplying the complexity of the two commands instead of simply summing up that complexity.

Other good example are hg commit --amend and hg amend, the amend feature comes with various new flags (--current-date, --current-user) and other evolution related complexity (and future flag, e.g. to control orphan creation).

Does this means we should never do so? No, this is definitely useful in various case, we should just do it with care and stay aware of the potential complexity pulled into the command. Adding many more flag losely related to the command and only relevant in some case can make the help harder to follow and the overall user Experience harder.

In previous discussion, RyanMcElroy pointed out that this recurrent needs to combine multiple operation is a hint for the needs of a more powerful alias system. He says that having and easy way to make a "multi commands" alias, possibly while ensuring "atomic" operation could fit many of the use-case that trigger the needs for adding a flags to glue a command to another one.

Another side effect of adding "unrelated" flags to command is that is can drastically affect the "core" semantic of the command. For example, hg pull is a command that adds changeset to the repository, and has absolutely no business with the working copy. having the hg pull --update flag suddenly means it now have business with the working copy. This affect command "boundaries" and can lead to confusing UI. Note that thisis not automatic for example, hg commit is creating a changeset, without altering the working copy contents (the file content stay untouched), adding the hg commit --amend does not change this aspect of the command.

(I am using hg pull --update example because it is clear and simple, but I am not especially against it.)

Feature discoverability, command vs flags, etc

Getting user to discover new features or variant of existing feature is always a bit complicated. In that regards, it is often easier for a user to discover a new command --flags for a command it knows than to discover a new commands entirely. And this seems true.

For that reason, the project have been leaning more toward "less commands, more flags". This sometimes hits the issue mentionned above, where too much logic get pushed into a single command, with either too simplistic handling of some case or too many complexity coming along.

Another common design opinion is that having many different way to do the same things is confusion and should be avoided. Combined with the "less commands, more flags" this has lead to having the "flag" version only.

Over time, I have been thinking about experimenting more about an hybrid approach. Where we could put flag to had basic version of a feature on command where it is likely to be found. (e.g. hg commit -amend) and point to a more powerfull, but narrower commands (e.g. hg amend).

We actually ended up putting it in practice in the past without really realizing it. For example the following chain follow this principle hg commithg commit --amendhg amendhg amend --extracthg uncommit. Each new dedicated command introduce some new flags that did not made sense in the previous one.

It would be interesting to start incorporating this idea earlier in the UX design process and build meaning full "pyramid" of commands to build more and more advance feature at each level.

A possible application of that I have been thinking about recently is how we could have hg merge --linear and hg merge --onto (name can change) to easily bring rebase like feature to user, keeping the more advance case to be dealt with by more dedicated command (like rebase).

Evolution UX and small iteration

Mercurial has a nice "decomposition" philosophy from the start : "commit" is distinct from "push", "pull" is distinct from "update" and "merge", etc. It does so by having valid and clear "state" that represent the "not so great situation" that the user will have to resolve eventually (e.g. multiple heads on one branch).

This decomposition helps users to focus on the things at hand, without forcing them into resolving thing -right-now- when they could do it at a better time. This has been an important design principle for Evolution too (and partly inherited from MQ). You can keep amending a changeset without having to deal with the consequence of this amend until you are ready to do so (e.g orphan changesets). And dealing with such consequence only when ready, and only for the part that is ready to do so.

This is a powerful feature and we try to build command UI that nudge user toward using it. Both by making the command enforce it by default (e.g. hg amend not evolving children) and by help using to navigate in their unstable state (e.g. hg evolve things, hg next doing transparent evolution, …).

So in the general case, on of evolve design principle is for command to focus on the minimal operation to performs its duty. Leaving the consequence to be solved later. If an option to automatically apply "resolution" exists, it is usually disabled by default.

There are also strong technical reason to go that route. For example if hg amend where doing auto evolution of the children, reworking a stack of "N changesets could lead the creation of O(N²) obsolescence markers. N² quickly create scaling issue once N is no longer a very small value.


Pierre-YvesDavid (last edited 2021-10-08 16:04:03 by Pierre-YvesDavid)