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.