University – Git like a pro – The basics

The aim of this article is to help you master git and make it do exactly what you want it to do! This article is dedicated to the very basics of git. At the end of this article, you will be able to see your commit tree, commit, push and pull, all of these using the command line of course.

Why should you learn how to use the command line when there are some nice GUI tools? Well, git is very powerful and most of the GUI tools only allow you to access the basics. But do not worry, there are times where you should be using such tools, like when doing diff while resolving conflicts. This article will not teach you more than you can achieve using a GUI tool, but this is a necessary step before you can understand the git advanced features described in the second part (coming soon).

How to see pretty git logs in the console: git lola

Let’s start by introducing git lola. This is an alias that allows you to see the whole (i.e. all branches) commits tree of the project. To get this alias, all you need to do is to paste the following lines in your ~/.gitconfig file. Credit goes to Scott Chacon for this alias.

[alias]
        lol = log --graph --decorate --pretty=oneline --abbrev-commit
        lola = log --graph --decorate --pretty=oneline --abbrev-commit --all
[color]
        branch = auto
        diff = auto
        interactive = auto
        status = auto

You see that you also get git lol which show the commits tree of the current branch.

Let’s see what it really looks like with an example, first git lol:

* 11ebadc (HEAD -> master, origin/master, origin/HEAD) Update README.md
* cbb64b2 Added cache & logging
* 6e1b4f2 (origin/blog-back) Transaction added, more service methods implemented + Refactoring
* c6761c2 Added blog project skeleton
* 9b657ab Improved web architecture, implemented angular.js
* 9a6f4fd Added basic grunt config for blog website
* a8f3a6f Initial commit

Each line is a commit with its associated hash. As you can see, you have some additional informations next to some commits. For instance: origin/blog-back, origin/master. Everything starting with origin/ (and showing red in your console) indicates that it is from the remote. Indeed, origin is just an alias that points to your remote repository. You can check this by running the git remote -v command. The name following the origin/ is the name of the branch.

The names not starting with origin/ are the branches we have checked out locally (they are written in green in your console) and HEAD -> shows where we currently are. It tells us we are on the master branch, this is the default branch.

Wait, I thought git lol only shows the current branch but I can also see the origin/blog-back branch. Well this is because this branch was merged in the commit “6e1b4f2 Transaction added, more service methods implemented + Refactoring”. After merging a feature, it is a good practice to remove the branch. This was not done here, so we are seeing this branch as the commit is shared between branch master and blog-back.

Now let’s see the difference with git lola:

* 11ebadc (HEAD -> master, origin/master, origin/HEAD) Update README.md
| * 055fa06 (origin/feature/restApi) Added security with basic auth.
| * 1d91794 Cleaned up gradle files.
| * 0778d5f Added gradlew support
| * 7c99c4b Added flyway to the project
| * 2f0f85b Added test on PostController
| * 9390604 Refactoring + Better handling of null values + Extracted services to interfaces
| * 9c8de47 Fixed file names
| * d6ce35d Created services, controllers, refactored Dtos, removed final keyword on domain UUIDs, FIXME: UUID retrieval (probably a mistake in type handler)
| * 91793fb Gradlized project
|/  
* cbb64b2 Added cache & logging
* 6e1b4f2 (origin/blog-back) Transaction added, more service methods implemented + Refactoring
* c6761c2 Added blog project skeleton
* 9b657ab Improved web architecture, implemented angular.js
* 9a6f4fd Added basic grunt config for blog website
* a8f3a6f Initial commit

Wow, this seems more complicated with the tree splitting in two lines. What it tells us is that another branch name feature/restApi exists and it is starting from the “cbb64b2 Added cache & logging” commit.

Now that you understand the log command, you can see your commit trees in the console as clearly as you would in a GUI tool! Awesome, let’s see how to get some pretty nice informations from git.

When you get lost: git status

The next command you need to know is git status. When you forget what you are doing, just type this command in your console. It will output something like this:

On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

We have a confirmation of the branch we currently are on and we are up to date with the remote server. Well nothing really interesting actually, we already knew all of these from our git lola command. Let’s work a bit on our project and try again to run the git status command.

On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   build.gradle
	modified:   persistence/build.gradle
	modified:   service/build.gradle
	modified:   webservice/build.gradle

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	webservice/src/main/java/org/resourcepool/security/

no changes added to commit (use "git add" and/or "git commit -a")

Now that is something helpful! You can see, git explains you the available options and it even gives you the associated command!

