GitHub recently changed the repository's default branch name from master
to main
, challenging millions of developers' muscle memory:
git checkout master
error: pathspec 'master' did not match any file(s) known to git
git checkout main
Switched to branch 'main'
It's going to take a while to get used to this 😀
$ # In a recent repo that did the switch:
$ git checkout master
error: pathspec 'master' did not match any file(s) known to git
$ # In an older repo that hasn't switched yet:
$ git checkout main
error: pathspec 'main' did not match any file(s) known to git
This is an attempt to improve a developer's workflow by offering a fallback system.
Unfortunately, git
does not allow its core commands to be overridden, neither with an alias, nor a custom command script, as I try to show below:
$ git config --global alias.hw '!hw(){ echo "Hello world";};hw'
$ git hw
Hello world
$ git config --global alias.checkout '!hw(){ echo "Hello world";};hw'
$ git checkout
--Does not output Hello world--
$ echo "#!/usr/bin/env sh
echo 'Hello world'" > "$HOME/bin/git-helloworld"
$ chmod u+x "$HOME/bin/git-helloworld"
$ git helloworld
Hello world
$ mv "$HOME/bin/git-helloworld" "$HOME/bin/git-checkout"
$ git checkout
--Does not output Hello world--
My first approach, rather lean, is to define a new alias, cm
, which will check out either main
or master
, whichever it finds first.
$ git config --global alias.cm '!cm(){ git checkout main || git checkout master;}; cm'
$ # In an old repo:
$ git cm
error: pathspec 'main' did not match any file(s) known to git
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ # In a newer repo:
$ git cm
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
The behaviour when both master
and main
branches exist can be decided by the order in which the commands are written:
$ # If both exist, this will check out `main`
$ git config --global alias.cm '!cm(){ git checkout main || git checkout master;}; cm'
$ # If both exist, this will check out `master`
$ git config --global alias.cm '!cm(){ git checkout master || git checkout main;}; cm'
Luckily, I'm used to a simple git alias, co
, which has been in my .gitconfig
file for years now:
$ git config --global alias.co checkout
$ git co master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
As a result, I haven't used git checkout
in years and only use git co
instead.
Although I cannot override git checkout
's behaviour, the closest thing I can do is redefine git co
to be a bit smarter.
I decided to go with a custom command script, as I prefer readable code and did not want to cram too much unreadable code in my .gitconfig
file. I went with Ruby as it is my favorite language, but you can replace it with whatever language you like.
The Ruby script shared in this Gist needs to be named git-co
, be executable and somewhere in your path.
$ # Setup
$ MY_BIN="$HOME/bin"
$ mkdir -p "$MY_BIN"
$ export PATH="$MY_BIN:$PATH"
$ cp git-co "$MY_BIN"
$ chmod u+x "$MY_BIN"/git-co
$ # In an old repo, with `master` but no `main`:
$ git co main
'main' does not exist, falling back to 'master'.
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git co other
Switched to branch 'other'
$ git co master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ # In a newer repo, with `main` but no `master`:
$ git co master
'master' does not exist, falling back to 'main'.
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
$ git co other
Switched to branch 'other'
$ git co main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
$ # In an updated legacy repo, with both `main` and `master` branches:
$ git co master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git co main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Unfortunately, I realized after a few minutes that this script introduces a huge drawback to my usual git co
workflow: autocompletion does not work anymore!
$ # Before, with a simple git alias
$ git co [Tab]
HEAD main other-branch
$ # Now, with ~/bin/git-co
$ git co [Tab]
CONTRIBUTING.md README.md ...
To fix this, we need to tell the shell (bash
, zsh
, etc) how git co
autocompletes. Fortunately, git's completion scripts make that task fairly easy. With the very basic understanding I have of it, we just need to implement a function named _git_co
and have it do something similar to what the existing _git_checkout
function does.
This is the simplest thing I managed to write:
$ _git_co() { _git_checkout ; }
The result is satisfying:
$ git co [Tab]
Display all 16570 possibilities? (y or n)
$ git co mas[Tab]
master mater
$ git co -b [Tab]
Display all 4862 possibilities? (y or n)
I really need to cleanup my branches, but you get the idea! The last one also reproduced the original git checkout
autocomplete behaviour that only shows local branches when using the -b
flag.
Currently, the Ruby script I wrote will help only if git co
was passed a single argument (assumedly a branch or reference name), because I did not want to spend time parsing the arguments passed to git co
. If you happen to pass more than one argument to git co
, then it will not try to be smart and may fail to send you to the proper master
/main
branch:
$ git co main
'main' does not exist, falling back to 'master'.
Already on 'master'
Your branch is up to date with 'origin/master'.
$ git co --quiet main
error: pathspec 'main' did not match any file(s) known to git
All in all, I'm rather satisfied with this solution, even if it's not perfect and required some fiddling. I achieved the goal I had set myself: this provides developers with a crutch for the most common frustration scenario related to the switch from master
to main
.
There still is a lot to explore related to this change, such as how CI and other tooling may need an update which could be tricky, and how automation could alleviate the issue, but that will be for another time!