git merge

Photo by Yancy Min on Unsplash

git merge

git merge

Continuando a série sobre git conceitual, no artigo anterior falei sobre branches e a divergência de versões do código.

Essas versões paralelas em algum momento poderão convergir, sendo reintegradas em uma versão única do projeto.

Para isso, trago nesse artigo o conceito de merges e como eles funcionam no git.

merge

O merge serve para incorporar as alterações que ocorreram em um branch do projeto em um outro branch.

Usarei os nomes 'branch de destino' para o branch que sofrerá o merge, ou seja, o que será alterado, e 'branch de origem' o branch que contém as alterações que estão sendo integradas no branch de destino. O branch de origem não é alterado no merge.

branches não divergentes

O primeiro cenário de merge é quando não houve divergência entre os branches de origem e destino.

Nesse cenário o branch main é o destino, que irá incorporar as alterações do branch feature/trem_de_pouso.

git-fast-forward-before.png

Podemos ver que a partir do branch de origem (feature/trem_de_pouso) é possível alcançar o branch de destino (main) seguindo as setas dos parent commits, não houve divergência. (Nesse exemplo foram representados apenas 2 commits pra simplificar o desenho, mas pode existir qualquer quantidade de commits entre esses dois branches)

Nesse cenário a versão final será exatamente igual à branch de origem, já que não houve nenhuma alteração paralela a ser considerada.

Como vimos nos artigos anteriores da série, o branch é apenas um ponteiro pro commit, que representa a fotografia completa do projeto, então nesse cenário por padrão o git irá apenas reapontar o branch main para o commit 'adiciona trem de pouso' apontado pelo branch de origem feature/trem_de_pouso.

Essa operação é chamada de fast-forward e nesse cenário nenhum novo commit é criado, o branch é apenas reapontado para outro commit.

git-fast-forward-after.png

É possível forçar a criação de um commit de merge caso queira que apareça no histórico o evento de merge com a data que ocorreu e alguma mensagem (utilizando a opção no-fast-forward --no-ff no comando de merge).

Nesse caso será criado um novo commit, igual ao do branch de origem, com 2 parents, mas será possível editar a mensagem de commit.

git-no-fast-forward.png

branches divergentes

O outro cenário é quando houve divergência entre os branches envolvidos.

git-merge-three-way-before.png

Podemos ver nesse caso que a partir do branch origem feature/asas não conseguimos chegar no branch de destino main navegando pelos parent commits, portanto nesse caso os dois branches divergiram.

O git precisa considerar as alterações que ocorreram nos dois branches para gerar o commit de merge.

Para isso a primeira coisa que ele faz é localizar um ancestral comum entre os dois branches (merge base). Nesse caso o commit com a mensagem 'adiciona fuselagem'

Temos portanto 3 commits envolvidos, o merge base, merge source e merge target (three-way merge). Commits intermediários não são considerados no merge.

git-merge-three-way-commits.png

O ancestral comum (merge base) é comparado com cada um dos outros dois e as diferenças são agregadas para o commit de merge.

git-merge-three-way-diffs.png

O commit de merge é então gerado, tendo os 2 commits dos branches como parents e o branch de destino (main) é reapontado para esse novo commit.

git-merge-three-way-after.png

conflito

Um conflito pode ocorrer quando nessa comparação houver alterações nas mesmas partes do projeto.

Por exemplo, se em um branch a fuselagem tivesse sido pintada de azul e no outro branch de amarelo:

git-merge-conflict-before.png

O git não saberia dizer qual o correto, já que essa parte foi alterada nas 2 versões do projeto. Nesse caso ele sinalizará a parte conflitante para o usuário decidir qual deve permanecer.

git-merge-conflicting.png

Somente após resolvido o conflito será gerado o commit de merge. Por exemplo, caso o usuário tivesse escolhido o amarelo.

git-merge-conflict-after.png

Obs: O git não considera o tempo (timestamp dos commits) nas operações de merge, já que é um sistema distribuído e esses timestamps não são confiáveis.

conclusão

  • merges servem para juntar versões paralelas do projeto
  • não havendo divergência entre o branch de origem e destino, por padrão o git irá apenas reapontar o branch de destino para o commit de origem (fast-forward)
  • para branches divergentes será feito o 3-way merge, considerando 3 commits (ancestral comum, branch origem e branch destino).
  • se no processo de merge forem detectadas alterações nas mesmas partes do projeto nos dois branches, serão gerados conflitos e o usuário deverá resolver todos antes de finalizar o merge
  • tempo não é um fator considerado no processo

referências