Table of Contents

Introduction

In the first article you should have learned how to use Git to create commits, check the status of the project, check the graph of the commits. We started with the basic configuration and the definition of very useful aliases. In particular do not attempt to read this part if you are not familiar with the command git lo and its output.

Branches

We discussed in the previous article the meaning of:

$ git lo
* 39cd015 (HEAD -> master) English dictionary - day 3
* 26ea295 English dictionary - day 2
* f62b56e English dictionary - day 1

The only part that was not fully explained is HEAD -> master because we can forget about it (it is not relevant) when working with a single branch.

So what is a branch in Git?

It is simply a pointer to a commit. And HEAD is the pointer to the active branch (master in the case of the example above). Let’s introduce a couple of more commands:

  • git branch or the alias git br: display the branches in this repository
  • git checkout or git co $BRANCH: jump on a branch identified by $BRANCH (for example git co dev switch to the commit pointed by the branch called dev).
  • git co -b $NEW_BRANCH: create a new branch called $NEW_BRANCH from the current commit and switch to it (for example git co -b dev create a new branch called dev that points to the current commit)

To understand better let’s see in practice:

$ git lo
* 39cd015 (HEAD -> master) English dictionary - day 3
* 26ea295 English dictionary - day 2
* f62b56e English dictionary - day 1

$ git br
master

$ git co -b dev
$ git lo
* 39cd015 (HEAD -> dev, master) English dictionary - day 3
* 26ea295 English dictionary - day 2
* f62b56e English dictionary - day 1

$ git br
dev
master

$ git co f62b56e
$ git lo
* 39cd015 (dev, master) English dictionary - day 3
* 26ea295 English dictionary - day 2
* f62b56e (HEAD) English dictionary - day 1

$ git br
dev
master

$ git co -b test
$ git lo
* 39cd015 (dev, master) English dictionary - day 3
* 26ea295 English dictionary - day 2
* f62b56e (HEAD -> test) English dictionary - day 1

$ git br
dev
master
test

In the example above we started with a Git repository with a single branch called master. This branch points to the latest commit (which can be thought as a snapshot of the project). Then we created a new branch called dev from the current commit. Now we have two branches pointing to the same commit. To demonstrate it’s the commit that hold the data (in contrast to the branch), we were able to checkout the first commit of our project using its hexadecimal identifier f62b56e. We can see HEAD is moved at the bottom of the graph. If you check the content of the text file in your git folder, it is reverted to the first commit. We then created a new branch called test that points to the current commit. If you want to get back to the latest version of your project, just run git co master (or git co dev since they both point to the most recent commit).

Sounds cool? It’s not finished here.

We are going to add a new test file and a commit while we are on branch test and then we will change the content of eng.txt from the branch dev and see how the graph of commits evolves:

# better check the current status of the graph before beginning 
$ git lo
* 39cd015 (dev, master) English dictionary - day 3
* 26ea295 English dictionary - day 2
* f62b56e (HEAD -> test) English dictionary - day 1

# create a new file called "test.txt"

$ git st
?? test.txt

$ git add test.txt
$ git commit -m "Add configuration file for tests"
$ git lo
* DiUCqAU (HEAD -> test) Add configuration file for tests
| * 39cd015 (dev, master) English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

$ git co dev
$ git lo
* DiUCqAU (test) Add configuration file for tests
| * 39cd015 (HEAD -> dev, master) English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

# change the content of eng.txt

$ git st
M  eng.txt

$ git add eng.txt
$ git st
 M eng.txt

$ git commit -m "English dictionary - day 4"
$ git lo
* bn2xziE (HEAD -> dev) English dictionary - day 4
* 39cd015 (master) English dictionary - day 3
* 26ea295 English dictionary - day 2
| * DiUCqAU (test) Add configuration file for tests
|/
* f62b56e English dictionary - day 1

As you can see the graph now looks more like a graph. Notice how the most recent commit is always on top and remember there is a hidden line implied between consecutive commits. If I had to expand the last graph it would be:

* bn2xziE (HEAD -> dev) English dictionary - day 4
|
* 39cd015 (master) English dictionary - day 3
|
* 26ea295 English dictionary - day 2
|
| * DiUCqAU (test) Add configuration file for tests
|/
* f62b56e English dictionary - day 1

The commit DiUCqAU is a child of f62b56e. Also 26ea295 is a child of the same and since they are separate commits we see the two lines in the graph connecting f62b56e -> DiUCqAU and f62b56e -> 26ea295. If you are not accustomed to seeing stuff in the terminal, take few moments to look at the graph and imagine each asterisk as a dot and the lines between them.

The last concepts of today are merge and rebase. They both answer the question: I have two commits pointed by two different branches and I want to merge the content of the two. Yes, you heard it correctly: also rebase achieve the same goal of merging two versions of the project:

$ git lo
* bn2xziE (HEAD -> dev) English dictionary - day 4
* 39cd015 (master) English dictionary - day 3
* 26ea295 English dictionary - day 2
| * DiUCqAU (test) Add configuration file for tests
|/
* f62b56e English dictionary - day 1

$ git co test
$ git co -b prod
$ git lo
* bn2xziE (dev) English dictionary - day 4
* 39cd015 (master) English dictionary - day 3
* 26ea295 English dictionary - day 2
| * DiUCqAU (HEAD -> prod, test) Add configuration file for tests
|/
* f62b56e English dictionary - day 1

# create a new file called prod.txt

$ git st
?? prod.txt

# the following command adds all the untracked and modified files
$ git add .

$ git commit -m "Add production configuration"
$ git lo
* AXqCLP0 (HEAD -> prod) Add production configuration
* DiUCqAU (test) Add configuration file for tests
| * bn2xziE (dev) English dictionary - day 4
| * 39cd015 (master) English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

