Git
Chapters ▾ 2nd Edition

3.1 Branches no Git - Branches em poucas palavras

Quase todo Sistema de Controle de Versionamento tem alguma forma de suporte a ramificações (Branches). Ramificação significa que você diverge da linha principal de desenvolvimento e continua a trabalhar sem alterar essa linha principal. Em muitas ferramentas de versionamento, este é um processo um tanto difícil, geralmente exigindo que você crie uma nova cópia do diretório do código-fonte, o que pode demorar muito em projetos maiores.

Algumas pessoas se referem ao modelo de ramificação do Git como seu “recurso matador” e certamente diferencia o Git na comunidade de sistemas de versionamento. Por que isso é tão especial? A forma como o Git cria branches é incrivelmente leve, tornando as operações de ramificação quase instantâneas, alternando entre os branches geralmente com a mesma rapidez. Ao contrário de muitos outros sistemas, o Git incentiva fluxos de trabalho que se ramificam e se fundem com frequência, até mesmo várias vezes ao dia. Compreender e dominar esse recurso oferece uma ferramenta poderosa e única e pode mudar totalmente a maneira como você desenvolve.

Branches em poucas palavras

Para realmente entender como o Git trabalha com Branches, precisamos dar um passo atrás e examinar como o Git armazena seus dados.

Como você deve se lembrar de << ch01-introdução >>, o Git não armazena dados como uma série de mudanças ou diferenças, mas sim como uma série de snapshots (instantâneos de um momento) .

Quando você faz um commit, o Git armazena um objeto de commit que contém um ponteiro para o snapshot do conteúdo que você testou. Este objeto também contém o nome do autor e o e-mail, a mensagem que você digitou e ponteiros para o commit ou commits que vieram antes desse commit (seu pai ou pais): sem pai para o commit inicial, um pai para um commit normal, e vários pais para um commit que resulta de uma fusão de dois ou mais branches.

Para verificar isso, vamos assumir que você tem um diretório contendo três arquivos, e você seleciona todos eles e efetua o commit. Ele Prepara os arquivos e calcula uma verificação para cada um (o hash SHA-1 que mencionamos em << ch01-introdução >>), armazena essa versão do arquivo no repositório Git (Git se refere a eles como blobs), e adiciona esse hash de verificação à área de preparação (staging area):

$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

Quando você faz um commit executando git commit, o Git verifica cada subdiretório (neste caso, apenas o diretório raiz do projeto) e armazena esses objetos no repositório do Git. O Git então cria um objeto de commit que possui os metadados e um ponteiro para a raiz do projeto para que ele possa recriar aquele snapshot quando necessário.

Seu repositório Git agora contém cinco objetos: um blob para o conteúdo de cada um dos seus três arquivos, uma árvore que lista o conteúdo do diretório e especifica quais nomes de arquivo são armazenados e quais seus blobs e um commit com o ponteiro para essa árvore e todos os metadados de commit.

A commit and its tree.
Figure 9. Um commit e sua árvore

Se você fizer algumas mudanças e confirmar novamente, o próximo commit armazena um ponteiro para o commit que veio imediatamente antes dele.

Commits and their parents.
Figure 10. Commits e seus pais

Um branch no Git é simplesmente um ponteiro móvel para um desses commits. O nome do branch padrão no Git é master. Conforme você começa a fazer commits, você recebe um branch master que aponta para o último commit que você fez. Cada vez que você faz um novo commit, ele avança automaticamente.

Note

O branch ' master ' no Git não é um branch especial. É exatamente como qualquer outra ramificação. A única razão pela qual quase todo repositório tem um é que o comando git init o cria por padrão e a maioria das pessoas não se preocupa em alterá-lo.

A branch and its commit history.
Figure 11. Um branch e seu histórico de commits

Criando um Novo Branch

O que acontece se você criar um novo branch? Bem, fazer isso cria um novo ponteiro para você mover. Digamos que você crie um novo branch chamado: testing. Você faz isso com o comando git branch :

$ git branch testing

Isso cria um novo ponteiro para o mesmo commit em que você está atualmente.

Two branches pointing into the same series of commits.
Figure 12. Duas branches apontando para a mesma série de commits

