Usando ramificações

Conflitos Git merge

Os sistemas de controle de versão servem para gerenciar contribuições de vários autores (que são, com frequência, desenvolvedores). Às vezes, diversos desenvolvedores querem editar o mesmo conteúdo. Se o Desenvolvedor A tenta editar códigos ao mesmo tempo que o Desenvolvedor B, podem ocorrer conflitos. Para reduzir esses conflitos, os desenvolvedores vão trabalhar em ramificações isoladas. A principal responsabilidade do comando git merge é combinar ramificações separadas e resolver conflitos de edição.

Entendendo conflitos de merge

Merges e conflitos são partes comuns da experiência Git. Conflitos em outras ferramentas de controle de versão, como SVN, podem custar tempo e dinheiro. O Git torna o processo de merge superfácil. Na maioria das vezes, ele mesmo descobre como fazer a integração automática de novas mudanças.

É comum que surjam conflitos quando duas pessoas alteram as mesmas linhas no mesmo arquivo. Ou então quando algum desenvolvedor exclui arquivos enquanto outra pessoa faz alterações. Nesses casos, o Git não pode determinar qual está correto. Os conflitos afetam apenas o desenvolvedor que conduz o merge e o resto da equipe não fica ciente deles. O Git marca os arquivos em conflito e interrompe o processo de merge. A partir daí, é responsabilidade dos desenvolvedores resolver o conflito.

Tipos de conflitos de merge

O merge pode entrar em estado de conflito em dois momentos diferentes. No começo ou durante o processo de merge. A seguir, a gente discute como enfrentar esses cenários de conflito.

O Git não consegue iniciar o merge

A inicialização do merge pode falhar se o Git vir que há mudanças no diretório de trabalho ou na área de staging do projeto atual. O Git não pode iniciar o merge, pois essas mudanças pendentes podem ser gravadas pelos commits que estão passando pelo processo de merge. Quando isso acontece, é por causa de conflitos com mudanças locais pendentes, não conflitos com outros desenvolvedores. O estado local vai precisar ser estabilizado por meio de git stash, git checkout, git commit ou git reset. A falha da inicialização do merge vai fazer com que a mensagem de erro a seguir seja exibida:

error: Entry '<fileName>' not uptodate. Cannot merge. (Changes in working directory)

O Git falha durante o merge

Falhas DURANTE merges indicam conflitos entre a ramificação local atual e a ramificação passando pelo processo de merge. Isso indica conflitos com o código de outro desenvolvedor. O Git vai tentar fazer merge dos arquivos, mas vai deixar você encontrar a solução manual para os arquivos com conflitos. Falhas no meio do merge fazem com que a mensagem de erro a seguir seja exibida:

error: Entry '<fileName>' would be overwritten by merge. Cannot merge. (Changes in staging area)

Como criar o conflito de merge

Para entender melhor os conflitos de merge, a próxima seção vai simular um conflito para você examinar e resolver mais tarde. O exemplo vai usar a interface Git de linha de comando parecida com o Unix para executar a simulação de exemplo.

$ mkdir git-merge-test
$ cd git-merge-test
$ git init .
$ echo "this is some content to mess with" > merge.txt
$ git add merge.txt
$ git commit -am"we are commiting the inital content"
[master (root-commit) d48e74c] we are commiting the inital content
1 file changed, 1 insertion(+)
create mode 100644 merge.txt

Esse exemplo de código executa a sequência de comandos que faz as ações descritas a seguir.

  • Cria o diretório chamado git-merge-test, muda para esse diretório e faz a inicialização como o novo repositório Git.
  • Cria o arquivo de texto merge.txt com algum conteúdo dentro.
  • Adiciona o merge.txt ao repositório e faz a confirmação.

Agora, a gente tem o novo repositório com a ramificação mestre e o arquivo merge.txt com conteúdo dentro. A seguir, a gente vai criar a ramificação para usar como o merge do conflito.

$ git checkout -b new_branch_to_merge_later
$ echo "totally different content to merge later" > merge.txt
$ git commit -am"edited the content of merge.txt to cause a conflict"
[new_branch_to_merge_later 6282319] edited the content of merge.txt to cause a conflict
1 file changed, 1 insertion(+), 1 deletion(-)

O processo da sequência de comando realiza as seguintes ações:

  • cria e faz check-out da ramificação chamada new_branch_to_merge_later
  • substitui o conteúdo em merge.txt
  • confirma o novo conteúdo

Com essa nova ramificação: new_branch_to_merge_later, a gente criou o commit que substitui o conteúdo do merge.txt

