Git

Introdução

O Git (o “g” é pronunciado como na palavra gato e não como “jit”) é um programa para controle de versão distribuído desenvolvido por Linus Torvalds (o criador do Linux) e mantido por Junio C Hamano. A página do git em http://git-scm.com/ tem vários tutoriais e manuais. Um bom tutorial rápido é http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html.

Instalação

Para instalar o git no Ubuntu execute o seguinte comando.

apt-get install git-core

É uma boa idéia configurar o git para usar o seu nome e email:

git config --global user.name "Seu Nome"
git config --global user.email "seu@email.com.br"

Configuração

Usando abreviações

Eu sugiro que você coloque algo como a linha abaixo no seu ~/.bashrc.

export repos=git@github.com:kroger/

Desse modo você poderá baixar um projeto de uma maneira mais fácil:

git clone $repos/genos-bib.git

O arquivo config

Você pode configurar o arquivo .git/config de um repositório local para usar nomes abreviados. Isso é particularmente útil se está usando mais de um repositório ou ramos diferentes.

cat >>.git/config <<EOF
[remote "public-repo"]
       url = ssh://yourserver.com/~you/proj.git
EOF

Você pode fazer a mesma coisa com git remote:

git remote add public-repo ssh://example.com/project.git

Saída colorida

Se você é viciado em saída colorida vai querer executar os comandos abaixo:

git config --global color.diff auto
git config --global color.status auto
git config --global color.branch auto

Comandos básicos

Para gravar uma mudança é necessário primeiro adicioná-la ao index. Modifique alguns arquivos e então adicione seu conteúdo atualizado ao index:

 git add arquivo1 arquivo2 arquivo3

Para ver um sumário da situação do repositório utilize:

 git status

Você então deve gravar suas mudanças com:

 git commit -m "mensagem descrevendo o commit"

Caso queira adicionar todos os arquivos modificados pode fazer:

 git commit -a -m "mensagem descrevendo o commit"

Use o comando abaixo para enviar suas mudanças para o repositório central:

 git push

Para atualizar sua cópia local, ou seja, para baixar as mudanças que outros tenham feito no repositório, use os comandos:

 git pull --rebase

onde <branch remoto> é o branch remoto onde o desenvolvimento acontece. Provavelmente é master.

Para reverter as mudanças que não foram enviadas use

 git checkout -f

Repositórios remotos

O git tem um modo interno muito inteligente para lidar com repositórios remotos. Cada repositório pode guardar “ponteiros” para outros repositórios (chamados de remotes na documentação), e o git tem comandos simples para enviar e receber ramos (“branches”) desses repositórios. Como o git é bem flexível, vale a pena descrever alguns fluxos de trabalho.

Exportando um repositório local

Às vezes se começa a trabalhar em um repositório, se faz coisas e se quer pegar essas coisas, colocar em um repositório remoto e manter os dois atualizados.

A primeira coisa a se fazer nesse caso é criar o repositório remoto. Para fazer isso, vá ao lugar remoto onde vai ficar o repositório, crie um diretório com o nome desejado, vá para esse diretório e execute

git init

Isso vai inicializar um repositório git vazio no lugar escolhido. Depois, vá para o seu repositório local e execute

git remote add <nome-remoto> <caminho-para-o-repo-remoto>
git push <nome-remoto> refs/heads/master:refs/heads/master

onde <nome-remoto> é o nome que você deseja dar para o repositório remoto (o nome padrão pra isso é origin) e <caminho-para-o-repo-remoto> é o caminho onde foi colocado o outro repo. Se ele é local, basta um ../foo/bar, se ele está num servidor com suporte ao protocolo git o endereço é parecido com git://servidor/repo.git, e se ele está num servidor com ssh o caminho é algo comossh://usuario@servidor/caminho/repo.git. Depois desses comandos um

git pull <nome-remoto>

faz o pull das modificações no repo remoto.

Clonando um repositório remoto

O procedimento para clonar um repo remoto está descrito na seção #Repositórios do Genos.

Repositórios do Genos

O genos mantém diversos repositórios listados no github. É possível baixar um repositório somente leitura com algo como:

git clone git://github.com/kroger/genos-bib.git

ou com

git clone http://github.com/kroger/genos-bib.git

Além disso é possível criar uma conta própria no github e fazer um fork de um repositório do Genos.

Pull versus Rebase

Existem dois jeitos de importar as mudanças de um repositório remoto pro repositório local. Elas são o “pull” e o “rebase”. A diferença é a forma como as mudanças locais são mantidas na hora de importar as remotas.

Imagine uma árvore de commits assim (onde local é o branch atual de desenvolvimento):

                    A---B---C local
                   /
              D---E---F---G master

Após um “git pull” aplicado no computador local, a árvore fica:

                    A---B---C---F'--G' local
                   /           /
              D---E---F---G ----master

Após um git push, a árvore no repositório remoto fica

                    A---B---C---F'--G'--
                   /           /        \
              D---E---F---G -------------A'--B'--C'--F--G  master

