Entendendo os branches remotos no Git

O Git foi pensado para ser ágil quando o assunto é criar, comparar, trocar e unificar branches, inclusive você é estimulado a usá-los sempre que possível.

Um branch no Git é um ponteiro para um commit, sim, somente um ponteiro. Isto torna as operações com branches muito rápidas. Tudo o que o git precisa saber é o commit inicial e percorrer seus ancestrais para formar o workind directory.

Existem dois tipos de branches: locais e remotos. Antes de falarmos sobre branches remotos, vamos entender como funcionam os branches locais.

Branches locais

Por padrão um branch chamado master é criado quando um novo repositório é iniciado com o comando git init. Vamos supor que você criou seu repositório e fez dois commits, assim você terá a estrutura abaixo.

     master

1 -- 2

A primeira forma de criar um novo branch é com o comanto git branch <nome>. Vamos criar um branch chamado novo-botao.

git branch novo-botao
git checkout novo-botao

Neste momento você tem a seguinte estrutura no seu repositório.

     master

1 -- 2

novo-botao

Mas como o git sabe em qual branch você está trabalhando? Através da mesma técnica dos branches: um ponteiro.

Existe um ponteiro chamado HEAD que assim como um branch, aponta para um commit. O ponteiro HEAD aponta para o commit atual do seu workind directory, já o branch aponta para o último commit naquele branch.

Saiba mais sobre o working directory em As três árvores do Git

     master ← HEAD

1 -- 2

novo-botao

Este comando git branch cumpre o que promete: cria um novo branch, e somente isto. Portanto é necessário usar o comando git checkout <nome> para trocar de branch.

     master

1 -- 2

novo-botao ← HEAD

Você pegar um atalho e criar um branch e ao mesmo tempo o acessar. Use o argumento -p no comando checkout, assim o comando ficará git checkout -b <nome>.

Você mudou para o novo branch e já pode começar a registar seus commits.

     master

1 -- 2
\
3 -- 4

HEAD → novo-botao

Não é incomum surgir a necessidade de parar o trabalho atual por um momento para trabalhar em outro assunto mais urgente. Então você muda para o branch master para não misturar as alterações.

git checkout master
     master ← HEAD

1 -- 2
\
3 -- 4

novo-botao

A partir do momento em que registar alguma alteração no branch master, as duas histórias ficarão divergentes e cada vez que acessar um branch, você terá arquivos diferentes no seu working directory, pois ambos branches tem alterações diferentes.

        HEAD → master

1 -- 2 -- 5 -- 6
\
3 -- 4

novo-botao

Terminado seu trabalho na master, você volta para o branch novo-botão e registra mais um commit.

               master

1 -- 2 -- 5 -- 6
\
3 -- 4 -- 5

HEAD → novo-botao

Você terminou seu trabalho no branch novo-botao e quer colocar estas alterações no branch master para serem publicadas.

Unificando histórias

O comando mais comum para unir duas histórias (ou branches se preferir) é o merge, com este comando você, a pardir do branch que está no momento, indica qual branch será unificado.

Se quiser colocar as alterações de novo-botao no branch master você precisa mudar para o branch master e em seguida solicitar a união do novo-botao ao Git.

git checkout master
git merge novo-botao

E nasceu um tipo especial de commit: o commit de merge. A diferença deste commit para um commit comum é que este tem dois ancestrais, um em master e outro em novo-botao.

Geralmente um commit de merge tem apenas a referência para seus ancestrais, mas também pode conter alterações neste commit, comom a resolução de um conflito.

             HEAD → master

1 -- 2 -- 5 -- 6 -- 7*
\ /
3 -- 4 -- 5

novo-botao

Com o trabalho finalizado em novo-botao, você pode removê-lo com o comando git branch -d novo-botao.

Tudo isto aconteceu localmente (outro superpoder que torna o Git rápido), mas em um cenário comum você estará sincronizando seu trabalho em um repositório comum para outras pessoas, que também irão sincronizar o trabalho delas.

Neste momento os branches remotos começam a brilhar mas antes você precisa conhecer o conceito de repositórios remotos.

Repositórios remotos

