Created at:

Modified at:

Git notes

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.

CVS

Subversion

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.

Mercurial

Bazaar

Monotone

Fossil

git

GitHub

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

github-ssh

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!

git clone

    $ git clone git@github.com:username/project.git

I'm supposing the name of the user is username and the repository name you choose is project.

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 doing).

Standalone mode

If you don't want to use GitHub, you can setup your own repository. First create the directory of the project, then use git init:

    $ mkdir project
    $ cd project
    $ git init

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 .git directory.

Playing with git

According to the gittutorial_, after you install git, it is a good idea to tell who you are, with git config:

    $ git config --global user.name "Your Name Comes Here"
    $ git config --global user.email your@email.comes.here.com

gittutorial

git config

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, fileA.txt and 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:

    $ git add *

git add

They are *marked for adition* but they are not commited to the repository yet.

Actually, 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.

staging-area

To commit the files in the repository, use git commit:

    $ git commit

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 differences:

    $ git diff

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. The flag -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 -m flag::

    $ git commit -a -m 'Commit message comes here'

For new files git commit -a will not work. You will have to add them manually with git add.

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.

git log

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 <my@email.com>
    Date:   Thu Jan 5 15:39:43 2017 -0200

        Adding a test line

    commit 112f5c1ec517bb25d1a054c063652c796d918a77
    Author: My Name <my@email.com>
    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

Alongside with fileA.txt and fileB.txt there is the .git 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

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 --bare to 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

git push

origin is the repository we cloned (this name can be changed with git remote rename). master is the name of the branch we are syncing with the other repositories. We see about branches in section "Branch, checkout and merge".

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 project directory (the one you cloned from GitHub) or project2 (the one you cloned from your own bare repository). Use the command git remote:

    $ git remote -v
    origin  git@github.com:username/project.git (fetch)
    origin  git@github.com:username/project.git (push)

git remote

When we clone a repository, the name of the repository we cloned from is, by default, origin (you can change it with git remote mv). When we commit to our repository, changes **don't** get synced back to the origin repository by default. To push that changes back, we need to type the following command::

    $ 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  git@github.com:username/project.git (fetch)
    origin  git@github.com: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:

    $ git pull origin master

git pull

It is the opposite of git push.

There is also git fetch:

    $ git fetch origin

git fetch

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.

Branching

Every repository has at least one branch, normally called master (you can change its name with git branch -m). Let's try in our project directory:

    $ 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:

    $ git branch mybranch

git branch

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::

    $ git checkout mybranch
    Switched to branch 'mybranch'

git checkout

And show branches again:

    $ git branch
      master
    * mybranch

Now, every change will be committed in branch mybranch, not master.

Note: Command 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 master branch? First, we need to change (checkout) to the branch that is going to receive the changes::

    $ git checkout master
    Switched to branch 'master'

And them execute command git merge:

    $ git merge mybranch
    Updating cc28b9f..af0381b
    Fast-forward
     fileA.txt | 1 +
     1 file changed, 1 insertion(+)

git merge

Note: You see the line Fast-forward? It is the algorithm used by git merge. Git uses different algorithms for merging, depending on the case. git merge is a powerful command with lots of options. It is worth to study the git merge documentation.

Other resources

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.

Good luck!

Random tips

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 b.git 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 a.git, do:

    $ 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:

    $ git fetch upstream

git-fetch

It will fetch "objects", but not "refs". Refs (inside the refs 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 (the cat utility) it, you will see a hash representing a commit. An example of my current refs/heads/master ref:

    $ 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 the 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...

    $ 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" as refs/remotes/upstream/master points to.

git-update-ref

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.

Troubleshooting

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) description file.

"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

Before calling 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 /usr/pkg.

pkgsrc

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.

When 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

git-config

error: "patch failed .. patch does not apply .. Patch already applied"

After a 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 git-bundle for details). The clone was made, but git cannot checkout a working tree.

git-bundle

A 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 yourrepo.git/hooks/update.

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 variable::

    $ GIT_SSL_NO_VERIFY=true git ...