Take a moment to familiarize yourself with the last graph. Notice the most recent commit is now on top and the layout of the representation of the graph switched between the second last and the last execution of git lo.

$ git co master
$ git lo
* AXqCLP0 (prod) Add production configuration
* DiUCqAU (test) Add configuration file for tests
| * bn2xziE (dev) English dictionary - day 4
| * 39cd015 (HEAD -> master) English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

$ git merge dev
fast-forward merge

$ git lo
* AXqCLP0 (prod) Add production configuration
* DiUCqAU (test) Add configuration file for tests
| * bn2xziE (HEAD -> master, dev) English dictionary - day 4
| * 39cd015 English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

$ git co test
$ git lo
* AXqCLP0 (prod) Add production configuration
* DiUCqAU (HEAD -> test) Add configuration file for tests
| * bn2xziE (master, dev) English dictionary - day 4
| * 39cd015 English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

$ git merge master -m "Merge master into test"
*   c5f8ce1 (HEAD -> test) Merge master into test
|\  
| * bn2xziE (master, dev) English dictionary - day 4
| * 39cd015 English dictionary - day 3
| * 26ea295 English dictionary - day 2
| | * AXqCLP0 (prod) Add production configuration
| |/  
|/|   
* | DiUCqAU Add configuration file for tests
|/  
* f62b56e English dictionary - day 1

$ git co prod
$ git rebase master
* J6R1usC (HEAD -> prod) Add production configuration
* 1FQqe2r Add configuration file for tests
| *   c5f8ce1 (test) Merge master into test
| |\  
| |/ 
|/|  
* | bn2xziE (master, dev) English dictionary - day 4
* | 39cd015 English dictionary - day 3
* | 26ea295 English dictionary - day 2
| | 
| |  
| * DiUCqAU Add configuration file for tests
|/  
* f62b56e English dictionary - day 1

The sequence might not be evident at first, but take your time to review the example and check how the graph changes during our merge and rebase. Note the following:

  • the first merge (master into dev) is a fast-forward merge that does not create any new commit since dev points to a commit child of the one pointed by master; in this case Git simply updates the pointer master making it point to the same commit of dev (it moves the pointer master forward)
  • the second merge tries to mix the status of the project in DiUCqAU with bn2xziE and since these two commits are not one child of the other, there is no way to do it unless a new commit gets created: c5f8ce1; notice we used the parameter “-m” to pass the new commit message and only test is moved to point to the new commit
  • now the graph is a little bit more articulated, but inspect asterisks and lines to see that there is no changes in the relationships of the commits in the graph before the merge and now
  • let’s proceed and rebase prod on top of master: keep in mind that each commit saves the changes in the whole project in respect to the previous commit (its father)
  • therefore to perform a rebase, Git checks which one is the common parent of AXqCLP0 (pointed by prod) and bn2xziE (pointed by master) and it finds f62b56e
  • then creates the list of commits from f62b56e to AXqCLP0 (f62b56e -> DiUCqAU -> AXqCLP0) and tries to apply them on top of bn2xziE (except for the first one which is already a common ancestor)
  • since the operation applying the changes in a commit means creating a new commit, the hexadecimal value of what we rebase changes
  • we are eventually in the situation of the last graph where the new commits 1FQqe2r and J6R1usC are created and the pointer prod is now on the most recent commit.

Last but not least, notice that DiUCqAU is still there because it is part of the history of c5f8ce1, while AXqCLP0 has disappeared from the graph. This is because when we rebase a branch it’s like cutting a row of asterisks and attaching them on top of another one. You can recreate the situation before the first merge and rebase prod on top of dev to confirm what we just explained:

$ git lo
* AXqCLP0 (HEAD -> prod) Add production configuration
* DiUCqAU (test) Add configuration file for tests
| * bn2xziE (dev) English dictionary - day 4
| * 39cd015 (master) English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

# delete the branch test
$ git br -d test
$ git lo
* AXqCLP0 (HEAD -> prod) Add production configuration
* DiUCqAU Add configuration file for tests
| * bn2xziE (dev) English dictionary - day 4
| * 39cd015 (master) English dictionary - day 3
| * 26ea295 English dictionary - day 2
|/
* f62b56e English dictionary - day 1

$ git rebase dev
* 8yo2Zgg (HEAD -> prod) Add production configuration
* IuHiA86 Add configuration file for tests
* bn2xziE (dev) English dictionary - day 4
* 39cd015 (master) English dictionary - day 3
* 26ea295 English dictionary - day 2
* f62b56e English dictionary - day 1

We introduced the command git br -d $BRANCH to delete a branch and we see the main advantage of using rebase over merge: the final history of the repository is linear.

Conclusion

In this article we introduced the concept of branches and the operations to view, create, switch and merge different commits. Keep in mind that when we speak we say for brevity something like “rebase branch X on top of Y” when we should say “rebase the commit pointed by X and its history on top of the commit pointed by Y and move the pointer X on the most recent commit”.

In the next article we will talk about conflicts and how to resolve them, remote repositories and how to manage remote branches.

Cheatsheet

Command Explanation
git checkout $BRANCH switch to $BRANCH
git checkout -b $NEW_BRANCH create a new branch called $NEW_BRANCH
git merge $BRANCH merge the current branch and $BRANCH when a fast-forward merge is expected
git merge $BRANCH -m $MESSAGE merge the current branch and $BRANCH in a 3-way merge (when a new commit is expected) using $MESSAGE as the message for the new commit
git rebase $BRANCH rebase the current branch on top of $BRANCH
git branch show the branches in this repository
git branch -d $BRANCH delete the branch called $BRANCH
git add . add all untracked files and modified files from the current directory and subdirectories