git checkout master
Switched to branch 'master'
echo "content to append" >> merge.txt
git commit -am"appended content to merge.txt"
[master 24fbe3c] appended content to merge.tx
1 file changed, 1 insertion(+)

Essa cadeia de comandos faz check-out da ramificação mestre, anexa conteúdo ao merge.txt e faz o commit. Isso faz com que o repositório de exemplo tenha dois novos commits. Um na ramificação mestre e outro na ramificação new_branch_to_merge_later. Agora, faça o git merge de new_branch_to_merge_later e veja o que acontece!

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

BUM 💥. O conflito apareceu. Valeu por avisar a gente, Git!

Como identificar conflitos de merge

Como você viu no exemplo de procedimento, o Git produz saídas descritivas e avisa quando CONFLITOS ocorrem. A gente pode ter mais informações executando o comando git status

$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

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

both modified:   merge.txt

A saída do git status indica que há caminhos que não passaram pelo merge por causa de conflitos. O arquivo merge.txt agora aparece com estado modificado. Examine o arquivo e veja as modificações.

$ cat merge.txt
<<<<<<< HEAD
this is some content to mess with
content to append
=======
totally different content to merge later
>>>>>>> new_branch_to_merge_later

Aqui, a gente usou o comando cat para extrair o conteúdo do arquivo merge.txt. Podemos ver algumas novas adições estranhas

  • <<<<<<< HEAD
  • =======
  • >>>>>>> new_branch_to_merge_later

Pense nessas novas linhas como "divisores de conflitos". A linha ======= é o "centro" do conflito. Todo o conteúdo entre o centro e a linha <<<<<<< HEAD é o conteúdo que existe na ramificação mestre atual, que o ref. HEAD está apontando. Como alternativa, todo o conteúdo entre o centro e >>>>>>> new_branch_to_merge_later é o conteúdo presente na ramificação de merge.

Como resolver conflitos de merge usando a linha de comando

O modo mais direto de resolver conflitos de merge é editar o arquivo com conflitos. Abra o arquivo merge.txt no editor que você preferir. Como exemplo, a gente vai apenas remover todos os divisores com conflito. O conteúdo do arquivo merge.txt modificado deve se parecer com isso:

this is some content to mess with
content to append
totally different content to merge later

Quando o arquivo tiver sido editado, use git add merge.txt para fazer stage do novo conteúdo que passou pelo merge. Para finalizar o merge, crie o commit executando:

git commit -m "merged and resolved the conflict in merge.txt"

O Git percebe que o conflito foi resolvido e cria o commit de merge para finalizar o processo.

Os comandos Git que podem ajudar a resolver conflitos de merge

Ferramentas gerais

git status

O comando de status é usado com frequência ao trabalhar com Git. Durante o merge, ele pode ajudar a identificar arquivos com conflito.

git log --merge

Passar o argumento --merge para o comando do git log vai produzir o log com a lista de commits com conflitos entre as ramificações.

git diff

O diff ajuda a encontrar diferenças entre os estados de repositórios/arquivos. Ele é útil para prever e prevenir conflitos de merge.

Ferramentas para quando o git não consegue iniciar o merge

git checkout

O checkout pode ser usado para desfazer mudanças em arquivos ou para alterar ramificações

git reset --mixed

O reset pode ser usado para desfazer mudanças no diretório de trabalho e na área de staging.

Ferramentas para quando surgem conflitos git durante o merge

git merge --abort

Executar o git merge com a opção --abort encerra o processo de merge e faz a ramificação voltar ao estado anterior ao merge.

git reset

O Git reset pode ser usado durante conflitos de merge para redefinir arquivos com conflitos para estados válidos

Resumo

Enfrentar conflitos de merge pode ser intimidador. Ainda bem que o Git oferece ferramentas poderosas para ajudar a navegar e a resolver conflitos. O Git pode lidar sozinho com a maioria dos merges com recursos automáticos. Conflitos aparecem quando duas ramificações diferentes editam a mesma linha do mesmo arquivo ou quando arquivos são excluídos na ramificação, mas editados em outra. É comum que conflitos aconteçam ao trabalhar em ambientes de equipe.

Há muitas ferramentas que ajudam a resolver conflitos de merge. O Git tem várias das ferramentas de linha de comando que a gente mencionou. Para mais informações sobre essas ferramentas, consulte as páginas independentes sobre git log, git reset, git status, git checkout e git reset. Além do Git, muitas ferramentas de terceiros oferecem recursos de suporte simplificados para conflitos de merge.

Pronto para tentar a ramificação?

Tente este tutorial interativo.

Comece agora mesmo