Get a clean git history
If you're working on a project with a lot of people, it's important to get a clean and clear git history. Through this article, i'll present you some easy git commands that will help you.
#Why a clean git history matters ?
When it comes to read your git history, it's important to find easily what you're looking for and also to be able to fix it quickly.
For example if you need to revert a feature, I'm pretty sure that you'd like to revert only one commit (same for cherry-picks)... If you look at the commits of a merge request, it's much more easier to understand how (the steps) the feature has been developed if it's not polluated by unnecessary commits... If you want to find the source of a bug, it's much more easier if the commit contains all the changes done related to this bug rather than looking through multiple small commits.
#Merging vs rebasing
When it comes to merging 1 branch in another, you have 2 common solutions : git merge
or git rebase
. I'll try to explain you what are the main differences and how to use them.
First of all, let's say that we are in the following situation : we have a master
branch which is public and we've created a feature
branch from it (the common commits are in white on the image). On your feature branch you've added 3 new commits (in green). And the master branch now contains 2 new commits (in blue). We are then in a classic situation where your branch is no more up-to-date with master but you need those 2 new commits from master in your feature branch...
#git merge
With the merge option, you just have to use one of the followings:
git checkout feature
git merge master
or
git merge feature master
It will merge the master branch into your feature branch by creating a new commit in your feature branch (green circle with a star in it).
So far, everything's fine 😀... if you don't care about your git history. Indeed, let's imagine that the master branch regularly receives new commits and you need them: you'll need to do multiple merges that will create one new commit for each merge... Moreover, your commits on the feature branch can be splitted and kind of "lost" between commits from master. It can quickly become a bit messy.
#git rebase
Note: it is highly recommanded to rebase only on private branches. If you share your branch with someone else (ie: a public branch), you should avoid rebasing as it may be confusing and a bit risky or do it really carefully and warn your team mates.
Indeed, rebasing will rewrite the git history by changing the base of your branch: it will start on the tip of the master branch (or the branch you're rebasing onto). As you can see in the following image, our branch is now starting after the 2 new commits of master and our 3 commits have been rewritten but no merge commit have been created !
Rebasing command is as simple as the merge one :
git checkout feature
git rebase master
After runnig it, all your commits will be moved one by one. It means that each one of them can create a conflict that you'll need to solve. If there are conflicts, the rebasing is paused so that you can fix them. When they are solved, you just have to add your changes and run git rebase --continue
.
To push your changes, as you've changed the git history (which is not a trivial operation), you have to force push by running git push --force
or git push -f
.
#Local cleanup
Please note that those changes should be done only if your branch isn't public: you're the only developer working on it. If this is not the case, it should be done really carefully as you (or your team mates) may lose some work...
#Changing your last commit (amend)
Git allows you to easily change your last commit. It can be either the message or the commit of even its content. This is useful when you notice that you've done a small error somewhere, you've handled some code review feedbacks, you forgot to add some files, etc. You can do it thanks to the following command:
git commit --amend
By running it, the editor will open and you'll be able to edit your last commit message. If you want to change the content of your previous commit, you just have to add / remove the files that you want to track before running the amend command.
This command accepts some extra options that may be useful. For example you can change directly your last commit message by doing this:
git commit --amend -m "My changed commit message"
And on the other side, if you know that you won't change your commit message, you can run:
git commit --amend --no-edit
By doing it, you've rewritten the git history. If you try to push it, git will prevent you from doing so because it conflicts with remote branch. You have to add the force option either by doing git push --force
or git push -f
.
#Rebase (squash) your commits
To change the history of one of your private branch, you can rebase your own branch; to squash, edit, change the order, remove, reword your commits. And good news, you can do it in an interactive way !
You just have to run one of the following : git rebase -i <hash>
(hash = the hash of the commit you want to rebase onto) or git rebase -i HEAD~<X>
(X = the number of commits you want to rebase).
By doing so, your text editor will show you something similar to:
pick 58cd2a3 Create feature 1
pick 3362add Create feature 2
pick ddab253 Fix typo of feature 1
# Rebase 390ffba..ddab253 onto 390ffba (3 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
If you want to change a commit, you have to replace its "pick" prefix word by one of the suggested commands. In our example, we can see that we've done 3 commits. 2 of them are related to a feature 1 (#58cd2a3 & #ddab253) and the other is related to feature 2 (#3362add). The problem here is that we weren't able to amend our first commit to fix a typo in it as there was a commit related to feature 2 in-between.
So the idea here could be to change the order of the commits and merge (squash) our typo commit into the commit of feature 1. In your editor, you can edit it to something like :
pick 58cd2a3 Create feature 1
squash ddab253 Fix typo of feature 1
pick 3362add Create feature 2
Once saved, the rebase will start and you may face some conflicts like you do in regular rebasing. When the rebasing is finished, you'll be prompt to write the new commit message for the commits you've squashed.
Here again, as you've changed the git history, you have to force push your changes thanks to git push --force
or git push -f
.
Our example is pretty simple but if needed, you can do multiple squashes at the same time.
#🎁 Bonus: git rerere
As you may have noticed, rebasing can be a bit tedious as you can face a lot of conflicts (if you don't rebase regularly) and those conflicts can be repetitive... To help you resolving those recuring conflicts, you can use the rerere functionality of git. rerere stands for Reuse Recorded Resolution.
By default, it's deactivated in git and you can enable it by running :
git config --global rerere.enabled true
And... that's it ! When you'll face a conflict, git will take a snapshot of this conflict and save also your resolution. So the next time you'll face it again, git will be able to resolve it automatically. It'll give you something like this in your console :
CONFLICT (content): Merge conflict in index.html
Resolved 'index.html' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
As you can see, git automatically resolved the conflict in index.html
but this file is not automatically staged: you have to do a git add index.html
... If you prefer, you can tell git to auto-stage files it solved by updating the configuration :
git config --global rerere.autoupdate true
Now when you'll face an already resolved conflict, git will auto-solve and auto-stage it. That said, you'll still need to commit and push them 😉.
#Learn more and inspirations
- Merging vs rebasing
- Rewriting history
- Always squash and rebase your git commits
- Beginner's guide to git rebasing and squashing
- Why You Should Care About Squash and Merge in Git
- Git Tools - Rerere
- Fix conflicts only once with git rerere
🙌 I would like to thanks Valentin Letourneur and Boris Guéry for their help on this article !
If you liked this article, please share it on Twitter.
Victor Gosse
@VictorGosseI work at Attineos since 2015 as a front-end developer. This blog is mine and I'll be very happy to discuss about it with you in DM on Twitter.