Chapters ▾ 2nd Edition

3.2 Ramificação (Branching) no Git - Ramificação (Branching) e Mesclagem (Merging) Básicas

Ramificação (Branching) e Mesclagem (Merging) Básicas

Vamos passar por um exemplo simples de ramificação (branching) e mesclagem (merging) com um fluxo de trabalho que você pode usar no mundo real. Você seguirá as seguintes etapas:

  1. Fazer algum trabalho em um site.

  2. Criar um branch para uma nova história de usuário (user story) na qual você está trabalhando.

  3. Fazer algum trabalho naquele branch.

Nesta fase, você receberá uma chamada informando que outro problema é crítico e você precisa de um hotfix. Você fará o seguinte:

  1. Mudar para o seu branch de produção.

  2. Criar um branch para adicionar o hotfix.

  3. Depois que for testado, mesclar (merge) o branch de hotfix e enviar (push) para produção.

  4. Mudar de volta para a sua história de usuário original e continuar trabalhando.

Ramificação Básica

Primeiro, digamos que você esteja trabalhando no seu projeto e já tenha alguns commits no branch master.

Um histórico de commits simples
Figure 18. Um histórico de commits simples

Você decidiu que vai trabalhar na issue #53 em qualquer sistema de rastreamento de problemas (issue-tracking) que a sua empresa utilize. Para criar um novo branch e mudar para ele ao mesmo tempo, você pode executar o comando git checkout com a chave (switch) -b:

$ git checkout -b iss53
Switched to a new branch "iss53"

Isso é um atalho para:

$ git branch iss53
$ git checkout iss53
Criando um novo ponteiro de branch
Figure 19. Criando um novo ponteiro de branch

Você trabalha no seu site e faz alguns commits. Fazer isso move o branch iss53 para frente, porque você fez checkout dele (ou seja, seu HEAD está apontando para ele):

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
O branch `iss53` avançou com o seu trabalho
Figure 20. O branch iss53 avançou com o seu trabalho

Agora você recebe a chamada de que há um problema com o site e você precisa corrigi-lo imediatamente. Com o Git, você não precisa implantar (deploy) a sua correção junto com as alterações da iss53 que você fez, e não precisa fazer muito esforço para reverter essas alterações antes de poder trabalhar na aplicação da sua correção ao que está em produção. Tudo o que você precisa fazer é mudar de volta para o seu branch master.

No entanto, antes de fazer isso, observe que se o seu diretório de trabalho ou a área de preparação tiverem alterações não commitadas que conflitam com o branch para o qual você está fazendo o checkout, o Git não deixará você mudar de branch. É melhor ter um estado de trabalho limpo quando você muda de branch. Existem maneiras de contornar isso (como, por exemplo, o stashing e a alteração de commits) que abordaremos mais tarde, em Fazendo Stash e Limpando. Por enquanto, vamos assumir que você fez commit de todas as suas alterações, então você pode mudar de volta para o seu branch master:

$ git checkout master
Switched to branch 'master'

Neste ponto, o diretório de trabalho do seu projeto está exatamente da mesma forma que estava antes de você começar a trabalhar na issue #53, e você pode se concentrar no seu hotfix. Este é um ponto importante a ser lembrado: quando você muda de branch, o Git redefine (resets) seu diretório de trabalho para que ele fique como na última vez em que você fez commit naquele branch. Ele adiciona, remove e modifica arquivos automaticamente para garantir que a sua cópia de trabalho seja exatamente como o branch estava no seu último commit nele.

Em seguida, você tem um hotfix a fazer. Vamos criar um branch hotfix no qual trabalharemos até que seja concluído:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
 1 file changed, 2 insertions(+)
Branch hotfix baseado no `master`
Figure 21. Branch hotfix baseado no master

Você pode executar os seus testes, certificar-se de que o hotfix é o que você deseja, e, finalmente, mesclar o branch hotfix de volta para o seu branch master para implantar na produção. Você faz isso com o comando git merge:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Você notará a frase “fast-forward” (avanço rápido) nessa mesclagem. Como o commit C4 apontado pelo branch hotfix que você mesclou estava diretamente à frente do commit C2 em que você está, o Git simplesmente move o ponteiro para a frente. Em outras palavras, quando você tenta mesclar um commit com um commit que pode ser alcançado seguindo o histórico do primeiro commit, o Git simplifica as coisas movendo o ponteiro para a frente, pois não há trabalho divergente a ser mesclado — isso é chamado de “fast-forward.”

A sua alteração agora está no snapshot do commit apontado pelo branch master, e você pode implantar a correção.

`master` é avançado (fast-forwarded) para `hotfix`
Figure 22. master é avançado (fast-forwarded) para hotfix

Depois que sua correção superimportante for implantada, você estará pronto para voltar ao trabalho que estava fazendo antes de ser interrompido. No entanto, primeiro você excluirá o branch hotfix, pois não precisa mais dele — o branch master aponta para o mesmo lugar. Você pode excluí-lo com a opção -d do comando git branch:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

Agora você pode voltar ao seu branch de trabalho em andamento na issue #53 e continuar trabalhando nela.

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
O trabalho continua no `iss53`
Figure 23. O trabalho continua no iss53

