The Stochastic Game

Ramblings of General Geekery

Vim and Mercurial

Edit: since the original publication, I added a paragraph on vim-signify and
a few other little tips based on some #mercurial IRC feedback.

I already mentioned Vimwaysadvent blogging about Vim, but here’s some
more commentary on one of their entry, namely the Vim and Git one. It was
quite good so I figured I would write a “Vim and Mercurial” one!

Since Samuel (the original post’s author) did a good job with the overall
article structure, I’m going to totally plagiarize it.

The Mercurial perspective

Just like Git, Mercurial needs to open a text editor for some operations:

  • editing commit messages.
  • editing history (the “interactive” half of what Git calls “interactive
    rebasing”).

Mercurial also needs to open some kind of tool (not necessarily a text editor…
but you can guess which tool we’ll use!) for some other operations:

  • showing a diff.
  • resolving a merge.

Editing commit messages

Just like Git and pretty much every other tool in existence, Mercurial checks
the $EDITOR environment variable to know what text editor to use. And just
like Git, it also offers a configuration setting for when it’s easier to set it
this way:

[ui]
editor = vim

If you have both $EDITOR and ui.editor set, Mercurial favours ui.editor.

Once you’ve run hg commit and Vim opens, you can type your commit message as
usual. If you change your mind, you can do :cq and exit Vim with a non-zero
exit code, which Mercurial will detect and abort the commit. Otherwise, :wq
will exit normally and Mercurial will proceed.

Editing history

Where Git has git rebase --interactive to do both rebasing and history
editing, Mercurial has two different operations: hg rebase to rebase, and hg histedit to edit history. Rebasing doesn’t require the involvement of a text
editor, but history editing does.

First, Mercurial doesn’t let you edit history by default. It wants you to
consciously opt-in to this type of tricky command, by enabling the extension it
comes in:

[extensions]
histedit =

Once that’s done, you’ll get the hg histedit command, which as usual reads the
$EDITOR environment variable and the ui.editor configuration setting. The
text buffer that will open in Vim will look very similar to the one from Git’s
interactive rebase, so just use ciw, dd, and p or P.

Showing a diff

Using hg diff prints out a diff in the terminal. To use an external tool,
you’ll need to enable another extension (yes, Mercurial is big on forcing you
to enable extensions
, sadly):

[extensions]
extdiff =

Then you can configure one or more “external diff tools” for Mercurial to use,
like, for instance:

[extdiff]
cmd.vimdiff = vimdiff

You can then run hg vimdiff ... and it will open Vim in diff mode over the
changed file. Sadly, it only works fine for cases where you’re only diffing
a single file. If you pass a revision that touched multiple files, the extdiff
extension will copy the previous/next versions of those files into a pair of
temporary directories, and pass those to Vim. Vim doesn’t have anything to
handle directory diffing out of the box.

The standard solution for this is to install the DirDiff Vim plugin, which
does exactly what the title implies. Its only drawback is that it’s not very
friendly to non-traditional Unix setups – Windows, or using Fish as your shell,
among other examples, make the Vim plugin fail.

You can still try it yourself by following the example on the Extdiff
extension
’s wiki page:

[extdiff]
# add new command called vimdiff, runs gvimdiff with DirDiff plugin
#(see http://www.vim.org/scripts/script.php?script_id=102)
# Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
# your .vimrc
cmd.vimdiff = vim
opts.vimdiff = -f '+next' '+execute "DirDiff" fnameescape(argv(0)) fnameescape(argv(1))'

A note on your working directory

Watch out for plugins or scripts you might have in your .vimrc that change the
working directory! I had some issues with my configuration of vim-projectroot
which I had setup to automatically cd into the current buffer’s project root.
This messed up extdiff because the way it runs the external tool is:

  • Set the current working directory to a temporary folder that contains the
    2 sub-folders with all the files’ snapshots.
  • Pass the name of those 2 sub-folders as arguments to the external tool.