Let’s focus on the first part, as you can see, I have modified 4 build.gradle files. This means, these files were known by git from previous commits and now, they have changed. Git also lets me know that they are not staged for commit. Commits in git are a two steps operation. First you need to add the file you want to commit. Then you do the actual commit.

I can see the command I need to use to add files to my commit:

git add build.gradle

I want to know what has changed, so I run git status again:

On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   build.gradle

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   persistence/build.gradle
	modified:   service/build.gradle
	modified:   webservice/build.gradle

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	webservice/src/main/java/org/resourcepool/security/

As you can see the build.gradle file is now in a different category Changes to be committed. This means that if I commit right now, only this file will be saved. Git also lets me know that I can type git reset HEAD build.gradle to unstage my file. If I do so, git status will output the exact same thing before I ran my git add command.

There is also an Untracked files category. In this category, you will find files that are present in your directory but git does not know what to do about them. Do you want to add them to your repository? Do you want to ignore them? It has no idea. Well I do not know why this directory is here either but I need to commit the build.gradle files I have modified.

git add -u .

This command stages all the files known by git for commit and ignores the untracked files (the -u stands for -update):

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   build.gradle
	modified:   persistence/build.gradle
	modified:   service/build.gradle
	modified:   webservice/build.gradle

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	webservice/src/main/java/org/resourcepool/security/

If I wanted git to stage everything for commit, event the untracked files, I should have used the same command without the -u: git add .

I’m ready for my first commit: git commit

We have added all the files we want to commit, so let’s commit:

git commit -m "Clean up build.gradle files"

Let’s run git lol to be sure our commit is there:

* aa89b14 (HEAD -> feature/restApi) Clean up build.gradle files
* 0778d5f Added gradlew support
* 7c99c4b Added flyway to the project
* 2f0f85b Added test on PostController
* 9390604 Refactoring + Better handling of null values + Extracted services to interfaces
* 9c8de47 Fixed file names
* d6ce35d Created services, controllers, refactored Dtos, removed final keyword on domain UUIDs, FIXME: UUID retrieval (probably a mistake in type handler)
* 91793fb Gradlized project
* cbb64b2 Added cache & logging
* 6e1b4f2 (origin/blog-back) Transaction added, more service methods implemented + Refactoring
* c6761c2 Added blog project skeleton
* 9b657ab Improved web architecture, implemented angular.js
* 9a6f4fd Added basic grunt config for blog website
* a8f3a6f Initial commit

Alright! Now you’re all happy, you go tell your colleagues that they can get all the awesome clean you’ve made in those build.gradle files. But they will soon tell you they can’t see your commit! This is because in git, commits are made locally. We will learn a bit later how to share your commits to the team.

Oh by the way, remember when I said Commits are a two steps operation? Well for such a simple case, we could just have run one command:

git commit -a -m "Clean up build.gradle files"

Notice the new -a option, this is what tells git to perform a add before committing. Take extra note, this is the equivalent of our git add -u .: it will ignore all untracked files. -a is the short version of –all and -m of –message.

Before going further, we need to understand what exactly is happening when we do an add command. The add command tells git it needs to track this file. But git will also take a snapshot of the file and stage it for your next commit. This is very important to understand that git snapshots the content of your files during the add command and not during the commit.

Let me show you an example. I will create a new test file and write “I’m a git expert now!” in it. I add my file to git with the git add command.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   test

Now I realize I went a bit fast, so I edit my file and replace the text with “I will soon be a git expert!”. I commit, share the file to my colleagues and now everyone is talking about me being a git expert… Although I wrote “soon”. This is because they can only see the “I’m a git expert now!”. If I had run a git status before my commit, I would have seen this:

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   test

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   test

It tells me, that there is this new “test” file that git needs to track and use in the next commit. But this file has actually been modified since and the changes are not staged for commit! I need to add this file again to be sure to have the latest changes in my commit (or use the -a option while performing my commit).

In git, the add command creates a graph object. The commit command is very fast as it just gives a name (the hash) to the graph.

Oops I made a mistake, I need to rollback: git checkout —

If you look at the previous git status, git tells me I can use the

git checkout -- test

To discard all changes I made. Doing so will revert the file back to the state it was in the last commit (or the last add if we had done any). After this command my test file contains “I’m a git expert now!” and my “I will soon be a git expert!” is lost for good, there is NO WAY I can get it back… except if I type it all over again of course.

As we will see in part 2, the checkout command is used to switch to another branch. The double-dash is here to prevent the command from looking for a “test” branch.

