This post tries to provide a gentle introduction to git. While it is aimed at newcomers it is also meant to give a rather good overview and understanding about what one can do with git and thus is extensive. It follows the premise:
If you know how a thing works, you can make it do what you want it to.
Please don't be afraid as I'm trying to use light language and many examples so you can read fast through it and understand anyway (if I succeed). I'm also trying to highlight important things at least for long texts to ease reading a bit more. However, at some points this tutorial may require you to think for yourselves a bit - it's a feature, not a bug.
This tutorial is meant for everyone willing to learn git - it does not matter if you are a developer who never really got the hang of it or a student wanting to learn. Even if you are doing non coding tasks like design or documentation git can be a gift from heaven. This tutorial is for everyone - don't be scared by its length and believe me, it pays off! The only requirement is that you have git installed, know cd
, ls
and mkdir
and have something to commit - and who doesn't have any digital data!?
This tutorial is the first out of three tutorials which are meant to free your mind from a traditional view on filesystems.
This tutorial, Genesis (i.e. "Creation" or "Beginning"), will cover some very basic things:
- Configuring git so you can use it how you want. (Basic, aliases)
- Creating your git repository (play god once!)
- Creating your first git commits.
- Learning what the repository is and where commits get stored.
- Browsing through history.
- Ignoring files.
Exodus (i.e. "going out") will cover:
- Store temporary changes without committing.
- Navigating commits in git.
- Sharing commits to a remote place.
- Locally (huh, that's sharing?)
- Via email
- To a server
- To a client
- Working with others (i.e. non-linear).
- Join things.
- Linearize things.
- Writing good commits.
- Editing commits. (Actually not editing but it feels like that.)
Apocalypse (i.e. "uncovering") will try to uncover some more advanced features of git, finally freeing your mind from your non-versioned filesystem:
- Finding more information about code.
- Finding causes of bugs in git.
- Reverting commits.
- Reviewing commits.
- Travelling though time and changing history (you want me to believe you've never wanted to do that?)
- Getting back lost things.
- Let git do things automatically.
A short warning: If you ever really got the hang of git you will not be able to use something else without symptoms of frustration and disappointment - you'll end up writing every document versioned as an excuse to use git.
A warning for windows users: you may need to use equivalent commands to some basic UNIX utilities or just install them with git. (Installer provides an option for that.) In general it's a bit like travelling with Vogons - avoid when possible.
A warning for GUI users: Don't use your GUI. Be it the GitHub App or SourceTree or something else - they usually try to make things more abstract for us, thus they hinder us from understanding git and we can then not make git do what we want. Being able to communicate directly with git is a great thing and really bumps productivity!
I wrote this tutorial to the best of my knowledge and experience, if you spot an error or find something important is missing, be sure to drop me a message!
So go now, grab a cup of coffee (or any other drink), a towel, take your best keyboard and open a terminal beneath this window!
Before we really get started it is important to know what git roughly does: git is a program that allows you to manage files. To be more specific git allows you to define changes on files. In the end your repository is just a bunch of changes that may be related to each other.
Before we can continue we'll have to set up a few tiny things for git. For this we will use the git config --global
command which simply stores a key value pair into your user-global git configuration file (usually stored at ~/.gitconfig
).
Let's tell git who we are! This is pretty straightforward:
$ git config --global user.name "Ford Prefect"
$ git config --global user.email ford@prefect.bg
This makes git store values for "name" and "email" within the "user" section of the gitconfig.
For some operations git will give you an editor so you can enter needed data. This editor is vim by default. Some people think vim is great (vim is great!), some do not. If you belong to the latter group or don't know what vim is and how to operate it, let's change the editor:
$ # Take an editor of your choice instead of nano
$ git config --global core.editor nano
Please make sure that the command you give to git always starts as an own process and ends only when you finished editing the file. (Some editors might detect running processes, pass the filename to them and exit immediately. Use -s
argument for gedit, --wait
argument for sublime.) Please don't use notepad on windows, this program is a perfect example of a text editor which is too dumb to show text unless the text is written by itself.
So, lets get started - with nothing. Let's make an empty directory. You can do that from your usual terminal:
$ mkdir git-tutorial
$ cd git-tutorial
$ ls -a
./ ../
So, lets do the first git command here:
$ git init
Initialized empty Git repository in /home/lasse/prog/git-tutorial/.git/
$ ls -a
./ ../ .git/
So now we've got the .git
folder. Since we just created a repository with git init
, so we can deduce, that this .git directory must in fact be the repository!
So, let's create some content we can manage with git:
$ echo 'Hello World!' >> README
$ cat README
Hello World!
Since we know, that the .git directory is our repository, we also know that we did not add this file to our repository yet. So how do we do that?
As I've hinted before, our git repository does not contain files but only changes - so how do we make a change out of our file?
The answer lies in (1) git add
and (2) git commit
which allow us to (1) specify what files/file changes we want to add to the change and (2) that we want to pack those file changes into a so called commit. Git also offers a helper command so we can see what will be added to our commit: git status
.
Let's try it out:
$ git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
$ git add README
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README
So obviously with git add
we can stage files. What does that mean?
As we know, when we're working in our directory any actions on files won't affect our repository. So in order to add a file to the repository, we'll have to put it into a commit. In order to do that, we need to specify, what files/changes should go into our commit, i.e. stage them. When we did git add README
, we staged the file README, thus every change we did until now to it will be included in our next commit. (You can also partially stage files so if you edit README now the change won't be committed.)
Now we'll do something very special in git - creating the first commit! (We'll pass the -v
argument to get a bit more info from git on what we're doing.)
$ git commit -v
You should now get your editor with contents similar to this:
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
#
# Initial commit
#
# Changes to be committed:
# new file: README
#ref: refs/heads/master
# ------------------------ >8 ------------------------
# Do not touch the line above.
# Everything below will be removed.
diff --git a/README b/README
new file mode 100644
index 0000000..c57eff5
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+Hello World!
Since we're about to create a change, git asks us for a description. (Note: Git actually allows to create commits without a description with a special argument. This is not recommended for productive collaborative work!)
Since we passed the -v
parameter, git also shows us below what will be included in our change. We'll look at this later.
Commit messages are usually written in imperative present tense and should follow certain guidelines. We'll come to this later.
So, let's enter: Add README
as our commit message, save and exit the editor.
Now, let's take a look at what we've created, git show
is the command that shows us the most recent commit:
$ git show
commit ec6c903a0a18960cd73df18897e56738c4c6bb51
Author: Lasse Schuirmann <lasse.schuirmann@gmail.com>
Date: Fri Feb 27 14:12:01 2015 +0100
Add README
diff --git a/README b/README
new file mode 100644
index 0000000..980a0d5
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+Hello World!
So what do we see here:
- It seems that commits have an ID, in this case
ec6c903a0a18960cd73df18897e56738c4c6bb51
. - Commits also have an author and a creation date.
- Of course they hold the message we wrote and changes to some files.
What we see below the diff ...
line is obviously the change. Let's take a look at it: since git can only describe changes, it takes /dev/null
(which is a bit special, kind of an empty file, not important here), renames it to README
and fills it with our contents.
So, this commit is pretty godish: It exists purely on it's own, has no relations to any other commit (yet, it's based on an empty repository, right?) and creates a file out of nothing (/dev/null is somehow all and nothing, kind of a unix black hole).
So, let's look in our repository!
$ ls -la .git
total 52
drwxrwxr-x. 8 lasse lasse 4096 Feb 27 16:05 ./
drwxrwxr-x. 3 lasse lasse 4096 Feb 27 14:11 ../
drwxrwxr-x. 2 lasse lasse 4096 Feb 27 14:11 branches/
-rw-rw-r--. 1 lasse lasse 486 Feb 27 14:12 COMMIT_EDITMSG
-rwxrw-r--. 1 lasse lasse 92 Feb 27 14:11 config*
-rw-rw-r--. 1 lasse lasse 73 Feb 27 14:11 description
-rw-rw-r--. 1 lasse lasse 23 Feb 27 14:11 HEAD
drwxrwxr-x. 2 lasse lasse 4096 Feb 27 14:11 hooks/
-rw-rw-r--. 1 lasse lasse 104 Feb 27 14:11 index
drwxrwxr-x. 2 lasse lasse 4096 Feb 27 14:11 info/
drwxrwxr-x. 3 lasse lasse 4096 Feb 27 14:12 logs/
drwxrwxr-x. 7 lasse lasse 4096 Feb 27 14:12 objects/
drwxrwxr-x. 4 lasse lasse 4096 Feb 27 14:11 refs/
$
Now let's look into it further to get to know what it is a bit more. I will try to cover only important parts here, if you're interested even deeper, you can try DuckDuckGo or take a look at this: http://git-scm.com/docs/gitrepository-layout
The config file is a similar file to the one where our settings in the beginning got stored. (User and editor configuration, remember?) You can use it to store settings per repository.
The objects directory is an important one: It contains our commits.
One could do a full tutorial on those things but that's not covered here. If you want that, check out: http://git-scm.com/book/en/v2/Git-Internals-Git-Objects
We just saw the ID of the commit we made: ec6c903a0a18960cd73df18897e56738c4c6bb51
Now let's see if we find it in the objects directory:
$ ls .git/objects
98/ b4/ ec/ info/ pack/
$ ls .git/objects/ec
6c903a0a18960cd73df18897e56738c4c6bb51
So, when we create a commit, the contents (including metadata) are hashed and git stores it finely into the objects directory.
That isn't so complicated at all, is it?
git show
accepts a commit ID as an argument. So you could e.g. do git show ec6c903a0a18960cd73df18897e56738c4c6bb51
instead of git show
if this hash is the current commit.
Investigate what the other two objects are, which are stored in the objects directory. (Ignore the info and pack subdirectory.)
Do git show
again and take a look at the line beginning with "index". I'm sure you can make sense out of it!
The HEAD file is here so git knows what the current commit is, i.e. with which objects it has to compare the files in the file system to e.g. generate a diff. Let's look into it:
$ cat .git/HEAD
ref: refs/heads/master
So it actually only references to something else.
So let's take a look into refs/heads/master - what ever this is:
$ cat .git/refs/heads/master
ec6c903a0a18960cd73df18897e56738c4c6bb51
So this HEAD
file refers to this master
file which refers to our current commit. We'll see how that makes sense later.
Now, let's go on and create another commit. Let's add something to our README. You can do that by yourself, I'm sure!
Let's see what we've done:
$ git diff
diff --git a/README b/README
index 980a0d5..c9b319e 100644
--- a/README
+++ b/README
@@ -1 +1,2 @@
Hello World!
+Don't panic!
Let's commit it. However, since we're a bit lazy we don't want to add the README manually again; the commit command has an argument that allows you to auto-stage all changes to all files that are in our repository. (So if you added another file which is not in the repository yet it won't be staged!)
git commit -a -v
Well, you know the game. Can you come up with a good message on your own?
$ git show
commit 7b4977cdfb3f304feffa6fc22de1007dd2bebf26
Author: Lasse Schuirmann <lasse.schuirmann@gmail.com>
Date: Fri Feb 27 16:39:11 2015 +0100
README: Add usage instructions
diff --git a/README b/README
index 980a0d5..c9b319e 100644
--- a/README
+++ b/README
@@ -1 +1,2 @@
Hello World!
+Don't panic!
So this commit obviously represents the change from a file named README which contents are stored in object 980a0d5
to a file also named README which contents are stored in object c9b319e
.
Let's see a timeline of what we've done:
$ git log
commit 7b4977cdfb3f304feffa6fc22de1007dd2bebf26
Author: Lasse Schuirmann <lasse.schuirmann@gmail.com>
Date: Fri Feb 27 16:39:11 2015 +0100
README: Add usage instructions
commit ec6c903a0a18960cd73df18897e56738c4c6bb51
Author: Lasse Schuirmann <lasse.schuirmann@gmail.com>
Date: Fri Feb 27 14:12:01 2015 +0100
Add README
That looks fairly easy. However I cannot withstand to point out that despite commits look so fine, linearly arranged here, they are actually nothing more than commit objects, floating around in the .git/objects/ directory. So git log
just looks where HEAD points to and recursively asks each commit what it's parent is (if it has one).
Since every good hitchhiker does know how to travel through time and change events, we'll learn to do that in the next chapter ;)
It is worth to mention that git add
also accepts directories as an argument. I.e. git add .
recursively adds all files from the current directory.
In order to generally ignore certain patterns of files (e.g. it's bad practice to commit any generated stuff), one can write a .gitignore
file. This file can look as follows:
README~ # Ignore gedit temporary files
*.o # Ignore compiled object files
The exact pattern is defined here: http://git-scm.com/docs/gitignore
Files matching this pattern will:
- Not be added with
git add
unless forced with-f
- Not be shown in
git status
as unstaged
It is usually a good idea to commit the .gitignore
to the repository so all developers don't need to care about those files.
So, we've learned quite some stuff. However git command's aren't as intuitive as they could be sometimes. They could be shorter too. So let's define us some aliases of the commands we know. The ones given here are only suggestions, you should choose the aliases in a way that suits best for you!
If you're using git much, you might want to add alias g=git
to your .bashrc
or .zshrc
or whatever. (On windows you're a bit screwed. But what did you expect? Really?)
Let's let git give us our editor since we don't want to edit just one value:
git config --global --edit
You can add aliases through the [alias]
section, here are the aliases I suggest:
[alias]
a = add
c = commit -v
ca = commit -v -a
d = diff
dif = diff # Thats a typo :)
i = init
l = log
st = status
stat = status
So what did we learn?
We did some basic git commands:
git config
: accessing git configurationgit init
: creating a repositorygit status
: getting current status of files, staging and so ongit add
: staging files for the commitgit diff
: showing the difference between the current commit and what we have on our file systemgit commit
: writing staged changes to a commitgit log
: browsing history
We also learned how git organizes commits, how it stores files and how we can make git ignore files explicitly.
I hope this helped you understanding a bit what git does and what it is. The next tutorial will hopefully cover all the basics. (Some were already hinted here.)