Como o Git sabe em qual branch você está atualmente? Ele mantém um ponteiro especial chamado HEAD. Note que isso é muito diferente do conceito de HEAD em outros sistemas de versionamento com os quais você pode estar acostumado, como Subversion ou CVS. No Git, isso é um ponteiro para o branch local em que você está. Neste caso, você ainda está em master. O comando git branch apenas criou um novo branch - ele não mudou para aquele branch.

HEAD pointing to a branch.
Figure 13. HEAD apontando para um branch

Você pode ver isso facilmente executando um simples comando git log que mostra para onde os ponteiros do branch estão apontando. Esta opção é chamada de --decorate.

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project

Você pode ver os branches master e testing que estão bem ali ao lado do commit f30ab.

Alternando entre Branches

Para mudar para um branch existente, você executa o comando git checkout. Vamos mudar para o novo branch testing:

$ git checkout testing

Isso move o HEAD e o aponta para o branch testing.

HEAD points to the current branch.
Figure 14. HEAD aponta para o branch atual

O que isso significa? Bem, vamos fazer outro commit:

$ vim test.rb
$ git commit -a -m 'made a change'
The HEAD branch moves forward when a commit is made.
Figure 15. O branch do HEAD avança quando um commit é feito

Isso é interessante, porque agora seu branch testing avançou, mas seu branch master ainda aponta para o commit em que você estava quando executou git checkout para alternar entre os branches. Vamos voltar para o branch master:

$ git checkout master
HEAD moves when you checkout.
Figure 16. O HEAD se move quando você faz o checkout

Esse comando fez duas coisas. Ele moveu o ponteiro HEAD de volta para apontar para o branch master, e reverteu os arquivos em seu diretório de trabalho de volta para o snapshot para o qual master aponta. Isso também significa que as alterações feitas a partir deste ponto irão divergir de uma versão mais antiga do projeto. Essencialmente, ele retrocede o trabalho que você fez em seu branch testing para que você possa ir em uma direção diferente.

Note
A troca de branches muda os arquivos em seu diretório de trabalho

É importante notar que quando você muda de branches no Git, os arquivos em seu diretório de trabalho mudam. Se você mudar para um branch mais antigo, seu diretório de trabalho será revertido para se parecer com a última vez que você fez commit naquele branch. Se o Git não puder fazer, ele não permitirá que você faça a troca.

Vamos fazer algumas mudanças e confirmar novamente:

$ vim test.rb
$ git commit -a -m 'made other changes'

Agora o histórico do seu projeto divergiu (consulte Histórico de diferenças). Você criou e mudou para um branch, fez algum trabalho nele e, em seguida, voltou para o seu branch principal e fez outro trabalho. Ambas as mudanças são isoladas em branches separados: você pode alternar entre os branches e mesclá-los quando estiver pronto. E você fez tudo isso com comandos simples branch, checkout e commit.

Divergent history.
Figure 17. Histórico de diferenças

Você também pode ver isso facilmente com o comando git log. Se você executar git log --oneline --decorate --graph --all, ele mostrará o histórico de seus commits, exibindo onde estão seus ponteiros de branch e como seu histórico divergiu.

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

Como um branch no Git é na verdade um arquivo simples que contém a verificação SHA-1 de 40 caracteres do commit para o qual ele aponta, branches são fáceis de criar e destruir. Criar um novo branch é tão rápido e simples quanto escrever 41 bytes em um arquivo (40 caracteres e uma nova linha).

Isso está em nítido contraste com a forma como as ferramentas de versionamento mais antigas se ramificam, o que envolve a cópia de todos os arquivos do projeto em um segundo diretório. Isso pode levar vários segundos ou até minutos, dependendo do tamanho do projeto, enquanto no Git o processo é sempre instantâneo. Além disso, como estamos gravando os pais quando fazemos o commit, encontrar uma base adequada para a mesclagem é feito automaticamente para nós e geralmente é muito fácil de fazer. Esses recursos ajudam a incentivar os desenvolvedores a criar e usar branches com frequência.

Vamos ver por que você deve fazer isso.

scroll-to-top