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
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:
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:
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>
$ 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.
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>
$ 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
$ 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
$ 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.
If you are using a tracking branch to track a remote branch in a remote repository, doing 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 !