O Git é um controle de versão distribuído, portanto as alterações acontecem localmente por padrão e você pode sincronizar seu repositório com qualquer outro (com histórico em comum), inclusive mais de um repositório.

Com isto aparace o conceito de repositórios remotos. Internamente, o Git armazena o endereço de outro repositório (geralmente um endereço http ou ssh) e para que você o identifique de forma fácil, é vinculado a um apelido. Quando um repositório é clonado, o Git apelida este repositório de origin.

Ao obter os commits de um repositório remoto, um tipo especial de branch é criado para identificar os branches do reposistório remoto. Estes branches são somente leitura e tem o formato <remoto>/<nome>.

Como o Git cria o repositório remoto chamado origin, a branch master deste repositório tem o nome de origin/master.

Lembra que o Git trabalha localmente por padrão? Assim depois de sincronizar os commits do repositório remoto com seu repositório local, você pode efetuar comparações e até obter o histórico deste repositório, com todas operações sendo locais, fantástico não?

Agora que você já sabe sobre o novo tipo de branch, vamos ilustrar sua estrutura?

Branches remotos

Quando começa a trabalhar em um repositório compartilhado, você geralmente faz um clone do repositório com o comando git clone <endereço>.

Imagine que o repositório remoto tem a estrutura abaixo.

          master

1 -- 2 -- 3

Ao cloná-lo, você terá a seguinte estrutura localmente.

   HEAD → origin/master

master

1 -- 2 -- 3

E outra pessoa também clonou o repositório e registrou seus commits no repositório local dela.

REMOTO

master

1 -- 2 -- 3
VOCÊ

origin/master

master

1 -- 2 -- 3
OUTRA PESSOA

origin/master

1 -- 2 -- 3 -- 4 -- 5 -- 6

HEAD → master

Um cenário muito comum é outras pessoas sincronizarem suas alterações antes de você, deixando o repositório REMOTO diferente do que você havia obtido.

REMOTO

master

1 -- 2 -- 3 -- 4 -- 5 -- 6
VOCÊ

origin/master

master

1 -- 2 -- 3

O ideal é você obter os commits do repositório remoto constantemente, mas para não perder o raciocínio do trabalho você registra seus commits normalmente.

REMOTO

master

1 -- 2 -- 3 -- 4 -- 5 -- 6
VOCÊ

HEAD → master

1 -- 2 -- 3 -- 7 -- 8 -- 9

Ao tentar enviar suas alterações com o comando git push origin, o git irá te avisar de que existem alterações não sincronizadas, então você executa o comando git pull origin.

O comando pull é feito em duas etapas, a primeira é executar o comando fetch seguido do comando merge.

Na prática seria o equivalente a git fetch origin e git merge origin/master

Na etapa do fetch interno seu repositório local tem as alterações do repositório principal, identificadas pelo branch especial origin/master.

VOCÊ

origin/master

1 -- 2 -- 3 -- 4 -- 5 -- 6
\
7 -- 8 -- 9

HEAD → master

Ao ser executado na sequência o comando merge unifica as duas histórias que se divergiram, através de um commit de merge (aquele que tem dois ancestrais).

VOCÊ

HEAD

origin/master master
↓ ↓
1 -- 2 -- 3 -- 4 -- 5 -- 6 -- 10*
\ /
7 --- 8 --- 9 ---

Feito o merge, você já pode enviar suas alterações para o repositório remoto com o comando git push origin novamente.

REMOTO

master

1 -- 2 -- 3 -- 4 -- 5 -- 6 -- 10*
\ /
7 --- 8 --- 9 ---
VOCÊ

HEAD → origin/master → master

1 -- 2 -- 3 -- 4 -- 5 -- 6 -- 10*
\ /
7 --- 8 --- 9 ---

Conclusão

Neste texto vimos o que são branches, como criar um e como unificar as histórias que se divergem através do comando merge, tudo isso de forma local (sem necessitar de uma conexão de rede).

Descobrimos o que são repositórios remotos, como são armazenados pelo Git e o tipo especial de branches que são criados no seu repositório local. Para sincronizar os repositórios, aprendemos sobre os comandos pull e push.

Gostou? Deixe um comentário sobre o que achou. Se tiver alguma dúvida, comente aqui que terei o prazer de te ajudar.

Até mais.