Fixing Git, Part 1
Next post in series: Fixing Git, Part 2
Part 1: Git Sucks
I am embarking on a quest to fix the distributed version control software (DVCS) known as Git. And no, I'm not proposing yet another DVCS program (even though I do prefer Mercurial), nor am I going to tell you not to use Git. No, Git is not optional. We must all use Git. Trying to avoid Git is like trying to browse the internet with JavaScript disabled. Technically possible, but practically impossible. Git won the DVCS war, and we all just have to adapt.
So that what this mini-series is about: smoothing the rough edges of Git so that we can have just one fewer head-ache to deal with when working on our projects.
What's Wrong with Git?
I learned Git and Mercurial at the same time in 2010. Since then, I've used Git nearly every day, and Mercurial about once per week. And the sad fact is that I've had to Google a git command every day, while I've only had to Google a Mercurial command twice in the past 10 years! How can I find Mercurial so much more intuitive, and understand it's built-in documentation so much easier, than Git?
The answer is simple: Git's command-line interface (CLI) UI is terrible, and the few GUI tools that exist are not much better.
The reasons Git has such a bad UI are many and controversial, but I'm just going to pick out two particular issues.
Problem 1: Overloaded Commands
To be fair, Git is not alone in this sin, but it's a problem that some commands (most notably the checkout command) have very different behavior depending on which options they are given. For example:
Go back to a previous version:
git checkout commit_hash
Make a new branch:
git checkout -b new_branch_name
or
git checkout -b new_branch_name source_banch_or_commit_hash
Go to the tip of an existing branch:
git checkout branch_name
List all local branches:
git checkout -l
Haha! Just kidding! No, all branch related operations should use the overloaded git checkout command, except listing (or deleting) branches, that uses the git branch command:
git branch -a
Interesting side-note: git branch does not have a --porcelain option, which will become a problem in Part 2.
This is just one example, but one that will bite the unwary on an almost daily basis. You can see that command overloading is a bit arbitrary: Why not list branches with the checkout command? Why not make new branches with the branch command? Regardless, the result is the user must memorize a list of arcane formulae just to get the job done. As much as I love playing a Wizard in Dungeons & Dragons, I don't want to role-play one while coding.
Problem 2: Gap between high-level and low-level concerns
This problem is a little more subtle, but is no less important.
If you take a step back from Git, and just think about the typical DVCS workflow, you can create a short list of tasks that cover 98% of your DVCS interaction. Vincent Driessen's famous 2010 blog post A Successful Git Branching Model provides an excellent overview of the DVCS workflow. From that workflow, we can see that, most of the time, what you really want your DVCS to do is:
1. Create a named local branch (aka "feature branch") from the authoritative central repository
2. Commit changes to your local branch
3. Go back to an earlier commit and continue development from there (in case you committed a mistake or need to push an urgent hot-fix)
4. Merge any new changes from the authoritative central repository since operation 1 above into your local branch
5. Send your local branch back to the authoritative central repository as a commit to the development/main branch
The Git commands for these operations are:
1. Create a local branch from the central repository
git clone https://github.com/username/project_name
cd project_name
git config credential.helper cache
git config user.name "My Name"
git config user.email "My.Email@somedomain.com"
git checkout -b feature_branch main_branch
2. Commit changes to your local branch
git status -uall
# check the list of files for surprises
git add -A
git commit -m "my commit message"
3. Go back to an earlier commit and continue from there
# Git does not support anonymous branching, so you need
# to name a new branch every time you do this or there
# will be "detached head" problems
git checkout -b new_feature_branch old_commit_hash
4. Merge new changes from the central repository
git merge --no-ff main_branch
# now resolve merge conflicts and re-run tests
git add -A
git commit -m "merged with main_branch"
5. Send back to the central repository as a commit
git checkout main_branch
git pull
# if any new commits were pulled, go back to operation 4
git merge --squash feature_branch
git commit -m "very detailed commit message"
git push origin main_branch
Whew! That's a lot of Git for just a few operations. I appreciate the complexity of what Git is doing behind the scenes to make it all happen, but as a user of Git, my concerns are quite high-level (e.g. "Save my changes") while the commands are rather low-level ("show me all files that changed, then stage files in the index, then make a commit"). There's a big gap between the "what" and the "how" of DVCS that, in other contexts, would usually be filled by middleware.
So why can't each of these common tasks have it's own command in Git? Actually, there's a good reason they can't. While those five operations make up 98% of Git usage, 2% of the time you really do need fine-grained control in order to solve more complex problems (like discovering sensitive information in an old commit that must be excised from the entire history). If each operation had it's own command, there would be way too many commands (or too many CLI switch options) for even the most die-hard Git user to remember.
The Solution!
If we can't quit Git, and don't want to write yet another DVCS program, then what can we do? Grin and bear it? Beg Linus Torvalds to add more commands to Git? Create aliases to shorten each command to a 3-letter abbreviation?
Nah! We write a wrapper script, of course! Stay tuned for Part 2 where I combine Git, Python, and bad toilet analogies to make a better front-end CLI for Git!