Git cheat sheet (part 1)

Git is probably one of the best revision control software out there. My very first experience with distributed version control system was with Bazaar 7 years ago and quickly after I tried Mercurial and Git. Git was extremely powerful but at that time it was also a bit tougher to learn.

So eventually I ended up using Mercurial and helped introducing it in my company. But I kept looking at Git from time to time (and had to remember that what is called ‘pull’ in hg is called ‘fetch’ in git and the other way around ).

At some point, I found that Git was really no longer so difficult and really more powerful. So despite my company was still using Mercurial I started using Git for my personal projects. Eventually my company will adopt Git as well next to Mercurial.

This post aims at helping you starting with Git (and GitHub) as well as being a cheat sheet for me

Git

Getting a repository and doing changes

One of the best feature of Git is how it deals with branches. That’s the reason why I finally chose Git over Mercurial. It is extremely efficient to deal with branches in Git. But first things first, let’s first create a repository:

$ git init

This creates an empty repository. Git is a distributed version control system, meaning that there is no centralized server that holds the entire history of the revisions. Instead, everyone can have a copy of the entire repository with the revisions history. To obtain a copy of an existing repository, you can use the clone command:

$ git clone <a repository>

You can start doing some modifications in your copy of the repository. You will want to commit your modificaitons into the repository at some point, that is to register fo good your modifications into the repository. But before you can do that, you need to tell Git what modifications need to be considered when committing.

This seems a bit cumbersome a the beginning but it proves to be very powerful on the long run. You can stage in (or unstage) modifications. This is done by “adding” your modifications so they can be taken into account for the next commit. Suppose you create a new file FILE, it can be added using:

$ git add FILE

And then you can commit your added changed using:

$ git commit

After you have committed something, you can look at the list of revisions and see what has just been added. This is achieved by using:

$ git log

If you want to have a more visual representation, you can also use:

$ git log --graph

‘git log’ has a number of options which are very interesting, so I suggest you take some time looking at them. In particular the ‘-p’ option.

We have seen the very basics. A important thing to understand is the difference between a repository – all the registered revisions since the creation of the repository – and the working directory – the set of files a given version of made of and that you can look at on your file system.

Every revision is identified by a SHA-1 identifier such as:

93d926280e04da71ffd694e4ed0576d2fffe95f7.

It is therefore easy to look what a given version looked like by doing:

$ git checkout <SHA-1 identifier>

The working directory, that is the say the fles you see and modify, will be updated to that specific version. You can go back to the latest version by using the special identifier ‘master’.

You can read more with this excellent guide from the Git web site. Make sure you understand the difference between the repository and the working dir before reading further.

Working with others

Until now, we have made changes and committed them in our own repository. One thing to note is that the repository your work on is local. You can share your changes with another respository by pushing them using:

$ git push

Alternatively, you can get the changes from another repository by doing:

$ git pull

But before going further you need to understand the notion of remote repositories. Remote repositories are other people’s repositories for the same code. If you have obtained your repository by cloning someone else’s repository, the source repository is known as origin in your own repository. You can list all the remote repositories that are known to yours by running:

$ git remote

The output will be a list of all known remote repositories. Unless you created your repository by running ‘git init’ there should at least one known remote repository, the one you have cloned from and which is called origin:

$ git remote
origin

When you use ‘git push’ or ‘git pull’, it is the origin repository that is used. ‘git pull’ is a bit special. In fact it does two things: it first fetches all the modifications from the remote repository and then apply those modifications to your working dir. It is possible though simply get the modifications without applying them to the working dir by doing:

git fetch

Branches

Let’s move to branches. Branches are so convenient with Git what you will want to use branches all the time.

To create a branch it is as easy as:

$ git branch <branch_name>

You can list all the existing branches in your repository by doing:

$ git branch

The output will be something like:

bug-fix
* master
new-feature

The branch with the asterisk is the currently active branch, i.e. the one that is checked out in your working dir.

To move from a branch to another, simply checkout the one you want:

$ git checkout bug-fix
Switched to branch 'bug-fix'

When you are working on a branch, you can add changes and commit them the same way we have seen (git add, git commit) after you have switched to that branch. After all, the default branch ‘master’ is just a branch.

It is possible to rename a local branch:

$ git branch -m <old branch name> <new branch name>

or simply:

$ git branch -m <new name>

to rename the current branch we are on.

It is possible also to delete a branch once we are done with it:

$ git branch -d <name of the branch>

Deleting a branch makes sense when we are done doing some experimentation with it, when we have finished merging the new development into the master branch or when we have shared all the development we have done in that branch with someone else. It is therefore possible to push to a specific branch on a remote repository from one of your branchs and also to pull from a specific branch into a one of your branches. We will see that later on.

Collaborative Developments

Git is very powerful at collaborative developments thanks to remote repositories and branches. As we have already seen, it is possible to list all the known remote repositories using:

$ git remote

or the more useful:

$ git remote -v

It is also possible to add a remote repository to the list of known ones using:

$ git remote add <local name for the remote repo> <remote repo to add>

Ex:

$ git remote add upstream git://github.com/someone/repo.git

To remove a remote repository from the list of known repositories, it is pretty straightforward:

$ git remote rm <your local name for the remote repository>

Same way you can rename it:

$ git remote rename <old name for the remote> <new name for the remote>

Once you have added a remote repository, then we can go even further in the collaboration. It is very easy to get modifications from that remote repository. You can fetch changes doing:

git fetch <your local name for the remote repository>

