Getting more from git
This post is based on an internal workshop I delivered at a previous job, where we used a modified version of gitflow. This post will contain faint reflections of that strategy, and although I’d recommend reading up on gitflow it’s not necessary.
Rolling back
There are different ways to go back in time in git depending on what you want to achieve.
Undoing changes in the working directory
The working directory is the files and folders that make up the current (checked out) branch of the git repository. When a file is opened, edited then saved it is a change in the working directory.
If you save a file then realise it won’t work and want to get back to the previously committed version, check it out.
git checkout index.html
If you change a few files and want them all changed back take advantage of the command line syntax for the current folder.
git checkout .
Removing files from the staging area
The staging area is a list of changes to files that are ready to be committed to the Git directory. After saving a file it’s added to the staging area with the add
command.
git add index.html
To remove the changes from the staging area and leave them in the working directory i.e. the state before they were added, use the reset
command.
git reset HEAD index.html
It’s simplest to think of HEAD as the latest commit in current branch. This command is saying, “Reset index.html to the state it was in at the last commit of the current branch, but don’t touch the working directory.”
The reason it doesn’t affect the working directory is due to the missing default --mixed
flag, which leaves the working directory alone.
Again, all staged files can be removed from staging with the dot.
git reset HEAD .
Undoing the last commit
To get rid of the last commit use the revert
command.
git revert HEAD
This puts everything back to the state it was in at the previous commit. Nothing is staged and there are no changes in the working directory.
It does create a commit though, so when you run git log
there will be a revert commit. That means it’s safe to do on shared branches as no information is lost.
Undoing any commit
Sometimes a client asks us to add or remove a feature temporarily. The latest example was a client running a Kickstarter campaign and who wanted the Stripe checkout on his site disabled until the campaign had ended.
When the time came to turn it on again I was able to use a single command in git to do so. All I needed was the hash of the commit where it was added to the site in the first place.
git revert 7168ae9
Where 7168ae9 is the first few characters in the commit hash. Again this creates a commit and is safe to do on shared branches.
Instead of a commit hash you can also go relative to HEAD
. This command will revert the 3rd last commit:
git revert HEAD~3
Removing commits altogether
As explained above reset
removes files from the staging area when it’s given a path.
git reset HEAD index.html
However when it’s given a commit reference it removes all commits after the reference. If you’re working in a feature or hotfix branch there’s probably no need to keep unwanted changes in history by using revert
, so reset
is the perfect tool for the job.
If you’re 5 commits into a feature and things just aren’t working with what you’ve been trying since the 3rd commit it’s easy to go back.
git reset HEAD~3
Remember the default --mixed
flag means the working directory isn’t affected when reset
is used on a file? The same happens here. In this example the changes from the 3rd last commit are now in the working directory.
If you want rid of them completely use the --hard
flag.
git reset --hard HEAD~3
Again, a hash can be used if necessary.
git reset --hard a253c6b
The commits that have gone are totally removed at the next garbage collection.
Creating a branch from a previous commit
The standard command for creating a new branch is git checkout -b new_branch_name
. It creates a new branch from where you are at that moment in time.
However there are times when you’re not sure if you’re on the right track, or you just want to try a couple of different ways to develop a feature.
In this case you can create a branch from a previous commit just by adding a commit reference to the standard command.
git checkout -b new_branch_name HEAD~2
git checkout -b new_branch_name 983aaae
Adding and committing at once
This is just a handy time saver a lot of people don’t seem to know about.
A common scenario is something like working on a feature and remembering there’s an unrelated bug that needs squashed somewhere else.
The git commands to sort that would be something like:
git add feature.html feature.js
git commit -m "Add new feature"
git add bugfixed.css
git commit -m "Bug fixed"
And that’s great, it’s good to have that control over modified files. However a much more common scenario (for me anyway) is to make a bunch of changes and commit them all together.
git add .
git commit -m "Add new feature"
These commands can be combined just by adding the a
flag in the commit message and is a huge timesaver.
git commit -am "Add new feature"
Note this adds and commits modified files in one go, it doesn’t add untracked (new) files. They still need the add
command.
Rebasing
Rebasing (using our branching model) is very handy when you want to bring a feature branch up to date with a shared branch without merging.
I often find our designer pushes code to the dev branch that affects what I’m working on. Using merging I would have to merge at an unnatural point to get the design updates, creating a meaningless merge commit.
Rebasing means moving a local branch to the tip of the shared branch (dev in our case) so that it looks like it was checked out from the tip.
It’s a clean way to get everything on the shared branch into your branch.
git ck feature-branch
git rebase dev
Cherry-picking
Cherry-picking is used to get individual commits from other branches into the current branch.
For example a designer pushes 10 commits to the dev branch but a developer only needs the 3rd and 8th. A rebase will get them all (as will a merge) so a cherry-pick is required.
git cherry-pick 638af21 feed155
git cherry-pick dev~2 dev~7
Conclusion
The best way to get the most from tools is to learn how to use them well. Git is one of the most important tools for developers and we should take the time to learn how it works and how it can be a powerful ally when making websites.
A coherent branching strategy is scratching the surface and the commands summarised in this post are scratching a different part of the surface, so take the time to learn more commands, their flags and how git works in as much detail as possible.
It will be time very well spent.