Okay, so far we know how to look at our commit tree (git lola), how to see our current state (git status), how to add a file to our next commit (git add), how to remove a file from our next commit (git reset HEAD), how to discard all changes made to a file (git checkout –) and finally how to do the actuel commit (git commit).

Time to share our work to the world! Or at least to our team: git push

Pushing is really easy, all you have to do is give git the address of your repository and the branches you wish to push:

git push https://github.com/ydemartino/resourcepool.git master feature/restApi

Here I pushed two branches: master and feature/restApi to the repository https://github.com/ydemartino/resourcepool.git. I thought pushing was easy but this is a really long command for such a simple task… Your repository will usually never change, so a shortcut exist when you want to push to the same repository you cloned the project from:

git push origin master feature/restApi

In a perfect world or if you are working alone, this will always work. But look at what happened when I tried to git push origin master:

To https://github.com/ydemartino/resourcepool.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'https://github.com/ydemartino/resourcepool.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Don’t panic, it just tells me that someone has been working on the same branch as me and I need to first get the changes they made before pushing.

Always be up to date: git pull

Git nice as always tells me I should try to use git pull before pushing. Well it so happens that git pull is a two step operation, sounds familiar? No, no, this time it really is executing two commands:

git fetch && git merge FETCH_HEAD

To understand git pull, we will use the two command separately, first:

$ git fetch
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/ydemartino/resourcepool.git
   11ebadc..59c57ef  master     -> origin/master

We have downloaded some data from the repository to see what let’s run a git lol:

* cc7f6de (HEAD -> master) Awesome feature!
| * 59c57ef (origin/master, origin/HEAD) Merge restApi
|/  
* cbb64b2 Added cache & logging
* 6e1b4f2 (origin/blog-back) Transaction added, more service methods implemented + Refactoring
* c6761c2 Added blog project skeleton
* 9b657ab Improved web architecture, implemented angular.js
* 9a6f4fd Added basic grunt config for blog website
* a8f3a6f Initial commit

As you can see we have a little problem here… The last commit of our remote branch origin/master is “59c57ef (origin/master, origin/HEAD) Merge restApi” and it happened after “cbb64b2 Added cache & logging”. But after this last commit I did mine: “cc7f6de (HEAD -> master) Awesome feature!”. Two commits have been made at the exact same place in the tree, git does not know how to handle this.

Let’s use the other part of the git pull:

git merge FETCH_HEAD

As soon as I use this command, git will open a text editor with the following content:

Merge branch 'master' of https://github.com/ydemartino/resourcepool.git
  
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

The only uncommented line is the message of my commit and git wrote by default what I was doing: “Merge branch ‘master’ of https://github.com/ydemartino/resourcepool.git”. I replace the message with “I just wanted to push…” and save the file.

Auto-merging README.md
Merge made by the 'recursive' strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)

I changed some different part of the README.md file so git was able to auto-merge the file without any conflict. I try to push again:

$ git push origin master
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 607 bytes | 607.00 KiB/s, done.
Total 6 (delta 4), reused 0 (delta 0)
To https://github.com/ydemartino/resourcepool.git
   59c57ef..7ccf5c0  master -> master

Pheeeew, it finally work! I can know admire my commit with a git lol:

*   7ccf5c0 (HEAD -> master, origin/master, origin/HEAD) I just wanted to push
|\  
| * 59c57ef Merge restApi
* | cc7f6de Awesome feature!
|/  
* 11ebadc Update README.md
* cbb64b2 Added cache & logging
* 6e1b4f2 (origin/blog-back) Transaction added, more service methods implemented + Refactoring
* c6761c2 Added blog project skeleton
* 9b657ab Improved web architecture, implemented angular.js
* 9a6f4fd Added basic grunt config for blog website
* a8f3a6f Initial commit

Wait what is the ugly hard to understand tree? This is not what we wanted! We wanted to get a nice looking linear tree. Something that should have looked like this:

* cc7f6de (HEAD -> master, origin/master, origin/HEAD) Awesome feature!
* 59c57ef Merge restApi
* 11ebadc Update README.md
* cbb64b2 Added cache & logging
* 6e1b4f2 (origin/blog-back) Transaction added, more service methods implemented + Refactoring
* c6761c2 Added blog project skeleton
* 9b657ab Improved web architecture, implemented angular.js
* 9a6f4fd Added basic grunt config for blog website
* a8f3a6f Initial commit

Well we will get there, but in the second part! For now, you can try to use the command line for all the commands we have learned.

Leave a Comment