home | tech | misc | code | bookmarks (broken) | contact | README


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.

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 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 access this page 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!

$ 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

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

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 *

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

Note

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, check this link.

To commit the files in the repository, use 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

Voilà! This command show differences between the last version committed and the files your working copy. To commit the changes, execute:

$ git commit -a

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'

Note

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.

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

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.

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)

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

It is the opposite of git push.

There is also git fetch:

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

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'

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(+)

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 its man page.

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.

Also, there is the Pro Git book that spans from basic features to advanced ones. It is highly recommended!

Finally, when you have a quetoins, 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!

Primeiros passos com o Git

Comecei com sistemas de controle de revisão já há algum tempo, com CVS. Naquela época, já existiam softwares de controle de revisão mais modernos mas simplesmente amávamos a simplicidade do CVS. Algum tempo depois a empresa que eu trabalhava mudou o sistema, mas que ainda era da abordagem cliente-servidor (centralizado): começamos a utilizar o Subversion, que ainda acho o melhor sistema para um ambiente centralizado, como em algumas empresas, mas essa é uma outra história.

Quando comecei a desenvolver software livre, fui apresentado aos sistemas de versionamentos modernos e decentralizados. Depois de experimentar o Mercurial, Bazaar (usado pela Canonical para no projeto Ubuntu Linux), Monotone (sistema muito interessante que foi uma das principais inspirações para Linus Torvalds construir o git) e Fossil (outro sistema muito interessante que tem um sistema de rastreamento de bugs -- bug tracking -- junto com o sistema de versionamento de código). Também experimentei o git, que tem a enorme vantagem de ter sido adotado por uma comunidade gigantesca de desenvolvedores, dando origem a vários serviços que proveem hospedagem com o git, como o GitHub e outros.

Esse guia não irá te dizer a diferença entre os sistemas mencionados (vale a pena dar uma olhada em cada um deles) mas irá te ajudar com o básico do git. Antes de começar, verifique se você tem o git instalado em sua máquina. Iremos utilizar a interface de linha de comando. Existem interfaces gráficas para o git, tanto quanto plugins para sua IDE favorita, mas vamos nos ater à simples, e ainda poderosa, interface de linha de comando.

Iniciando com um serviço de hospedagem

Note

Se você não está utilizando o GitHub, pode ir direto para a seção Modo standalone.

Primeiro: git é um sistema de controle de versão decentralizado, no qual você não precisa de um servidor. Você pode sincronizar repositórios de diferentes máquinas, como seu desktop, seu laptop e computadores de seus amigos. Mas ele permite que você tenha um repositório de referência a partir do qual todos podem adquirir o código-fonte. Você pode começar usando um dos vários serviços de hospedagem git. Vamos começar com o mais comum: GitHub. O GitHub pede que você registre uma chave SSH para que você possa colocar código no seu repositório remoto. Neste artigo, não vamos entrar em detalhes em como fazer isso. Você pode acessar esta página para mais informações.

Crie uma conta no GitHub, configure suas chaves SSH e crie um repositório de teste. GitHub irá te fornecer um link para que você faça o clone de seu repositório. Então, simplesmente o clone com git clone!

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

Estou supondo que o nome de usuário é username e o nome do repositório que você escolheu é project.

O comando acima irá criar um diretório chamado project na sua máquina. É um diretório vazio, porque seu repositório no GitHub também está vazio por enquanto. Dentro dele, existe um diretório solitário com o nome .git que tem metadados de seu repositório e no qual você não deve fazer alterações (a menos que você saiba o que está fazendo).

Modo standalone

Se você não quer utilizar o GitHub, pode configurar seu próprio repositório. Primeiro, crie o diretório de seu projeto, então utilize o git init:

$ mkdir project
$ cd project
$ git init

É importante notar que, diferentemente do CVS or do Subversion, o repositório não é armazenado em outro lugar, mas dentro do diretório da cópia de trabalho. Depois que você modificar seus arquivos no diretório project, faça um commit (veja Brincando com o git) e as mudanças serão armazenadas no diretório .git.

Brincando com o git

De acordo com o gittutorial, depois de instalar o git, é uma ideia dizer quem você é, com git config:

$ git config --global user.name "Seu nome"
$ git config --global user.email seu@email.com

Será criado um arquivo no seu home chamado .gitconfig com esses e outras configurações que você mudou com git config. É importante que você se identifique, como acima, assim os commits serão atribuídos corretamente aos desenvolvedores.

Agora, dentro do diretório project, crie dois arquivos, fileA.txt e fileB.txt. Neles, coloque qualquer conteúdo, pois isso não importa por enquanto. A estrutura será como essa:

  • project/
    • fileA.txt
    • fileB.txt

Agora, adicione arquivos ao seu repositório com git add:

$ git add *

Eles estão marcados para adição, mas não foram salvas no repositório ainda.

Note

Na verdade, git add adiciona arquivo à stage area ou index. Essa é uma funcionalidade muito poderosa do git, mas está além do escopo desse texto. Para conhecer mais, verifique este link.

Note

Em português, não há uma palavra adequada para traduzir commit. Neste tutorial vamos utilizar o verbo salvar quando nos referirmos ao verbo to commit em inglês, ou ainda vamos dizer a expressão fazer commit.

Para salvar os arquivos no repositório, use git commit:

$ git commit

Seu editor de texto favorito surgirá, no qual é uma boa ideia escrever uma mensagem, explicando brevemente o que você mudou neste commit. Quando se trata do primeiro commit de um repositório, costumo colocar a mensagem Initial commit.

Agora, faça uma mudança nos seus arquivos e use git diff para ver as diferenças:

$ git diff

Voilà! Esse comando mostra as diferenças entre a última versão salva e os arquivos na cópia de trabalho. Para salvar as mudanças, execute:

$ git commit -a

Não esqueça da mensagem de commit com um resumo das mudanças que você fez. A flag -a é necessária para não ter que digitar git add para adicionar as mudanças. Git não salva as mudanças que você fez nos arquivos com git commit antes de marcá-las para adição (mesmo que as mudanças tenham sido feitas nos arquivos já adicionados ao repositório anteriormente).

Você pode passar a mensagem de commit para o comando git commit diretamente, sem abrir o seu editor. Utilize a flag -m:

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

Note

Para novos arquivos git commit -a não vai funcionar. Você terá que adicioná-los manualmente com git add.

Finalmente, o comando git log mostrará as mudanças que você fez, juntamente com o autor, data, etc.

Vejam como fica a saída dos comandos executados acima, em um repositório de teste que fiz:

$ 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

Repositório e cópia de trabalho

Em um sistema de controle de revisão tradicional e centralizado como o CVS e o Subversion, o repositório é sempre separado da cópia de trabalho local, onde desenvolvedores trabalham. O repositório é normalmente localizado em uma máquina remota e o usuário faz checkout do repositório para a cópia de trabalho e faz commit da cópia de trabalho ao repositório.

No git, o repositório fica junto da cópia de trabalho. Veja todos os arquivos de seu project:

$ ls -A1
.git
fileA.txt
fileB.txt

Juntamente com fileA.txt e fileB.txt existe o diretório .git. Esse é seu repositório! Todos os logs, commits, endereços de repositórios remotos, etc., está dentro desse diretório e se você excluí-lo irá perder todos os dados.

O repositório localizado no GitHub é um tipo especial de repositório chamado repositório bare.

Repositórios bare

Repositórios bare são como repositórios locais, sem a cópia de trabalho. Eles são concebidos para armazenar apenas a informação do repositório, não havendo a necessidade da cópia de trabalho. Supõe-se que os desenvolvedores não tenham acesso direto a um repositório bare e devem acessá-lo remotamente, utilizando os comandos do git.

Para configurar um repositório bare, passe a opção --bare ao comando git init. Vamos fazer isso com um outro exemplo:

$ cd /tmp/
$ mkdir project2.git
$ cd project2.git
$ git init --bare
Initialized empty Git repository in /tmp/project2.git/

Você pode agora cloná-lo, como faria com qualquer outro repositório:

$ 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 nos avisa que clonamos um repositório vazio. Mas isso não importa. Podemos agora adicionar arquivos, fazer commit e por aí em diante. Mas os arquivos ficarão em nosso clone (/tmp/project2-working-copy). Para enviar as mudanças de volta ao repositório que nós acabamos acabamos de clonar, utilizamos o comando git push:

$ git push origin master

origin é o repositório que nós clonamos (esse nome pode ser mudado com git remote rename). master é o nome da branch que estamos sincronizando com os outros repositórios. Veremos sobre branches na seção Branch, checkout e merge.

Na discussão de e-mails do git existe esta discussão interessante sobre repositórios bare que talvez você queira dar uma olhada.

Remote, push, fetch e pull

Agora que você sabe o que repositórios são (e repositórios bare), vamos descobrir como sincronizá-los. Voltei ao diretório project (que você fez clone do GitHub) ou project2 (o que você clonou de seu repositório bare). Utilize o comando git remote:

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

Quando clonamos um repositório, o nome do repositório que clonamos é, por padrão, origin (você pode mudá-lo com git remote mv). Quando fazemos commit ao repositório, mudanças não são sincronizadas de volta ao repositório origin por padrão. Para enviar as mudanças, precisamos executar o seguinte comando:

$ git push origin master

origin é o repositório ao qual desejamos enviar as mudanças e master é o nome do branch que queremos sincronizar. Verifique a seção Branch, checkout e merge para mais informações sobre branches.

Você pode adicionar diferentes remotes. Use o comando 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)

Agora pode enviar também as mudanças ao repositório remoto test. Sem dúvidas, o ambiente do git precisa também estar instalado e funcionando na máquina remota.

Agora, e se outro desenvolvedor também enviou suas mudanças para o repositório remoto? Como as obtemos? Usando o comando git pull:

$ git pull origin master

É o oposto de git push.

Existe também o comando git fetch:

$ git fetch origin

A diferença entre git fetch e git pull é que o fetch obtém as diferenças do repositório remoto mas não as consolida na sua cópia de trabalho local, enquanto git pull já faz isso para você. Na prática, git pull é o mesmo que git fetch + git merge. Para mais informações sobre merge, veja a seção Branch, checkout e merge.

Branch, checkout e merge

Branching é normalmente uma funcionalidade avançada dos sistemas de revisão de controle mas no git é apresentado desde o início ao desenvolvedor, pois você trabalha com branches todo tempo, mesmo que não perceba ou que não use o comando git branch.

Note

Uma tradução adequada para branch seria galho. Entretanto, o termo é tão comumente utilizado no original em inglês, que vamos utilizar a própria palavra branch no decorrer do texto.

Todo repositório tem pelo menos um branch, normalmente chamado master (você pode mudar seu nome com git branch -m). Vamos experimentar em nosso diretório project:

$ git branch
* master

O asterisco no começo da linha mostra o branch atual. Para criar um novo branch, apenas passe um parâmetro para git branch:

$ git branch mybranch

E mostre os branches novamente:

$ git branch
* master
  mybranch

mybranch já está criado (é uma cópia exata de master) but não está ainda obtido na sua cópia de trabalho. Para mudar para esse branch, use git checkout:

$ git checkout mybranch
Switched to branch 'mybranch'

E mostre os branches novamente:

$ git branch
  master
* mybranch

Agora, toda mudança salva com git commit irá no branch mybranch, não master.

Note

O comando git checkout tem muitos usos diferentes, não apenas obter um branch diferente. Ele pode ser usado, por exemplo, para obter uma revisão antiga de um determinado arquivo.

Finalmente, e se você quiser trazer as mudanças para o branch master? Primeiro, precisamos mudar (checkout) para o branch que irá receber as modificações:

$ git checkout master
Switched to branch 'master'

E executamos o comando git merge:

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

Note

Você percebeu a linha Fast-forward? É o algoritmo utilizado por git merge. Git usa diferentes algoritmos de merge, dependendo do caso. git merge é um comando muito poderoso com muitas opções. Vale a pena ler seu manual.

Outras páginas

Esse tutorial é apenas um ponto de partida para uma ferramenta tão poderosa. É altamente recomendável que você leia a documentação oficial do Git que tem muitas informações e também os manuais dos comandos que você está utilizando.