As such, if Vim’s current working directory is changed by the time it executes
DirDiff, it will fail to find anything.

Resolving a merge

Thankfully, resolving merges with Vim’s 3-way diff mode is supported pretty much
out of the box. In most distros’ Mercurial package, there’s actually a lot of
external tools supported for resolving merges in Mercurial. Check them out by
typing hg config merge-tools.

The one we’re interested in is, of course, declared as vimdiff. Unless your
Mercurial install was somehow packaged differently for your OS, it should look
a bit like this:

merge-tools.vimdiff.args=$local $other $base -c 'redraw | echomsg "hg merge conflict, type ":cq" to abort vimdiff"'
merge-tools.vimdiff.check=changed
merge-tools.vimdiff.priority=-10

At this point, you just need to bump the priority in your own .hgrc if it
doesn’t pick it up by default because it finds another available tool with
a higher priority on your system:

[merge-tools]
vimdiff.priority = 99

The default configuration puts the “local” file on the left, the “other” file in
the centre, and the “base” file on the right. I prefer to have the “base” file
in the middle, so I can better reason about how each side (left and right)
modified the code from that base version… so I just copied the default setting
and switched up the arguments in my configuration file:

[merge-tools]
vimdiff.args=$local $base $other -c 'redraw | echomsg "hg merge conflict, type ":cq" to abort vimdiff"'
vimdiff.priority=99

From there, you can do a mix of diffget/diffput (with do and dp as their
default bindings… people usually remember do as “diff obtain”), and ad-hoc
editing for trickier situations. Unlike a normal diff, however, you can’t use
do and dp directly: you have to specify which buffer you’re putting in or
obtaining from. The buffers in this case are simply numbered from left to right
(1 is “local”, 2 is “base”, 3 is “remote”) so you can do 3do or 1dp and
such. Alternatively, you can change your statusline to display %n when
a buffer has &diff enabled, but I don’t find that necessary.

The Vim perspective

Vanilla

As the original article explains with Git, a quick way to run Mercurial
from inside Vim is to use any of:

  • :!hg, which will execute the Mercurial process in a new shell. Unless you
    add some fancy syntax around it, it has the downsides of blocking Vim until the
    process exits, and not being able to interact with that process (like entering
    input).

    Here you can make use of Vim’s % shorthand for the current buffer, like with
    :!hg add %.

  • :terminal, which gives you a shell where you can run Mercurial and any other
    command line tool.

And, again from the original article, you can check some common Vim
configuration details to auto-reload files when you do an operation that changes
your working copy (e.g. hg update), highlight conflict markers, and so on.

A note on conflict markers

Note however that Mercurial doesn’t leave conflict markers by default. That’s
because it will just run some non-interactive pre-merge step using its own
internal merging algorithm, and will run the external tool (which we just
configured above to be Vim) if it finds any conflicts… in which case it shows
you “clean” files for you to resolve.

As a result, you will only see conflict markers if you specifically changed
Mercurial’s configuration to do that. For example, you may change the pre-merge
step to be :keep-merge3.

Given the sheer amount of customizability that Mercurial’s merging gives you,
this goes way out of scope for this article, but if you’re not happy with how it
works, check out the merge-tools configuration section help and the help
page on merge tools
. There’s really little chance you can make it work
exactly the way you want.

Plugins

Since Mercurial is a lot less popular than Git, it also has a lot less available
plugins for Vim.

Lawrencium

I can start with a shameless plug (hohoho) for my plugin!

Written by yours truly, it started as a port of Fugitive for Mercurial, but
it has since taken a life of its own. I think it’s pretty solid, but you may
find that it breaks down a bit if your workflow differs too much from mine.
That’s what bug reports and pull request are for, though!

It provides you with an interactive hg status window, easy ways to show diffs
in various ways, some basic hg log and hg annotate views, and more.

Check it out and report back!

Signify

