Git magic revealed — Part 1

Yash Ladia
5 min readOct 28, 2016

--

I won’t start here with the history of how Git developed or tell you what a distributed VCS is. I am assuming that you use git and know the basics of it.

Staging and Snapshots

We all use git add and git commit to save any changes. First we make changes in the working directory. When we are happy with the current state, we stage our changes with git add. Then these snapshots can be committed to our local repo using git commit.

Understanding refs and commit hashes

So when we have committed the changes that commit can be identified by a SHA1 hash( looks like 0c708fdec272bc4446c6cabea4f0022c2b616eba ). As you can see remembering this hash is not a good idea. Right? :) So here comes refs. Refs are indirect way of referring to a commit. I would highly recommend to ‘cd’ into your .git folder and see how git stores heads, stashes, remotes and other files. I bet a lot of the git magic would be revealed to you.

refs — folder structure

The heads directory defines all of the local branches in your repository. Each filename matches the name of the corresponding branch, and inside the file you’ll find a commit hash. This commit hash is the location of the tip of the branch. To verify this, try running the following two commands from the root of the Git repository:

# Output the contents of `refs/heads/master` file:
cat .git/refs/heads/master
# Inspect the commit at the tip of the `master` branch:
git log -1 master

The commit hash return by the cat command should match the commit ID displayed by git log.

Merging vs Rebasing

Both of these commands are designed to integrate changes from one branch into another branch. Let us see the difference of how exactly they work with the help of following example

commit history

This often happens that we checked out a feature branch from the master. Another feature branch might have been merged into the master due to which it has gone forward by 2 commits as seen in the above illustration. Now, let’s say that the new commits in master are relevant to the feature that you’re working on. To incorporate the new commits into your feature branch, you have two options: merging or rebasing.

git merge

git merge master feature

This creates a new “merge commit” in the feature branch that ties together the histories of both branches, giving you a branch structure that looks like this:

Merging is nice because it’s a non-destructive operation. The existing branches are not changed in any way, but the downside is that we get an extra commit.

git rebase

As an alternative to merging, you can rebase the feature branch onto master branch using the following commands:

git checkout feature
git rebase master

This moves the entire feature branch to begin on the tip of the master branch, effectively incorporating all of the new commits in master. But, instead of using a merge commit, rebasing re-writes the project history by creating brand new commits for each commit in the original branch.

The major benefit of rebasing is that you get a much cleaner project history. First, it eliminates the unnecessary merge commits required by git merge. Second, as you can see in the above diagram, rebasing also results in a perfectly linear project history—you can follow the tip of feature all the way to the beginning of the project without any forks. This makes it easier to navigate your project with commands like git log, git bisect, and gitk.

Squashing commits

Sometimes, we make a lot of commits and some of them are not necessary. There are 2 tips I can share which would avoid such a problem and also hide it from your code reviewer :D

  1. Interactive git rebasing

git rebase -i master

You would see something like this:

pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

There are options such as pick, fixup, squash and other options. So what I generally do is replace pick with squash in those commits which I don’t want to show in my git log or commit history. Save the file and no unnecessary commits now

2. git commit with amend option

This would add your changes to the last commit and a new commit won’t be created. Can’t really tell you how much useful it is.

Relative refs with ~ and ^

These are a way of referring commits relative to another commit.

The ~ character will always follow the first parent of a merge commit. If you want to follow a different parent, you need to specify which one with the ^ character. For example, if HEAD is a merge commit, the following returns the second parent of HEAD. To clarify how ~ and ^ work, the following figure shows you how to reach any commit from A using relative references. In some cases, there are multiple ways to reach a commit.

the small minus is basically ~

There is a lot lot more

A lot of the content here is easily available on the internet. But there is so much in Git that sometimes you don’t know where to start. I have just picked what I found was useful. Thanks for reading. Would come up with the next part soon :)

--

--