# Merge Branches permitem experimentar com o repositório, e separar o desenvolvimento de novas funcionalidades e correções. Também permite que várias pessoas trabalhem no mesmo projeto, através de branches remotos. Em algum momento, os vários branches devem ser reunidos em uma nova versão. Há várias formas de fazê-lo. Uma das ferramentas que o permite é o merge. Mas antes, revisaremos e veremos alguns conceitos. ## Branches Discutimos como criar branches na seção [[Branches]] e como obter branches remotos na seção [[Remotes]]. Vejamos, no entanto, porque levam esse nome. A imagem abaixo mostra um repositório com poucos commits. Com exceção do primeiro commit, todos os commits em Git possuem um ou mais genitores. Isso faz com que a relação entre os commits constitua um grafo acíclico dirigido, uma estrutura similar àquela associada a uma árvore genealógica. O exemplo abaixo mostra commits com apenas um genitor. Note, no entanto, que há um ramo, representado pelo branch devel. Isto é o que chamamos de histórico não-linear. Há formas de linearizar históricos e veremos mais adiante as razões para fazê-lo, quando não fazê-lo, e como fazê-lo. [[!img branches.png]] ## Commit IDs Vimos em [[Git_log]] que um commit é identificado por um ID. Este ID é um hash SHA-1 do objeto do commit. O objeto é composto pelo log, autor, data, a árvore de arquivos, outros metadados, e a lista de genitores. Dada a natureza de um hash criptográfico como SHA-1, pequenas mudanças no commit, seja no sumário, em algum arquivo, genitor, ou outros, causa uma mudança no commit ID. Vejamos como alterar um commit usando a opção --amend do comando git commit. Para tanto, criaremos um novo branch a partir do master e alteraremos nosso novo HEAD. ~/project$ git checkout -b amend master Switched to a new branch 'amend' ~/project$ Veja como fica o nosso grafo após o novo branch ser criado na imagem abaixo. Note que o grafo continua o mesmo e o branch amend é apenas uma referência ao mesmo commit ao qual o branch master se refere. [[!img branch.png]] Neste exemplo, alteramos apenas a mensagem de log, usando a opção --amend. ~/project$ git commit --amend [amend 571a638] Acrescenta lista de arquivos a serem ignorados. 1 file changed, 2 insertions(+) create mode 100644 .gitignore ~/project$ Note o resultado na figura abaixo. O commit foi alterado, mudando seu ID. Ele ainda aponta para o mesmo genitor, mas é um commit diferente daquele referenciado pelo branch master. [[!img amend.png]] ## Histórico A sequência de commits que vemos em um branch é também chamada de histórico. Como é utilizado um hash criptográfico e um link entre os commits utilizando o ID, e esse faz parte do conteúdo criptografado, não é possível alterar este histórico. Quando alteramos um commit, como feito acima, o histórico original é mantido enquanto ainda existir alguma referência a ele, através de branches, por exemplo. Se algum commit tiver o commit alterado como seu genitor, sua referência ainda será ao commit genitor original, não à nova versão do commit. Sendo assim, para aproveitar os commits já realizados, é necessário reescrevê-los para que apontem, em cadeia, às novas versões dos commits reescritos. Isso se chama reescrevendo a história, e pode ser feito com o comando git rebase, entre outros comandos. Veremos mais logo quando e como reescrever a história. Agora, veremos quando não se deve reescrevê-la. ## Fast-foward Quando realizamos um push em um repositório, enviamos nossas atualizações. No entanto, existe uma condição para que o Git aceite esta atualização sem emitir um aviso. O novo commit enviado deve conter todos os commits já presentes no branch sendo atualizado. Significa que o novo commit deve ter o velho commit como ancestral em seu grafo de histórico. No exemplo anterior em que fizemos o amend, tanto o branch amend quanto o branch devel não têm o branch master como ancestral. Isto significa que a atualização do branch master para um destes commits não avançaria o histórico. Não seria o que chamamos de fast-forward. Fazer uma atualização que não é fast-forward seria como apagar o histórico que já ocorreu. Substituir um ramo por outro. É o que muitos chamam de fork. Fazer fork utilizando diferentes repositórios por diferentes pessoas, ou diferentes branches com o propósito de realizar merges é saudável pra uma comunidade. É o que permite o envolvimento de mais pessoas em um projeto, e a experimentação. Mas substituir um mesmo branch público por algo que apaga parte de seu histórico pode introduzir vários problemas no fluxo de trabalho de uma comunidade, como merges duplicados e desnecessários, reescritas de histórico em toda a cadeia de branches e forks, criando um efeito cascata. Portanto, não publique branches que não pretenda manter de forma fast-forward, a não ser que fique claro no workflow como aquele branch será consumido em outro branch fast-forward, ou deixando claro que o branch será reescrito, e não deve ser utilizado como base a não ser que o contribuidor esteja preparado para lidar com as reescritas. Na prática, fast-forward facilita a leitura do histórico, evita problemas com push e merges, e facilita reescrita de históricos, nas situações em que estes devem ser feitos, em branches ainda não publicados. ## Merge Como podemos, então, produzir um commit fast-forward que integre o trabalho feito no branch devel? É possível criar um commit que seja fast-forward tanto do branch master quanto do branch devel? Ou de quaisquer dois commits? Lembre-se que dissemos que um commit pode ter um ou mais genitores. Se pudermos criar um commit que tenha os dois commits apontados pelos branches master e devel como genitores, teremos um commit que permite fast-forward para ambos. Esse é um dos papéis do merge. O comando git merge permite criar um commit com mais de um genitor. Mas ele faz mais que isso. Ele combina as árvores de arquivos dos commits que farão parte do merge. A estratégia utilizada para fazê-lo pode ser configurada e o padrão depende de quantos commits fazem parte do merge. No caso comum, dois commits fazem parte do merge, e a estratégia de combinar as duas árvores é um 3-way merge recursivo, que compara as árvores dos dois commits com um ancestral comum, e aplica as mudanças encontradas em ambas as árvores dos dois commits. O resultado é um novo commit com dois genitores, e uma árvore com mudanças realizadas nos dois branches. ~/project$ git merge devel Faz o merge de testes. Merge made by the 'recursive' strategy. Makefile | 3 +++ 1 file changed, 3 insertions(+) ~/project$ [[!img merge.png]]