Already mentioned in the original article, Signify gives you an idea of
what you modified in the current buffer. Unlike Gitgutter, which the
original article also recommends, Signify works with a multitude of VCSes,
including Mercurial.


With Anthem finaling, I haven’t been to enough meetings at work these past couple months to doodle much in my sketchbook… those are pretty much the last couple good ones


It’s been a long time since I last doodled. Can’t even get basic body proportions right 😭


Parents, you have exactly ONE CHANCE, after having listened to “Won’t Get Fooled Again” or “Baba O’Riley” or something, when the kids ask you who is playing that song, to reply with the right intonation. DON’T FUCK IT UP.


It’s really annoying to see all those animated movie remakes being done in 3D CGI. Like, what’s wrong with 2D animation? Or even 2D-looking CGI? I guess I need to throw my money at the Spider-verse movie to show support for different artistic styles.


7 year old kid: “That’s super old, super super old. Like, from the 20th century.”


Mercurial Advent Blogging

Here’s another advent blog series! This time it’s from Kamal Marhubi
and it’s on the subject of Mercurial. It’s pretty interesting, especially
for giving a fresh perspective on Mercurial’s user experience.

The introduction post lays down some of what makes Mercurial interesting
(phases, changeset evolution, extensibility) while the third post catches
up to another cool thing (revsets, one of my favourite Mercurial features) that
was missing from that initial list.

Extensions and papercuts

There’s a good article in the series about how Mercurial’s reliance on
extensions (and more specifically the need to explicitely enable them) is one of
the several “papercuts” you get when you start using it. This reminded me of
Josef Sipek’s “Modern Mercurial” blog series which starts with an
article precisely about that problem
:

Then it suddenly hit me —- before these tweaks, I had been using Mercurial
like it’s still 2005!

I think this is a very important observation. Mercurial didn’t seem to be
improving because none of the user-visible changes were forced onto the users.