When you do a ‘git fetch’ you might get some branches from the repo that you don’t have locally. Git will automatically creates read-only branches (think tag) in your repository from the ones in the remote repository you have fetched the changes from. Such read-only branches have a name like /. If you want to create a read-write branch in your repository for one of these remote branches you need to do it explicitly:

$ git checkout -b <local branch name> <local name for the remote repo>:<remote branch name>

This is called a tracking branch. It is a local branch with a direct relationship to a remote branch. So if you want to retain the branch name from the remote repository you can also do the simpler:

$ git checkout --track <local name for the remote repo>:<remote branch name>

Branches are really what you want to use Git for. It is so powerful at dealing with branches that you will find yourself using branches all the time. It is in particular possible to exchange branches with remote repositories. To publish a local branch to a remote repository, you can use:

git push <local name for the remote repo> <local branch name>:<branch name to give on the remote repo>

and to remove a branch on a remote repository, the counter-intuitive:

git push <remote repo> :<branch name on the remote >

It seems counter intuitive but it is actually extremely logical. Using this command you in fact instruct Git to take nothing from your side and to make it the remote branch in the remote repository. Hence, it is a delete…

Collaborations you need some way to inspect what you are about to push out or what you are about to pull in. Git allows you to do that. It is possible to list the commits that a branch has and that another one does not have. This is achieved by doing:

$ git log <branch name to start from>..<branch name to go to>

It start from the latest revision of and list all revisions to get to the latest revision of . This works also if you use 'git diff'. As we have seen earlier, when we do a 'git fetch' Git creates automatically some read-only branches for the branches that exists in the remote repository we are fetching from. So it is possible to list the revisions between a local branch and a remote branch by using this local read-only branch.

$ git fetch <local name for the remote repo>
$ git log <local name for the remote repo>/<remote branch name>..<local branch name>

This will show all the revisions your local branch has that the remote branch on the remote repository does not have. So very logically:

$ git fetch <local name for the remote repo>
$ git log <local branch name>..<local name for the remote repo>/<remote branch name>

will tell you the revisions the remote branch on the remote repository has and that your local branch doesn’t.

ATTENTION: If you are using a tracking branch to track a remote branch in a remote repository, doing a ‘git fetch ' and then doing a 'git log' between your local branch and the tracking branch won't give you an accurate result. Tracking branches are *NOT* updated when do a 'git fetch'.

Tracking branches are updated only after you merge. so you need to do:

$ git checkout <tracking branches>
$ git pull

And then you can run the ‘git log’ command to list the revisions between the two branches.

A typical flow with GitHub.

Let’s assume that you want to participate in the development on some cool repository on the great GitHub and that this repository is OWNER/REPOSITORY. First you need to fork it. This is simple, just click the ‘Fork’ button on the web site.

Boom. You get your own fork of OWNER/REPOSITORY. It is called YOU/REPOSITOPRY and it is a bare repository. A bare repository is a repositpory without a working dir. It’s important because if it was not a bare repository, you wouldn’t be able to push to it.

Now let’s hack a bit.

First step is to clone your forked repository. This is done with:

$ git clone https://github.com/YOU/REPOSITORY REPOSITPORY

Now if you do ‘git remote -v’ in your freshly cloned repository, you will see something like:

origin	https://github.com/YOU/REPOSITORY (fetch)
origin	https://github.com/YOU/REPOSITORY (push)

Then I recommend to add a remote repository to reference the upstream (aka the repository your forked initially) This is done like that:

$ git remote add upstream https://github.com/OWNER/REPOSITORY

So now when you do a ‘git remote -v’ you shall see:

origin	https://github.com/YOU/REPOSITORY (fetch)
origin	https://github.com/YOU/REPOSITORY (push)
upstream	https://github.com/OWNER/REPOSITORY (fetch)
upstream	https://github.com/OWNER/REPOSITORY (push)

At this point go code, and commit your changes. As many commits as you wish… When you are done and happy with what you have done, you can push your changes to your forked repository so they can be saved there. It is quite easy to do so, simply do a:

$ git push

You can code, commit and push some more if you want. Eventually, you will want to suggest your changes back to the project you have forked from. This is done with GitHub by submitting a pull request. More here on how to do it

Eventually, it will be merged (or rejected) by the owner of the repository. If it gets merged, it is to be noted that GitHub merges with the ‘–no-ff’ option of Git.

So there will be a revision to track the merge (no fast forward). This is a revision you won’t have in your forked repository. No big deal though, you’ll get it eventually when you sync with the original repository.

Indeed it is a good idea, once in a while, to keep your upstream remote updated with the original repository. You can do that by doing:

$ git fetch upstream

NOTE: you will get all branches present in upstream in read-only in your repository.

It is possible that you get also some developments made by others and merged into the initial repository. This is some code that you did not have before fetching. To merge this code into your repository, you can do:

$ git pull upstream master

If you had some local changes, this might require you merge locally. Once you are done, you can send it to your forked repository on GitHub by doing:

$ git push

You might want to list the revisions between your forked repository and the upstream one. To do that you will first fetch the up-to-date reference for both repository and then do a ‘git log’.

$ git fetch origin
$ git fetch upstream

$ git log origin/master..upstream/master

NOTE: even if you have just done a ‘git push’ to origin, you will need to do a ‘git fetch origin’. Git does not update local references after doing a push. And it is good it does not do it automatically

Alternatively, to keep yourself updated with upstream, you can simply do a

$ git fetch upstream

This won’t merge anything. It simply gets the changes and update the references of upstream (the read-only branches). Later on, when you are ready to merge your master branch with upstream, from your master branch simply do:

$ git merge upstream/master

and when you are happy with it, you can push it to your forked repository by doing a:

$ git push origin

Go, Hack !