Isso não é muito interessante por serem feitos, ao todo, dois merges das mudanças locais no repositório remoto. Com mais de duas pessoas fazendo mudanças ao mesmo tempo esse método gera árvores de histórico complicadas e difíceis de entender depois, com vários merges.

Uma alternativa é usar “git pull – -rebase” em vez de “git pull”. Fazendo isso, a árvore local, que era

                    A---B---C local
                   /
              D---E---F---G master

fica

                            A'---B'---C' local
                           /
              D---E---F---G master

Após um “git push”, a árvore do servidor fica

                            A'---B'---C'-
                           /             \
              D---E---F---G ---------------  master

ou

              D---E---F---G---A'--B'--C'-----  master

o que é muito mais limpo. Mesmo com várias pessoas desenvolvendo isso tende a gerar históricos mais lineares.

Escrevendo boas mensagens de commit

Além de usar a opção -m para indicar a mensagem de commit, você também pode digitar apenas git commit onde o git vai abrir o editor padrão para você digitar a mensagem de commit. Em geral o editor padrão é o vi. Eu sugiro que você coloque a linha abaixo no seu ~/.bashrc:

 export EDITOR="emacs"

Desse modo o git sempre vai abrir o emacs para pedir a mensagem de commit.

Em geral uma mensagem de commit tem o seguinte formato:

primeira linha

texto mais longo texto mais longo texto mais longo
texto mais longo texto mais longo texto mais longo
texto mais longo texto mais longo texto mais longo

Onde a primeira linha é um breve sumário da modificações, o texto mais longo contém mais detalhes. Observe que elas são separadas por uma linha em branco.

Usando diferentes ramos (branches)

Para ver os ramos do repositório basta usar o comando

git branch

Para mudar de ramo usa-se o comando

git checkout

Mudando do ramo master para novo-ramo:

git checkout novo-ramo

Conferindo o novo ramo:

git branch
  master
* novo-ramo

Para criar um novo ramo e mudar para ele automaticamente usa-se

git checkout -b novo-ramo

Para ver a lista de commits no novo-ramo em relação a master (pode-se usar a opção -p para ver um diff do código):

git log  master..novo-ramo

Para enviar um novo ramo para o repositório remoto:

git push origin ramo-local:ramo-remoto

Talvez você tenha que usar o caminho completo para o ramo, como em:

git push origin refs/heads/ramo-local:refs/heads/ramo-remoto

Para listar os ramos em um repositório remoto

git branch -r

Para apagar um ramo remoto:

git push origin :refs/heads/<ramo remoto>

Para baixar um ramo no repositório remoto usa-se git branch com a opção -track, indicando qual o nome do ramo local e o nome do ramo remoto. É uma boa prática ter o mesmo nome para os ramos local e remoto.

git branch --track novo-ramo origin/novo-ramo

O nome origin nada mais é que um alias para a localização de um repositório. Essa informação fica armazenada no arquivo .git/config dentro do repositório. O trecho do .git/config referente a configuração de origin pode ser vista abaixo:

[remote "origin"]
       url = ssh://cons@genos.mus.br/repos/teste.git
       fetch = +refs/heads/*:refs/remotes/origin/*

Para manter os ramos atualizados, git pull e git push devem ser suficientes. O Git mantém cada ramo separado sem interferir no outro. Contudo, mudanças sem commit vão aparecer em todos os ramos.

Para mesclar as mudanças do branch master no seu ramo é só fazer:

git pull

Escolhendo commits específicos

Você pode aplicar um commit específico na sua árvore com cherry-pick. No exemplo abaixo estamos no branch master e o commit 85cd08ee1aec0fbd3cf3d696a70872639e59212f aconteceu no branch novo-ramo. Ele vai aplicar apenas esse commit em master.

git cherry-pick 85cd08ee1aec0fbd3cf3d696a70872639e59212f

Isso é útil quando você corrigiu um bug em um ramo de desenvolvimento e quer replica-lo no ramo principal.

Resolvendo conflitos

Quando um merge não é resolvido automaticamente pelo git ele indica isso claramente. Você não vai conseguir dar um commit. Tanto commit quanto status vão mostrar os arquivos que precisam resolver os conflitos:

git commit
file.txt: needs merge

Git vai marcar os conflitos no arquivo usando marcadores de conflito. Abaixo podemos ver um conflito marcado com duas versões:

<<<<<<< variant A
Uma versão
>>>>>>> variant B
Outra versão
======= end

Tudo que você precisa fazer é editar o arquivo para resolver os conflitos e dar um commit com:

git add file.txt
git commit

Se você usa emacs pode usar o M-x vc-resolve-conflicts para lhe auxiliar na resolução de conflitos (ele funciona como o ediff).

Revisões antigas

Você pode ver uma versão antiga de um arquivo com cat-file:

git cat-file -p tags/v1.4.3:git.c
git cat-file -p f5f75c652b9c2347522159a87297820103e593e4:git.c

Enviando patches

Para criar patches com suas modificações você deve usar o format-patch. Esse comando vai gerar uma série de arquivos numerados, como 0001-mais-stuff.patch. Você deve enviar esses arquivos para a lista de discussão apropriada.

git format-patch origin/master

Se a quantidade de patches for muito grande, você pode instalar e usar o git-send-email, caso contrário é ok usar seu cliente de email favorito.

Para instalar o git-send-email em Debian/Ubuntu faça

sudo apt-get install git-email

Para enviar os emails é interessante usar o SendEmail. Para instalá-lo em Debian/Ubuntu:

sudo apt-get install sendemail

Para configurar o sendemail em seu git use os comandos abaixo adaptando para seu servidor de e-mail:

git config --global sendemail.smtpserver smtp.gmail.com
git config --global sendemail.smtpserverport 587
git config --global sendemail.smtpencryption tls
git config --global sendemail.smtpuser your_email@gmail.com

Após criar os patches com git format-patch envie:

git send-email 000n-nome-do-patch.patch

O sistema pedirá que preencha informações triviais como destinatário e senha do e-mail.

Aplicando patches

Para aplicar um patch faça

git am 0001-nome-do-patch.patch

Se recebê-lo por e-mail via git send-email, você pode salvar a mensagem em um arquivo local e aplicá-lo com git am. É preciso observar se há algo a mais na primeira linha do patch, que deve ter essa aparência:

Date: Sun, 18 Apr 2010 14:17:36 -0300
From: Pedro Kroger <pedro.kroger@gmail.com>
To: mdsmus@gmail.com
Cc: Pedro Kroger <pedro.kroger@gmail.com>
Subject: [PATCH 1/2] fix bug in way note value is computed
X-Mailer: git-send-email 1.7.0.4

the variable notes only had the notes C, D, E, F, G, A, and B, so
notes.index(note) would return things like 2 for 'E', while it should
have been 4.
---
 humdrum.py      |   11 +++++------
 test_humdrum.py |   20 +++++++-------------
 2 files changed, 12 insertions(+), 19 deletions(-)

diff --git a/humdrum.py b/humdrum.py
index 8712e8d..e8771c8 100755
--- a/humdrum.py
+++ b/humdrum.py
@@ -8,7 +8,7 @@ import utils as u

 ## regular expression to **pitch notes
-notes_regex = re.compile('([0-9.]+)([a-gA-G])([b#]+)?([0-9]*)')
+notes_regex = re.compile('([0-9.]+)([a-gA-G]|r)([b#]+)?([0-9]*)')

 def parse_accidentals(acc):
@@ -28,18 +28,17 @@ def parse_pitch(line):

     >>> spine = '**pitch\n4Eb4\n8F##3\n8C4\n*-'
     >>> [parse_pitch(line) for line in spine.split('\n')]
-    ['**pitch', 61,53, 60, '*-']
+    ['**pitch', 63,55, 60, '*-']
     """

-    notes = "C D E F G A B".split()
+    notes = "C . D . E F . G . A . B".split()

     if line.startswith("**pitch"):
         return "**midi"
     elif (line.startswith("!") or line.startswith("*") or
-          line.startswith("=") or line.startswith(".")):
+          line.startswith("=") or line.startswith(".") or
+          line == ):
         return line
-    elif line == :
-        return
     else:
         dur, note, acc, oct = notes_regex.search(line).group(1, 2, 3, 4)
         accidentals = parse_accidentals(acc) if acc else 0
diff --git a/test_humdrum.py b/test_humdrum.py
index 1993ba9..cf82450 100755
--- a/test_humdrum.py
+++ b/test_humdrum.py
@@ -15,16 +15,10 @@ def test_parse_accidentals():

 def test_parse_pitch():
-    notes_regex = re.compile('([0-9.]+)([a-gA-G])([b#]+)?([0-9]*)')
-    l1 = "**pitch"
-    l2 = "4Eb4"
-    l3 = "16F##4"
-    l4 = "."
-    l5 = "*-"
-    l6 = "="
-    assert h.parse_pitch(l1) == "**midi"
-    assert h.parse_pitch(l2) == 61
-    assert h.parse_pitch(l3) == 65
-    assert h.parse_pitch(l4) == '.'
-    assert h.parse_pitch(l5) == '*-'
-    assert h.parse_pitch(l6) == '='
+    assert h.parse_pitch("**pitch") == "**midi"
+    assert h.parse_pitch("4C4") == 60
+    assert h.parse_pitch("4Eb4") == 63
+    assert h.parse_pitch("16F##4") == 67
+    assert h.parse_pitch(".") == '.'
+    assert h.parse_pitch("*-") == '*-'
+    assert h.parse_pitch("=") == '='
--
1.7.0

Trabalhando com forks no Github

A melhor forma de atualizar um determinado branch de um fork no github é:

git pull
git checkout nome-do-branch
git pull
git merge master
git push