# git merge

# git merge

Continuando a série sobre [git conceitual](https://lluccia.hashnode.dev/series/git-conceitual), no [artigo anterior](https://lluccia.hashnode.dev/git-branches) 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250014367/9UOjXG7GD.png align="center")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250036055/Kfud_BXaJ.png align="center")

É 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250067273/vzexTJzFN.png align="center")

## branches divergentes

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

![git-merge-three-way-before.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250090763/hMp8WlvzF.png align="center")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250125959/dAB_3cexS.png align="center")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250107238/juwrLHUv6.png align="center")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250151205/cMwGO1uy8.png align="center")

### 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250163232/by3zQzYLk.png align="center")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250173742/57-RFvzgR.png align="center")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1662250180755/MKvmKwTKM.png align="center")

_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

- <https://git-scm.com/docs/git-merge>
- <https://developpaper.com/principle-of-git-merge-recursive-three-way-merge-algorithm>
