Git Essentials - Part 2
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 aliasgit br
: display the branches in this repositorygit checkout
orgit co $BRANCH
: jump on a branch identified by $BRANCH (for examplegit 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 examplegit 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
intodev
) is a fast-forward merge that does not create any new commit sincedev
points to a commit child of the one pointed bymaster
; in this case Git simply updates the pointermaster
making it point to the same commit ofdev
(it moves the pointermaster
forward) - the second merge tries to mix the status of the project in
DiUCqAU
withbn2xziE
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 onlytest
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 ofmaster
: 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 byprod
) andbn2xziE
(pointed bymaster
) and it findsf62b56e
- then creates the list of commits from
f62b56e
toAXqCLP0
(f62b56e -> DiUCqAU -> AXqCLP0
) and tries to apply them on top ofbn2xziE
(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
andJ6R1usC
are created and the pointerprod
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 |