Vale ressaltar aqui que o trabalho que você fez no seu branch hotfix não está contido nos arquivos do seu branch iss53. Se você precisar incorporá-lo (pull it in), você pode mesclar (merge) o seu branch master para dentro do seu branch iss53 executando git merge master, ou pode esperar para integrar essas alterações até decidir incorporar o branch iss53 de volta ao master mais tarde.

Mesclagem (Merging) Básica

Suponha que você decidiu que seu trabalho na issue #53 está completo e pronto para ser mesclado (merged) no seu branch master. Para fazer isso, você mesclará seu branch iss53 no master, de forma semelhante à mesclagem do seu branch hotfix anteriormente. Tudo o que você precisa fazer é fazer checkout do branch para o qual deseja mesclar (merge into) e depois executar o comando git merge:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

Isso parece um pouco diferente da mesclagem do hotfix que você fez anteriormente. Neste caso, o seu histórico de desenvolvimento divergiu de algum ponto anterior. Como o commit do branch em que você está não é um ancestral direto do branch que você está mesclando, o Git tem que fazer algum trabalho. Nesse caso, o Git faz uma simples mesclagem de três vias (three-way merge), usando os dois snapshots apontados pelas pontas dos branches (branch tips) e o ancestral comum aos dois.

Três snapshots usados em uma mesclagem típica
Figure 24. Três snapshots usados em uma mesclagem típica

Em vez de apenas mover o ponteiro do branch para a frente, o Git cria um novo snapshot que resulta dessa mesclagem de três vias (three-way merge) e cria automaticamente um novo commit que aponta para ele. Isso é conhecido como um merge commit (commit de mesclagem), e é especial porque tem mais de um pai.

Um merge commit
Figure 25. Um merge commit

Agora que o seu trabalho foi mesclado (merged), você não tem mais necessidade do branch iss53. Você pode fechar a issue no seu sistema de rastreamento de problemas e excluir o branch:

$ git branch -d iss53

Conflitos Básicos de Mesclagem (Merge Conflicts)

Ocasionalmente, esse processo não ocorre sem problemas. Se você alterou a mesma parte do mesmo arquivo de forma diferente nos dois branches que está mesclando, o Git não será capaz de mesclá-los de forma limpa (cleanly). Se a sua correção para a issue #53 modificou a mesma parte de um arquivo que o branch hotfix, você terá um conflito de mesclagem (merge conflict) parecido com este:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

O Git não criou automaticamente um novo merge commit. Ele pausou o processo enquanto você resolve o conflito. Se você quiser ver quais arquivos não foram mesclados (unmerged) em qualquer ponto após um conflito de mesclagem, você pode executar o git status:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

Qualquer coisa que tenha conflitos de mesclagem e não tenha sido resolvida é listada como não mesclada (unmerged). O Git adiciona marcadores de resolução de conflitos padrão aos arquivos que têm conflitos, para que você possa abri-los manualmente e resolver esses conflitos. O seu arquivo contém uma seção parecida com esta:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

Isso significa que a versão no HEAD (seu branch master, porque era esse que você havia feito checkout quando executou o comando merge) é a parte superior daquele bloco (tudo acima de =======), enquanto a versão no seu branch iss53 se parece com tudo o que está na parte inferior. Para resolver o conflito, você deve escolher um lado ou o outro ou mesclar o conteúdo você mesmo. Por exemplo, você pode resolver esse conflito substituindo o bloco inteiro por isso:

<div id="footer">
please contact us at email.support@github.com
</div>

Essa resolução tem um pouco de cada seção, e as linhas <<<<<<<, ======= e >>>>>>> foram completamente removidas. Depois de resolver cada uma dessas seções em cada arquivo em conflito, execute git add em cada arquivo para marcá-lo como resolvido. Preparar o arquivo (staging) marca-o como resolvido no Git.

Se você quiser usar uma ferramenta gráfica para resolver esses problemas, você pode executar o git mergetool, que aciona uma ferramenta de mesclagem visual apropriada e o guia através dos conflitos:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

Se você quiser usar uma ferramenta de mesclagem diferente da padrão (o Git escolheu opendiff neste caso porque o comando foi executado no macOS), você pode ver todas as ferramentas suportadas listadas no topo após “one of the following tools.” Basta digitar o nome da ferramenta que você prefere usar.

Note

Se você precisar de ferramentas mais avançadas para resolver conflitos de mesclagem complicados, cobriremos mais sobre mesclagem em Merging Avançado.

Depois de sair da ferramenta de mesclagem, o Git pergunta se a mesclagem foi bem-sucedida. Se você disser ao script que sim, ele prepara (stages) o arquivo para marcá-lo como resolvido para você. Você pode executar git status novamente para verificar se todos os conflitos foram resolvidos:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

Se você estiver satisfeito com isso e verificar que tudo o que tinha conflitos foi preparado (staged), você pode digitar git commit para finalizar o merge commit. A mensagem de commit, por padrão, se parece com isto:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

Se você acha que seria útil para outras pessoas analisarem essa mesclagem no futuro, você pode modificar esta mensagem de commit com detalhes sobre como você resolveu a mesclagem e explicar por que você fez as alterações que fez, se elas não forem óbvias.