Além disso, existe um livro chamado Pro Git book que percorre desde aspectos básicos até os mais avançados. É altamente recomendado!

Finalmente, quando tiver uma dúvida, procure pelas soluções na web. Muitas pessoas usam git e é provável que alguém já tenha postado algo em um fórum, fazendo as mesmas perguntas que você.

Boa sorte!

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, you have a bare repository somewhere (e.g. www.github.com), 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 (those 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 from modifications made in a.git, do:

$ cd /tmp/b.git
$ git fetch a.git

This thread in the git mailing list helped me.

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 project websites. Why keeping a bare repository that tracks another repository? They are organized this way:

+----------+
| upstream |
+----------+
      |
      v
+----------+
|  origin  |
+----------+
      ^
      |
      v
+----------+
|   work   |
+----------+

So, in this diagram "upstream" is the repository of original project maintaners in websites like SourceForge or github. "origin" is a bare in my personal server that clones upstream and fetches new changes from it. It is where I push my own branches to. "work" is my local working copy.

The problem appears when I fetch stuff from "upstream". I use git-fetch(1):

$ git fetch upstream

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(1) 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 just can't rebase or merge remote branches, like I'd do in a working copy repository. And when you use the git-fetch(1) without specifying any other argument, it will not update refs. So, you can 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(1):

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

Working with bundles

Sometimes you do not have an internet connection to push and pull repositories. To move your data using other media (e-mail, 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, enter your repository:

$ cd myrepo

Then create the bundle with all the branches, tags, (all the refs) etc.:

$ git bundle create myrepo.bundle --all

It will create a bundle with all the branches. Later, to clone it to a workable repository, do:

$ git clone myrepo.bundle -b master myrepo

This will create the myrepo directory with the master branch. You then fetch other branches:

git fetch ../myrepo.bundle mybranch:mybranch

See this page on the Pro Git book for more information on git bundle. See this for a workaround on how to fetch all the refs from bundle to your new working copy.

Deleting a remote branch

If your remote branch is called "foo", just:

$ git push origin :foo

Check remote: *** Deleting a branch is not allowed in this repository if you get an error like that.

Troubleshooting

This file 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

After cloned, modified, commited, I wanted to pull the modifications to the server, 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

After checking this question link in stackoverflow, I got the answer.

I had to change the description file of the repository, which means that I should provide a short description for it. I took some time to discover that it is not the .git/description file but actually the repository/config/description file.

Other reference: http://blog.riopro.com.br/2009/05/05/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 can find git in the PATH. In my problem, they were installed from pkgsrc, which by default installs stuff in /usr/pkg.

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

See this reference.

error: patch failed .. Applying: .. trailing whitespace when svn rebase

Maybe you get this error:

Applying: <Commit messasge>
.../cla-sisrot-trunk/.git/rebase-apply/patch:21: trailing whitespace.

When svn rebase, 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

Check the git-config(1) man page for more information.

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

After a git svn rebase, you got 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. According to this reference that should be safe to just ignore the error messages. So, just resume the rebase:

git rebase --skip

It might be a good idea to run git diff to check if the merge worked fine.

push says "Everything up-to-date", but it is not

When you git push, you may get the message "Everything up to date", but git log on your local repository and in the remote bare repository are different.

In my case, I was trying to push branch master. Is this the branch I'm commiting changes to? Let's see with git branch:

$ git branch
* (no branch)
  master

For any reason, my changes are being commited to no branch and the master branch still has old code (which is in sync with the repository I'm trying to push to). To solve this problem, do:

$ git checkout -b temp
$ git checkout master
$ git merge temp
$ git branch -d temp

This will track changes you commited to no branch in a temporary branch, merge them in master and delete the temporary branch. Now git push probably works.

"warning: remote HEAD refers to nonexistent ref, unable to checkout."

This error is caused when cloning from a bundle (check git-bundle(1) for details). The clone was made, but git cannot checkout a working tree.

According to these page, 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.

According to this page, you need to login to your remote machine and disable the hook with:

$ git --git-dir /path/to/yourrepo.git config --bool hooks.allowdeletebranch true