The problem is that the Mercurial devs have an extremely strict approach to how
the CLI should behave, and how no breaking changes should ever be introduced.
This means that:

  1. No “dangerous” commands should be accessible by default ("don’t give the
    user opportunities to shoot their own foot
    "), which explains why history
    rewriting commands like rebase are disabled out-of-the-box.
  2. Things that were later added (like colours, pager support, etc.) were often
    done in an optional extension to not break previous behaviour.

Arguably, these strict rules are partially the reason why the Mercurial CLI is
so nice to use compared to other CLIs like Git’s, but they’re also obviously
causing a lot of pain elsewhere.

I naively think this might be somewhat fixable by:

  • Making the first hg run setup some sane default if it doesn’t find any user
    .hgrc (i.e. better bootstrapping).
  • Relaxing some of the strict CLI rules by realizing that most use-cases that
    rely on specific historical behaviours (like automated scripts) are already
    most probably setting environment variables like HGPLAIN, in which case
    Mercurial can disable some of the more “human facing” features.

But of course, it’s lot more complicated than this… and to some degree, this
was addressed with the tweakdefaults setting (formerly known as “friendly
hg
”), but, ironically enough, you still need to know about enabling
it, since it default to false, of course.

Staging areas

The fourth post talks about how Mercurial doesn’t have a staging area
(a.k.a. index) like in Git.

It’s kind of funny to me that we have people who started with Git as their first
(and sometimes only) VCS, and it will soon dawn on them that, frankly, there’s
nothing out there quite like Git’s index (well, at least not in any of the
half-dozen VCSes I’ve used so far). Git’s pretty unique on that front.

Although I don’t recommend trying to emulate a tool’s idiosyncratic workflow
when using another tool, people coming from Git invariably ask about this so
there’s always been some partial answer for Mercurial. A few years ago, the MQ
extension was that answer (it kinda gives you something that looks like
multiple staging areas!), but it seems that most of the Mercurial community
has moved away from it (I have too). MQ is still available for those who want
it, but these days it seems that using secret phases and history rewriting is
more common (and it also kinda gives you multiple staging areas too!).

I don’t really do that, but I imagine it would work along the lines of:

[alias]
newstage = commit -s -m "STAGING AREA"
upstage = commit --amend

So when you start working on something new, you push your work-in-progress with
hg newstage into a secret commit called “STAGING AREA”. Since it’s secret,
it won’t get pushed and will stay local. Then, as you keep working, you run hg upstage every now and then to “grow” that commit. When it’s ready, you change
that commit’s phase to draft. It’s a bit clunky (there are 2 aliases when
ideally there would be only one) but you get the idea, and with a bit of
scripting I’m sure that it’s possible to get it down to a similar UX as Git’s.

Twelve days to go

Anyway, we’re still half-way to Christmas so check out Kamal’s blog
for the rest of the series (assuming he keeps it going 🙂 ).


Vimways Advent Blogging

Advent blogging, i.e. blogging about a specific subject every day between December 1st and December 24th, seems to be picking up steam quite a bit this year. In the previous years, FastMail was pretty much the only one in my feeds doing this but, ironically, as they stopped doing it this year, it’s been replaced by others like Micro.blog’s Manton Reece (although he’s only doing half of it) or Vimways.

Inspired by 24ways, another advent blog dating back a dozen years, Vimways is, as you might guess, about Vim!

I was flattered to have Gutentags, my little Vim plugin, mentioned in the 2nd entry of the blog, even if the Daniel Moch, the author, said he stopped using it a while ago. Since the various methods mentioned in the article are not all equivalent, I figured I would add some clarifications here.

This requires some basic knowledge about Vim’s tags feature-set, but thankfully if you’ve read the aforementioned Vimways article, you should know everything you need to.

Synchronization and scalability

Tags files contain the symbols of your codebase. As you work, however, symbols change – you add and remove methods, rename them, change their signature, etc. So, unless something is being done about it, the tags files slowly but surely get out-of-sync with the source code, until you can’t really jump around anymore (it’s common for me to keep a Vim instance alive for days on end!). Having something that can keep the tags in sync with code as you work on the latter is therefore important.

Now, most “simple” methods for managing tags files rely on running ctags whenever the tags file needs to be kept in sync with the source code. This works fine for most cases, because the codebase is either small (like in most utility/low-level libraries) or large but broken up in small pieces whose vast majority is excluded from the tags files (like most web development projects where all dependencies are in a node_modules-type folder).

For bigger codebases, however, this quickly breaks down because ctags takes a long time to run. It’s more efficient to only update the part of the tags file that relates to the code you’re changing, and leave the rest alone.

Gutentags does this by running ctags only on the file you just saved in Vim.

If you want to keep your tags management separate from the text editor, you can, like Daniel says in his article, use some file-system watcher to regenerate your tags… you can even use that file-system watcher to run Gutentags’ own tag generation script directly, passing the proper options:

cd /my/project
/path/to/update_tags.sh -s /my/project/file_that_was_written

Run update_tags.sh -?, or look inside the script’s source code, to see what other options are available. Even if you don’t use Gutentags as a Vim plugin, you might want to use its sh and cmd scripts!

Combining methods

One common problem with having Vim manage the tags file is that it doesn’t know when the codebase is being modified externally – mainly when pulling/merging/etc. files in your VCS.

To fix this, many Gutentags users actually combine Gutentags with Tim Pope’s Git hook-based. After all, there’s no reason not to have the best of both worlds, where you don’t need to remember to run :GutentagsUpdate! after a git pull, and where your tags don’t go out of sync when you write code
either!

It’s a DIY world

Vim is ultimately about being able to do things your own way, so hopefully you can find yet another cool method to manage your tags, in which case you can share it with us!


Stockholm people! I’ll be in your beautiful city next week for the annual Frostbite DevDays, so ping me if you want to meet for fika or even just say hi!