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
.
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.
É 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.
branches divergentes
O outro cenário é quando houve divergência entre os branches envolvidos.
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.
O ancestral comum (merge base) é comparado com cada um dos outros dois e as diferenças são agregadas para o commit de merge.
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.
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:
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.
Somente após resolvido o conflito será gerado o commit de merge. Por exemplo, caso o usuário tivesse escolhido o amarelo.
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