A while ago, I happened to see a tweet from @dhh where he mistyped a Git command as git pushy
and was surprised to notice that Git figured out that he probably meant git push
and then gave him 0.1 seconds to verify if that's what he wanted to run before it ran it anyways.
100 milliseconds?
As David is a semi-professional race car driver in addition to being a fellow Ruby programming nerd, he naturally noticed that the amount of time that Git afforded him to react was impossible for even Formula One drivers.
Of course this seems like a ludicrous bit of Git functionality, but I figured if this was surprising to David, you too might wonder why Git gave him (and possibly gives you) about the length of time that it takes a human eye to blink in order to:
- read a sentence
- determine if it's correct
- attempt to cancel the command
What could possibly be the reason to wait 100ms? So little time is essentially equivalent to simply running the command.
Well, it's a combination of a misunderstanding, a misconfiguration, and the suggestion, 17 years ago, of a somewhat questionable unit of time by the Git maintainer himself.
How was this designed to work?
It's important to note that this is not the default functionality of Git.
The default response to typing a command that doesn't exist is to simply not run anything, figure out which commands you might have meant by string similarity and then just exit.
If most of you type git pushy
, you'll probably get this instead:
❯ git pushy
git: 'pushy' is not a git command. See 'git --help'.
The most similar command is
push
Originally, if you typed an unknown command, it would just say "this is not a git command". Then in 2008, Johannes Schindelin (somewhat jokingly) introduced a small patch to go through all the known commands, show you what is most similar to what you typed and if there is only one closely matching, simply run it.
Then Alex Riesen introduced a patch to make it configurable via the help.autocorrect
setting. In this initial patch, this setting was simply a boolean.
Since Git config settings that expect a boolean will interpret a 1
value as true
, you could originally set help.autocorrect
to 1
to have it automatically run the corrected command rather than just tell you what is similar.
As part of the conversation around this patch, Junio Hamano, to this day the Git maintainer, suggested:
Please make autocorrect not a binary but optionally the number of
deciseconds before it continues, so that I have a chance to hit ^C ;-)
Which was what the setting value was changed to in the patch that was eventually accepted. This means that setting help.autocorrect
to 1
logically means "wait 100ms (1 decisecond) before continuing".
Now, why Junio thought deciseconds was a reasonable unit of time measurement for this is never discussed, so I don't really know why that is. Perhaps 1 full second felt too long so he wanted to be able to set it to half a second? We may never know. All we truly know is that this has never made sense to anyone ever since.
So, the reason why it waits 100ms for David is that at some point he presumably learned about this setting, quite reasonably assumed that it was a boolean and set it to what Git config also generally considers to be a 'true' value in order to enable it:
❯ git config --global help.autocorrect 1
Not understanding that in this context, this means "wait 1 decisecond, then do whatever you think is best" rather than "please turn this feature on".
What should it be set to?
So, clearly you can set it to 10
for a full second or whatever. However, over the years, this setting has gathered a few other options that it will recognize.
According to the documentation, here are the values it can be set to:
- 0 (default): show the suggested command.
- positive number: run the suggested command after specified deciseconds (0.1 sec).
- "immediate": run the suggested command immediately.
- "prompt": show the suggestion and prompt for confirmation to run the command.
- "never": don’t run or show any suggested command.
Honestly, "prompt" is probably what most people would find the most reasonable, rather than a specific amount of time to wait for you to cancel the command.
If you do want to have it prompt you, you can run this:
❯ git config --global help.autocorrect prompt
❯ git pushy
WARNING: You called a Git command named 'pushy', which does not exist.
Run 'push' instead [y/N]?
How does it guess?
To keep picking on David, he followed up after doing some quick testing to see what the logic could be and it turns out that Git won't just take wild guesses.
There is a point where it will simply assume you're way off and not guess anything:
However, it's interesting to play around with this a bit:
❯ git bass
WARNING: You called a Git command named 'bass', which does not exist.
Run 'rebase' instead [y/N]? n
❯ git bassa
git: 'bassa' is not a git command. See 'git --help'.
❯ git dm
git: 'dm' is not a git command. See 'git --help'.
The most similar commands are
am
rm
❯ git dma
WARNING: You called a Git command named 'dma', which does not exist.
Run 'am' instead [y/N]?
So, git bass
is close enough to rebase
for it to guess that this could be what you mean. But bassa
is not close enough for it to still think you maybe meant rebase
.
Also, git dm
could mean am
or rm
, but interestingly it matches on the end of the string and not necessarily from the beginning. Also, dma
confidently matches am
only.
So how is this working?
As some of you may have guessed, it's based on a fairly simple, modified Levenshtein distance algorithm - which is basically a way to figure out how expensive it is to change one string into a second string given single character edits, with some operations being more expensive than others.
It has a hard coded cutoff, so once it's too expensive for any of the known commands, it just assumes you really messed up, which is why some of these don't match anything and others, even though quite different, match several options.
My little fix
In going through a bunch of the related autocorrect Git code in order to research this little blog post, I realized that there could be a relatively simple and largely backwards compatible fix.
Since a 1
value is so fast, it's in all human terms functionally equivalent to "immediately", I wrote up a small patch to interpret a 1
as "immediately" rather than "wait 100ms".
Junio came back to request that instead of special casing the "1" string, we should properly interpret any boolean string value (so "yes", "no", "true", "off", etc), so version two of my patch is currently in flight to additionally do that.
If I can get this landed, maybe future versions of Git will no longer test the mettle of Formula One drivers.
Anyhow, hope you enjoyed that little trip down this old alley of seemingly strange Git functionality. As is often the case with Git, there is some hidden method to the apparent madness, and like any open source project, there is a path to make it slightly better!