First steps with Git
I started with revision control systems some time ago, with CVS. At that time there were already more modern version control software but we just loved the simplicity of CVS. Some time later the company changed the system but still used a client-server approach (centralized): we started using Subversion that I still think it is the best system *for some centralized environments* like some kinds of companies, but that is another history.
When starting to making some development with free software, I was introduced to modern decentralized version control systems. After trying out Mercurial, Bazaar (used by Canonical for the Ubuntu Linux project), Monotone (very interesting and one of the main inspiration when Linus Torvalds built git) and Fossil (another very interesting system that tries to bundle bug tracking together with code revision). I also gave a try out git, which has the enormous advantage of being adopted by a huge developer community, which led to the birth of online services that provided git hosting, like GitHub and others.
This small guide will not tell you the difference of the systems I just mentioned (it is worth to take a look at each of them) but will just help you start with git basics. Before beginning, check if you have git installed in your machine. We are going to use the command line interface. There are graphical front ends to it, as well as plugins for your favorite IDE, but we'll stick to the simple yet powerful command line interface.
Starting with a hosting service
Note: If you are not using GitHub, you may jump directly to section "Standalone mode".
First: git is a *decentralized* version control system, which means that you do not need a server. You can sync repositories you have in different machines, like your desktop, your laptop and your friends' computers. But it allows you to have a *reference remote repository* where everybody will fetch source code from there. You may start using one of the many git hosting services. Let's start with the most common one: GitHub. GitHub requires you to register SSH keys so you can use them to push code into your remote repository. In this article, we aren't going into details on how to do that. You can see link below for more information
Create an account in GitHub, setup your SSH keys and create a testing
repository. GitHub will give you a link to *clone* your repository. So, just
clone it with
$ git clone email@example.com:username/project.git
I'm supposing the name of the user is
username and the repository name you
The command above will create a directory named
project in your machine.
It is an empty directory, because your GitHub repository is empty for now.
Inside it, there is a sole subdirectory named
.git, which has metadata of
your repository and where you shouldn't touch (unless you know what you are
If you don't want to use GitHub, you can setup your own repository. First
create the directory of the project, then use
$ mkdir project $ cd project $ git init
It is important to note that, unlike CVS nor Subversion, the repository is not
stored elsewhere, but within the directory of the working copy. After you
make changes to your files in
project directory, you make a
commit (see section "Playing with git") and changes are stored in the
Playing with git
According to the gittutorial_, after you install git, it is a good idea to
tell who you are, with
$ git config --global user.name "Your Name Comes Here" $ git config --global user.email firstname.lastname@example.org
It will create a file in your home directory named
.gitconfig with these
and other settings you changed with
git config. It is important to
identify yourself, like above, so commits are assigned to right developers.
Now, inside directory
project, create two files,
fileB.txt. Put any content inside it, it doesn't matter for now. The
structure will be like that:
* project/ * fileA.txt * fileB.txt
Now add files to your repository with
$ git add *
They are *marked for adition* but they are not commited to the repository yet.
git add adds file to the *staging area* or *index*. It is a
very powerful feature of git, but is beyond the scope of this text. To
know more about that, read about the staging area.
To commit the files in the repository, use
$ git commit
Your favorite text editor will show up, where it is a good idea to add a
commit message, brief explaining what you changed in this commit. When it is
the first commit of the repository I like the
Initial commit string.
Now, make a change in one of the files e use
git diff to see the
$ git diff
Voilà! This command show differences between the last version committed and the files your working copy. To commit the changes, execute::
Don't forget the commit message with a summary of the changes you've made.
-a is necessary to avoid having to type
git add to add the
changes. Git doesn't commit changes you've made in your files before marking
them to addition (even though changes are in files already added and committed
to the repository before).
You may pass the commit message to
git commit command directly, without
having to open your editor. Use the
$ git commit -a -m 'Commit message comes here'
For new files
git commit -a will not work. You will have to add them
Finally, the command
git log will show you the log of your changes. If you
are not convinced of the importance of filling out the with a message, you may
get convinced now. The
git log command shows the changes you've made,
alongside with the author, date, etc.
Take a look at the output of commands above, in a testing repository I've just made:
$ cd /tmp/ $ mkdir project $ cd project/ $ git init Initialized empty Git repository in /tmp/project/.git/ $ touch fileA.txt fileB.txt $ git add * $ git commit -a -m 'Initial commit' [master (root-commit) 112f5c1] Initial commit 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 fileA.txt create mode 100644 fileB.txt $ echo 'A test line' >> fileA.txt $ git diff diff --git a/fileA.txt b/fileA.txt index e69de29..9efab9b 100644 --- a/fileA.txt +++ b/fileA.txt @@ -0,0 +1 @@ +A test line $ git commit -a -m 'Adding a test line' [master cc28b9f] Adding a test line 1 file changed, 1 insertion(+) $ git log commit cc28b9f73a5d487c9d3166b0a3585926ab7d8961 Author: My Name <email@example.com> Date: Thu Jan 5 15:39:43 2017 -0200 Adding a test line commit 112f5c1ec517bb25d1a054c063652c796d918a77 Author: My Name <firstname.lastname@example.org> Date: Thu Jan 5 15:39:24 2017 -0200 Initial commit
Repository and working copy
In traditional centralized revision control systems like CVS and Subversion, the repository is always separated from the local working copy, where developers work. The repository is normally placed in a remote machine and the user checks out from the repository to the working copy and commits to the repository.
In git, the repository is together with the working copy. See all files of the the your project::
$ ls -A1 .git fileA.txt fileB.txt
fileB.txt there is the
directory. That is your repository! All the log, commits, remotes, etc., is
within this directory and if you delete it you will wipe everything out.
The repository in GitHub is a special kind of repository called *bare repository*.
Bare repositories are like normal repositories, but without the working copy. They are conceived to store repository information only, not needing to have the working copy. Developers are not meant to have direct access to the bare repository and should access it remotely, via git commands.
To setup a git bare repository, pass the
git init. Let's
make it with another example::
$ cd /tmp/ $ mkdir project2.git $ cd project2.git $ git init --bare Initialized empty Git repository in /tmp/project2.git/
You can now clone it, like you would do with any other repository::
$ cd /tmp $ git clone /tmp/project2.git /tmp/project2-working-copy Cloning into '/tmp/project2-working-copy'... warning: You appear to have cloned an empty repository. done.
Git tell us we have cloned an empty repository. But it doesn't matter, We
can now add files, make commits and so on. But they will be in our clone
/tmp/project2-working-copy). To push changes back to the repository we
just cloned, we use the
git push command::
$ git push origin master
origin is the repository we cloned (this name can be changed with
master is the name of the branch we are syncing with
the other repositories. We see about branches in section "Branch, checkout
There is a nice thread in the git mailing list about bare repositories that you may want to take a look:
Discussion: how to use two bare repositories?
Remote, push, fetch and pull
Now that you know what repositories are (and bare repositories), let's
discover how to sync them. Come back to either your
(the one you cloned from GitHub) or
project2 (the one you cloned from your
own bare repository). Use the command
$ git remote -v origin email@example.com:username/project.git (fetch) origin firstname.lastname@example.org:username/project.git (push)
When we clone a repository, the name of the repository we cloned from is, by
origin (you can change it with
git remote mv). When we
commit to our repository, changes **don't** get synced back to the
repository by default. To push that changes back, we need to type the
$ git push origin master
origin is the repository we want to push changes to and
master is the
name of the branch we want to sync. Check section "Branch, checkout and
merge" for more information about branches.
You can add different "remotes". Just use command
git remote add:
$ git remote add test mygithost:/foo/bar $ git remote -v origin email@example.com:username/project.git (fetch) origin firstname.lastname@example.org:username/project.git (push) test mygithost:/foo/bar (fetch) test mygithost:/foo/bar (push)
Now you can push your changes to the
test remote repository too. Of
course, the git environment need to be installed in the remote machine.
Now, what if other developers have also pushed their changes to the remote
repository? How we pull them back? Using the command
$ git pull origin master
It is the opposite of
There is also
$ git fetch origin
The difference between
git fetch and
git pull is that the fetch
"fetches" differences from the remote repositoy but don't *merge* them
automatically to your local branch, while
git pull does. In practice,
git pull is equal to
git fetch +
git merge. For more information
on merging, check the section
Branch, checkout and merge.
Branch, checkout and merge
Branching is normally an advanced feature of revision control systems but in
git it is presented to the developer very early, because in git you work with
branches every time, even though you don't realize that nor you don't use the
git branch command.
Every repository has at least one branch, normally called
master (you can
change its name with
git branch -m). Let's try in our
$ git branch * master
The star at the beginning of the line shows the current branch. To create a
new branch, just pass a parameter to
$ git branch mybranch
And show branches again::
$ git branch * master mybranch
mybranch is already created (it is a exact copy of
master) but it is
not checked out yet. To change to it, use
$ git checkout mybranch Switched to branch 'mybranch'
And show branches again:
$ git branch master * mybranch
Now, every change will be committed in branch
git checkout has a lot of different uses, not only checking
out a different branch. It can be used, for instance, to retrieve an old
revision of a given file.
Finally, what if I want to *merge* my changes back to the
First, we need to change (
checkout) to the branch that is going to receive
$ git checkout master Switched to branch 'master'
And them execute command
$ git merge mybranch Updating cc28b9f..af0381b Fast-forward fileA.txt | 1 + 1 file changed, 1 insertion(+)
Note: You see the line
Fast-forward? It is the algorithm used by
merge. Git uses different algorithms for
merging, depending on the
git merge is a powerful command with lots of options. It is
worth to study the
git merge documentation.
This tutorial is just a starting point for such a powerful tool. I highly recommend you to read the Git oficial documentation that has a lot of information, and also the man pages of the commands you are going to use.
Git oficial documentation
Also, there is the Pro Git book that spans from basic features to advanced ones. It is highly recommended!
Pro Git book
Finally, when you have a questions, look for its solutions on the web. Lots of people use git and it is likely that someone already posted something in a forum asking the same question of you.
Mirroring bare repositories
Since git is a decentralized system, you can also clone bare repositories and
tracking each other. This is useful way, say, if you have a bare repository
somewhere (e.g. GitHub), but you still need a bare repository locally
for any reason. Supposing
a.git is your primary repository and
is the one you "copy", and both are located in
/tmp (this path can be remote,
too), follow these steps::
$ cd /tmp/ $ git clone --bare /tmp/a.git b.git $ cd b.git $ git remote add a.git /tmp/a.git # --mirror flag can appear here
Then, whenever you want to sync
b.git, obtaining modifications made in
$ cd /tmp/b.git $ git fetch a.git
Discussion: how to use two bare repositories?
Branches and refs update in bare repository
I frequently use bare repositories not as and "end point" of commiting, but as a "middle" point. For instance, in my server I have some bare repositories that I clone from free software projects. Why keeping a bare repository that tracks another repository? They are organized this way:
So, in this diagram "upstream" is the oficial repositories hosted in providers like SourceForge or GitHub. "origin" is a bare repository in my personal server that clones "upstream" and fetches new changes from it. It is where I push my private branches to. "work" is my local working copy.
The problem shows up when I fetch changes from "upstream", using
$ git fetch upstream
It will fetch "objects", but not "refs". Refs (inside the
directory under the git control directory) are just text files pointing to
commits of the tree. For instance, if you have a tag named "bla", there will
be a ref in
refs/tags/bla and, if you
cat utility) it, you will
see a hash representing a commit. An example of my current
$ cat refs/heads/master a82cd88b10906fae8d5a0f10020eb6d7fb9274a7
So, back to our example, in a bare repository, I can't rebase or merge
remote branches, like I'd do in a working copy repository. And when I use
git-fetch without specifying any other argument, it will not update
refs. So, I have to use:
$ git fetch upstream master:master
So it will update
refs/heads/master to point to where the head of
"upstream" points. Sometimes it might not be easy to know the branch name you
are fetching from (in some situations under a shell script, for instance), so
it appears to be a good idea to use
$ git update-ref refs/heads/master refs/remotes/upstream/master
... it will update
refs/heads/master to make it to point to the same "object"
refs/remotes/upstream/master points to.
Working with bundles
Sometimes you do not have an internet connection to push and pull
repositories. To move your data using other media (email, an HDD, etc.), use
git bundle. It creates a "bundle" (a file) with the contents of your
repository. This file can be transfered safely.
First, cd into your repository directory:
$ cd myrepo
Then create the bundle with all the branches, tags, (all the refs) etc.::
It will create a bundle with all the branches. Later, to clone it to a to a repository with a working copy, do:
$ git clone myrepo.bundle -b master myrepo
This will create the
myrepo directory with the master branch. Then, you can
fetch other branches:
git fetch ../myrepo.bundle mybranch:mybranch
Deleting a remote branch
If your remote branch is called "foo", faça:
$ git push origin :foo
Check section "remote: *** Deleting a branch is not allowed in this repository" if you get an error like that.
This section tries to explain some difficulties I have trying to set up Git, and the solutions I found.
Error "*** Project description file hasn't been set" when
git pull origin
When trying to push to a new remote repository: I got the following error message:
*** Project description file hasn't been set error: hooks/update exited with error code 1 error: hook declined to update refs/heads/master
I had to change the
description file of the remote repository, which means that I
should provide a short description for it. Rememter: it is the remote repository
(not local one)
"Project description file" error in git?
Push do Git dando erro de Project description
"fatal: git upload-pack: aborting due to possible repository corruption on the"
If you are trying to clone a repository, and get the following error:
Initialized empty Git repository in /tmp/repo/.git/ error: git upload-pack: git-pack-objects died with error. remote: aborting due to possible repository corruption on the remote side. fatal: early EOF fatal: git upload-pack: aborting due to possible repository corruption on the remote side. fatal: index-pack failed
git fsck desperately, make sure the remote user's shell can find
git in the
$PATH. In my problem, they were installed from pkgsrc, which by
default installs stuff in
This was easily solved doing:
# cd /usr/local/bin # ln -s /usr/pkg/bin/git # ln -s /usr/pkg/bin/git-receive-pack # ln -s /usr/pkg/bin/git-upload-archive # ln -s /usr/pkg/bin/git-upload-pack
git-clone: path or ssh problem with git-upload-pack in 1.6.0?
error: "patch failed .. Applying: .. trailing whitespace when git svn rebase"
Maybe you get this error:
Applying: <Commit messasge> .../path-to-repository/.git/rebase-apply/patch:21: trailing whitespace.
git svn rebase returns this error, it means that someone commited to the
Subversion repository, code that "breaks" git constraints related to source code
style. To allow it be commited in your local git repository, run this command:
git config core.whitespace -trailing-space,-space-before-tab
error: "patch failed .. patch does not apply .. Patch already applied"
git svn rebase, if you see any error like:
Applying: <Commit message> error: patch failed: <file:line> error: <file>: patch does not apply Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... No changes -- Patch already applied.
For any reason your local repository and the Subversion repository are not in sync and git tries to get changes from SVN that were already taken. Try:
git rebase --skip
It might be a good idea to run
git diff to check if the merge worked fine.
"warning: remote HEAD refers to nonexistent ref, unable to checkout."
This error is caused when cloning from a bundle (check
details). The clone was made, but git cannot checkout a working tree.
git checkout with the right parameters is enough. So:
$ git checkout -b master origin/master
remote: *** Deleting a branch is not allowed in this repository
If you get an error like that when deleting a remote branch, your probably
have a hook enabled in the remote side that is not allowing you to delete it.
This is probably
Login to your remote machine and disable the hook with:
$ git --git-dir /path/to/yourrepo.git config --bool hooks.allowdeletebranch true
Cannot delete remote branch in git
fatal: unable to access '...': SSL certificate
This usually happens for self-signed certificates when cloning a repository. To
make it ignore the certificate check, set the
$ GIT_SSL_NO_VERIFY=true git ...