7.8 Ferramentas Git - Fusão Avançada.
Fusão Avançada.
Fusionar no Git geralmente é bastante fácil. Uma vez que o Git facilita a mesclagem de outro ramo várias vezes, isso significa que você pode ter um ramo de vida muito longa, mas você pode mantê-lo atualizado à medida que vai, resolvendo pequenos conflitos com freqüência, em vez de se surpreender com um enorme conflito na fim da série.
No entanto, às vezes ocorrem conflitos difíceis. Ao contrário de alguns outros sistemas de controle de versão, o Git não tenta ser excessivamente inteligente sobre a resolução de conflitos de mesclagem. A filosofia de Git é ser inteligente para determinar quando uma resolução de mesclagem é inequívoca, mas, se houver um conflito, não tenta ser inteligente em resolvê-la automaticamente. Portanto, se você esperar muito para unir dois ramos que divergem rapidamente, você pode encontrar alguns problemas.
Nesta seção, examinaremos o que alguns desses problemas podem ser e quais ferramentas o Git lhe dá para ajudar a lidar com essas situações mais difíceis. Também abordaremos alguns dos diferentes tipos de fusões não padronizados que você pode fazer, além de ver como remover as fusões que você fez.
Combinar Conflitos.
Embora tenhamos abordado alguns conceitos básicos sobre a resolução de conflitos de mesclagem nos Conflitos de mesclagem básicos, para conflitos mais complexos, o Git fornece algumas ferramentas para ajudá-lo a descobrir o que está acontecendo e a como lidar melhor com o conflito.
Em primeiro lugar, se for possível, tente certificar-se de que seu diretório de trabalho esteja limpo antes de fazer uma fusão que possa ter conflitos. Se você tiver trabalho em andamento, comprometa-o a uma ramificação temporária ou esconda-o. Isso faz com que você possa desfazer tudo o que você tentar aqui. Se você tiver mudanças não salvas no seu diretório de trabalho quando você tentar uma mesclagem, algumas dessas dicas podem ajudá-lo a perder esse trabalho.
Vamos passar por um exemplo muito simples. Temos um arquivo Ruby super simples que imprime o mundo do Olá.
Em nosso repositório, criamos um novo ramo chamado espaço em branco e procedemos a mudar todos os terminais de linha do Unix para os terminais de linha do DOS, mudando essencialmente cada linha do arquivo, mas apenas com espaços em branco. Então mudamos a linha "hello world" para "hello mundo".
Agora, voltamos para nosso ramo principal e adicionamos alguma documentação para a função.
Agora, tentamos fundir em nosso ramo de espaço em branco e obteremos conflitos por causa das mudanças de espaço em branco.
Abortando uma mesclagem.
Agora temos algumas opções. Primeiro, vamos abordar como sair dessa situação. Se você talvez não estivesse esperando conflitos e não querendo lidar com a situação ainda, você pode simplesmente sair da mesclagem com git merge - abortar.
A opção git merge --abort tenta retornar ao seu estado antes de executar a mesclagem. Os únicos casos em que talvez não seja capaz de fazer isso perfeitamente seriam se você tivesse mudanças não comprometidas e não confirmadas em seu diretório de trabalho quando você o executou, caso contrário, ele deveria funcionar bem.
Se, por algum motivo, você quiser começar novamente, você também pode executar o git reset - HEAD HEAD, e seu repositório voltará para o último estado comprometido. Lembre-se de que qualquer trabalho não comprometido será perdido, então tenha certeza de que não deseja nenhuma das suas alterações.
Ignorando Whitespace.
Neste caso específico, os conflitos estão relacionados ao espaço em branco. Nós sabemos disso porque o caso é simples, mas também é muito fácil de dizer em casos reais quando se olha para o conflito porque cada linha é removida de um lado e adicionada novamente no outro. Por padrão, o Git vê todas essas linhas como sendo alteradas, portanto, não pode mesclar os arquivos.
A estratégia de mesclagem padrão pode levar argumentos, e alguns deles são sobre como ignorar adequadamente as mudanças no espaço em branco. Se você ver que você tem muitos problemas de espaço em branco em uma mesclagem, você pode simplesmente abortá-lo e fazê-lo novamente, desta vez com - Xignore-all-space ou - Xignore-space-change. A primeira opção ignora o espaço em branco completamente ao comparar linhas, o segundo trata as seqüências de um ou mais caracteres de espaço em branco como equivalentes.
Uma vez que neste caso, as alterações reais do arquivo não eram conflitantes, uma vez que ignoramos as mudanças de espaços em branco, tudo se funde bem.
Este é um salva-vidas se você tem alguém em sua equipe que gosta de reformatar ocasionalmente tudo, desde espaços a guias ou vice-versa.
Arquivo manual de re-fusão.
Embora o Git manipule bem o processamento de espaços brancos, existem outros tipos de mudanças que talvez o Git não possa lidar de forma automática, mas são correções para scripts. Como exemplo, vamos fingir que a Git não conseguiu lidar com a mudança de espaço em branco e precisávamos fazê-lo à mão.
O que realmente precisamos fazer é executar o arquivo que estamos tentando mesclar através de um programa dos2unix antes de tentar a fusão de arquivos real. Então, como faremos isso?
Primeiro, entramos no estado de conflito de fusão. Então, queremos obter cópias da minha versão do arquivo, sua versão (do ramo em que estamos mesclando) e a versão comum (de onde ambos os lados se ramificaram). Então queremos consertar seu lado ou nosso lado e voltar a tentar a fusão novamente por apenas esse arquivo único.
Obter as três versões de arquivos é realmente muito fácil. O Git armazena todas essas versões no índice em "estágios", que cada um tem números associados a eles. Etapa 1 é o antepassado comum, o estágio 2 é sua versão eo estágio 3 é do MERGE_HEAD, a versão em que você está mesclando ("deles").
Você pode extrair uma cópia de cada uma dessas versões do arquivo em conflito com o comando git show e uma sintaxe especial.
Se você quiser obter um núcleo um pouco mais difícil, você também pode usar o comando ls-files - u plumbing para obter os SHA-1 reais dos blobs Git para cada um desses arquivos.
O: 1: hello. rb é apenas uma abreviatura para procurar aquele blob SHA-1.
Agora que temos o conteúdo de todos os três estágios em nosso diretório de trabalho, podemos consertar manualmente o deles para corrigir o problema de espaço em branco e re-mesclar o arquivo com o comando pouco conhecido git merge-file que faz exatamente isso.
Nesse ponto, combinamos o arquivo com bastante facilidade. Na verdade, isso realmente funciona melhor do que a opção ignore-space-change porque isso realmente corrige as mudanças de espaço em branco antes da mesclagem em vez de simplesmente ignorá-las. Na fusão de ignorar-espaço-mudança, na verdade, terminamos com algumas linhas com finais de linha do DOS, fazendo com que as coisas fossem misturadas.
Se você quiser ter uma idéia antes de finalizar este commit sobre o que realmente foi alterado entre um lado ou o outro, você pode pedir o git diff para comparar o que está em seu diretório de trabalho que você está prestes a comprometer como resultado da mesclagem para qualquer uma dessas etapas. Vamos passar por todos eles.
Para comparar o seu resultado com o que você teve no seu ramo antes da fusão, em outras palavras, para ver o que a incorporação introduziu, você pode executar as diferenças.
Então, aqui, podemos ver facilmente que o que aconteceu no nosso ramo, o que realmente estamos apresentando a este arquivo com essa fusão, está mudando essa única linha.
Se quisermos ver como o resultado da fusão diferiu do que era do lado deles, você pode executar o diff do git. Neste e no exemplo a seguir, temos que usar - b para retirar o espaço em branco, porque estamos comparando isso com o que está no Git, e não com o nosso arquivo de limpar hello. theirs. rb.
Finalmente, você pode ver como o arquivo mudou de ambos os lados com o GIT diff - base.
Neste ponto, podemos usar o comando git clean para limpar os arquivos extra que criamos para fazer a mesclagem manual, mas não precisamos mais.
Verificando Conflitos.
Talvez não estivéssemos felizes com a resolução neste ponto por algum motivo, ou talvez a edição manual de um ou de ambos os lados ainda não funcionou bem e precisamos de mais contexto.
Vamos mudar o exemplo um pouco. Para este exemplo, temos dois ramos mais vivos que cada um tem alguns cometidos neles, mas criam um conflito de conteúdo legítimo quando mesclado.
Agora temos três compromissos únicos que vivem apenas no ramo principal e outros três que vivem no ramo mundo. Se tentarmos fundir a ramificação do mundo, obtemos um conflito.
Gostaríamos de ver qual é o conflito de mesclagem. Se abrimos o arquivo, veremos algo assim:
Ambos os lados da mesclagem adicionaram conteúdo a este arquivo, mas alguns dos compromissos modificaram o arquivo no mesmo local que causou esse conflito.
Vamos explorar algumas ferramentas que você agora tem à sua disposição para determinar como esse conflito veio a ser. Talvez não seja óbvio como exatamente você deve consertar esse conflito. Você precisa de mais contexto.
Uma ferramenta útil é git checkout com a opção '--conflict'. Isso re-checkout o arquivo novamente e substituir os marcadores de conflito de mesclagem. Isso pode ser útil se você quiser redefinir os marcadores e tentar resolvê-los novamente.
Você pode passar --conflict diff3 ou fusion (que é o padrão). Se você passar diff3, o Git usará uma versão ligeiramente diferente de marcadores de conflito, não só lhe dando as versões "nosso" e "deles", mas também a versão "base" em linha para lhe dar mais contexto.
Uma vez que executamos isso, o arquivo ficará assim:
Se você gosta deste formato, você pode configurá-lo como padrão para futuros conflitos de mesclagem configurando a configuração merge. conflictstyle para diff3.
O comando git checkout também pode levar --ours e - suas opções, que podem ser uma maneira muito rápida de escolher apenas um lado ou outro sem fundir nada.
Isso pode ser particularmente útil para conflitos de arquivos binários onde você pode simplesmente escolher um lado ou onde você deseja apenas mesclar determinados arquivos de outro ramo - você pode fazer a mesclagem e, em seguida, efetuar o checkout de determinados arquivos de um lado ou outro antes de cometer .
Outra ferramenta útil ao resolver conflitos de mesclagem é git log. Isso pode ajudá-lo a contextualizar o que pode ter contribuído para os conflitos. Revisar um pouco de história para lembrar por que duas linhas de desenvolvimento estavam tocando a mesma área de código pode ser realmente útil às vezes.
Para obter uma lista completa de todos os compromissos exclusivos que foram incluídos em qualquer ramo envolvido nesta mesclagem, podemos usar a sintaxe de "ponto triplo" que aprendemos em Triple Dot.
Essa é uma boa lista dos seis compromissos totais envolvidos, bem como a cada linha de desenvolvimento em que cada um cometeu.
Podemos ainda simplificar isso para nos dar um contexto muito mais específico. Se adicionarmos a opção --merge para git log, isso só mostrará os compromissos em ambos os lados da mesclagem que tocam um arquivo que está atualmente em conflito.
Se você executar isso com a opção - p em vez disso, você obtém apenas os diffs para o arquivo que acabou em conflito. Isso pode ser realmente útil para lhe dar rapidamente o contexto que você precisa para ajudar a entender por que algo conflita e como resolvê-lo de forma mais inteligente.
Formato Diff Combinado.
Uma vez que o Git desenrola todos os resultados de mesclagem que são bem-sucedidos, quando você executa o Git diff enquanto estiver em um estado de mesclagem em conflito, você só obtém o que ainda está em conflito. Isso pode ser útil para ver o que você ainda precisa resolver.
Quando você executa git diff diretamente após um conflito de mesclagem, isso lhe dará informações em um formato de saída de DVD bastante exclusivo.
O formato é chamado de "Difusão combinada" e fornece duas colunas de dados ao lado de cada linha. A primeira coluna mostra se essa linha é diferente (adicionada ou removida) entre o ramo "nosso" e o arquivo em seu diretório de trabalho e a segunda coluna faz o mesmo entre o ramo "deles" e sua cópia do diretório de trabalho.
Então, nesse exemplo, você pode ver que o & lt; & lt; & lt; & lt; & lt; & lt; & lt; e & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; as linhas estão na cópia de trabalho, mas não estavam em ambos os lados da mesclagem. Isso faz sentido porque a ferramenta de fusão os colocou no contexto, mas esperamos que os remova.
Se resolvêssemos o conflito e executemos o GIT diff novamente, veremos o mesmo, mas é um pouco mais útil.
Isso nos mostra que "mundo hola" estava ao nosso lado, mas não na cópia de trabalho, que "hello mundo" estava no seu lado, mas não na cópia de trabalho e, finalmente, que "hola mundo" não estava em nenhum dos lados, mas agora está em a cópia de trabalho. Isso pode ser útil para rever antes de cometer a resolução.
Você também pode obter isso no log git para qualquer mesclagem para ver como algo foi resolvido após o fato. O Git emitirá este formato se você executar o show git em um commit de mesclagem, ou se você adicionar uma opção --cc a git log - p (o que, por padrão, mostra apenas patches para compromissos de não-mesclagem).
Desfazer Fusões.
Agora que você sabe como criar um compromisso de mesclagem, você provavelmente fará algum erro. Uma das ótimas coisas sobre trabalhar com o Git é que é bom cometer erros, porque é possível (e, em muitos casos, fácil) consertá-los.
Os compromissos de fusão não são diferentes. Digamos que você começou a trabalhar em um ramo tópico, fundou-o acidentalmente em mestre e agora o seu histórico de confirmação se parece com isto:
Existem duas maneiras de abordar esse problema, dependendo do resultado desejado.
Corrija as referências.
Se o commit de mesclagem indesejável existe apenas no seu repositório local, a solução mais fácil e melhor é mover os ramos para que eles apontem para onde você deseja. Na maioria dos casos, se você seguir a junção git errante com git reset - HEAD HEAD.
, isso irá redefinir os ponteiros de ramificação para que eles pareçam assim:
Nós cobrimos o reset novamente no Reset Desmistificado, então não deve ser muito difícil descobrir o que está acontecendo aqui. Aqui está uma atualização rápida: redefinir - difícil geralmente passa por três etapas:
Mova os pontos HEAD de ramificação para. Nesse caso, queremos mover o mestre para onde estava antes do compromisso de mesclagem (C6).
Faça com que o índice pareça HEAD.
Faça com que o diretório de trabalho se pareça com o índice.
A desvantagem dessa abordagem é que está reescrevendo o histórico, o que pode ser problemático com um repositório compartilhado. Confira The Perils of Rebasing para saber mais sobre o que pode acontecer; A versão curta é que, se outras pessoas tiverem os compromissos que você está reescrevendo, provavelmente você deve evitar a reinicialização. Esta abordagem também não funcionará se outros compromissos tiverem sido criados desde a fusão; mover as referências efetivamente perderia essas mudanças.
Inverta o commit.
Se mover os ponteiros do ramo ao redor não vai funcionar para você, o Git oferece a opção de fazer um novo commit que desfaz todas as mudanças de uma existente. Git chama essa operação de um "retorno", e neste cenário particular, você invocaria assim:
O sinalizador - m 1 indica qual pai é o "mainline" e deve ser mantido. Quando você invoca uma mesclagem em HEAD (git merge topic), o novo commit tem dois pais: o primeiro é HEAD (C6) e o segundo é a ponta do ramo sendo mesclado em (C4). Neste caso, queremos desfazer todas as alterações introduzidas pela mesclagem no pai 2 (C4), enquanto mantém todo o conteúdo do pai # 1 (C6).
O histórico com o commit de rever se parece com isto:
O novo commit ^ M tem exatamente o mesmo conteúdo que o C6, então, a partir daqui, é como se a fusão nunca acontecesse, exceto que os compromissos agora não combinados ainda estão no histórico da HEAD. Git ficará confuso se você tentar mesclar tópico no mestre novamente:
Não há nada no tópico que já não é alcançável pelo mestre. O que é pior, se você adicionar trabalho ao tópico e fundir novamente, o Git somente trará as mudanças desde a fusão revertida:
A melhor maneira de evitar isso é desativar a união original, já que você deseja trazer as alterações que foram revertidas e, em seguida, criar um novo commit de mesclagem:
Neste exemplo, M e ^ M cancelam. ^^ M efetivamente mescla nas mudanças de C3 e C4, e C8 se funde nas mudanças de C7, então agora o tópico é totalmente mesclado.
Outros tipos de fusões.
Até agora, cobrimos a fusão normal de dois ramos, normalmente manuseados com o que chamamos de estratégia "recursiva" de fusão. Existem outras maneiras de juntar sucursais entretanto. Vamos cobrir alguns deles rapidamente.
Nossa preferência ou a nossa.
Antes de tudo, há outra coisa útil que podemos fazer com o modo "recursivo" normal de fusão. Já vimos as opções ignorar-tudo-espaço e ignorar-espaço-mudança que são passadas com um - X, mas também podemos dizer ao Git que favorece um lado ou outro quando vê um conflito.
Por padrão, quando o Git vê um conflito entre dois ramos sendo mesclados, ele adicionará marcadores de conflito de mesclagem ao seu código e marcará o arquivo como conflitante e permitirá que você o resolva. Se você preferir que o Git simplesmente escolha um lado específico e ignore o outro lado, em vez de permitir que você resolva manualmente o conflito, você pode passar o comando de mesclagem, a - X ou ou - X.
Se Git vê isso, não irá adicionar marcadores de conflito. Quaisquer diferenças que sejam mergeáveis, ela irá fundir. Qualquer diferença que entre em conflito, ele simplesmente escolherá o lado que você especifica no todo, incluindo arquivos binários.
Se voltarmos para o exemplo do "mundo do Olá", estávamos usando antes, podemos ver que a fusão em nosso ramo causa conflitos.
No entanto, se o executemos com - Xours ou - Xtheirs não.
Nesse caso, em vez de obter marcadores de conflito no arquivo com "hello mundo" de um lado e "mundo hola", por outro, ele simplesmente escolherá "mundo hola". No entanto, todas as outras alterações não conflitantes nesse ramo são mescladas com sucesso.
Esta opção também pode ser passada para o comando git merge-file que vimos anteriormente executando algo como git merge-file --ours para fusões individuais de arquivos.
Se você quer fazer algo assim, mas não tem o Git mesmo tentando mesclar as mudanças do outro lado, há uma opção mais draconiana, que é a nossa estratégia de fusão "nós". Isso é diferente da opção de mesclagem recursiva "nosso".
Isso basicamente fará uma fusão falsa. Ele gravará uma nova combinação de mesclagem com os dois ramos como pais, mas nem sequer olhará para o ramo em que você está mesclando. Ele simplesmente registrará como resultado da mesclagem do código exato em seu ramo atual.
Você pode ver que não há diferença entre o ramo em que estávamos e o resultado da fusão.
Isso muitas vezes pode ser útil para basicamente enganar o Git para pensar que um ramo já foi mesclado ao fazer uma fusão mais tarde. Por exemplo, digamos que você ramificou um ramo de liberação e fez algum trabalho sobre isso que você quer se fundir novamente em seu ramo mestre em algum momento. Enquanto isso, alguns erros no mestre precisam ser enviados para o seu ramo de lançamento. Você pode mesclar o ramo do bugfix no ramo de lançamento e também mesclar - s somos o mesmo ramo em seu ramo principal (mesmo que a correção já esteja aí), então, quando você mesclar mais o ramo de lançamento novamente, não há conflitos do bugfix.
Fusão de sub-árvore.
A idéia da mesclagem de subárvore é que você tem dois projetos e um dos projetos mapeia para um subdiretório do outro. Quando você especifica uma mesclagem de subárvore, Git geralmente é inteligente o suficiente para descobrir que uma é uma subárvore da outra e mesclar adequadamente.
Passaremos por um exemplo de adicionar um projeto separado em um projeto existente e depois mesclar o código do segundo em um subdiretório do primeiro.
Primeiro, adicionaremos o aplicativo Rack ao nosso projeto. Vamos adicionar o projeto Rack como uma referência remota em nosso próprio projeto e depois verificá-lo em seu próprio ramo:
Agora, temos a raiz do projeto Rack em nosso ramo rack_branch e nosso próprio projeto no ramo principal. Se você verificar um e depois o outro, você pode ver que eles têm raízes de projetos diferentes:
Esta é uma espécie de conceito estranho. Nem todos os ramos em seu repositório realmente precisam ser ramos do mesmo projeto. Não é comum, porque raramente é útil, mas é bastante fácil ter ramificações com histórias completamente diferentes.
Neste caso, queremos puxar o projeto Rack para o nosso projeto mestre como um subdiretório. Podemos fazer isso em Git com git read-tree. Você aprenderá mais sobre a lição e seus amigos no Git Internals, mas por agora sabe que lê a árvore de raiz de um ramo em sua área de teste atual e no diretório de trabalho. Acabamos de trocar de volta para o seu ramo mestre e puxamos o ramo rack_branch para o subdiretório de rack do nosso ramal principal do nosso projeto principal:
Quando nos comprometemos, parece que temos todos os arquivos do Rack nesse subdiretório - como se os copiássemos de um tarball. O que fica interessante é que podemos facilmente cruzar as mudanças de um dos ramos para o outro. Então, se o projeto Rack atualizar, podemos puxar as mudanças a montante mudando para esse ramo e puxando:
Então, podemos juntar essas mudanças de volta ao nosso ramo principal. Para puxar as alterações e prepopular a mensagem de confirmação, use a opção --squash, bem como a opção - Xsubtree da estratégia de mesclagem recursiva. (A estratégia recursiva é o padrão aqui, mas nós o incluímos para maior clareza.)
Todas as mudanças do projeto do Rack são incorporadas e prontas para serem cometidas localmente. Você também pode fazer o contrário - faça alterações no subdiretório de rack do seu ramo principal e, em seguida, junte-os em seu ramo rack_branch mais tarde para enviá-los aos mantenedores ou empurrá-los para o rio.
Isso nos dá uma maneira de ter um fluxo de trabalho um pouco semelhante ao fluxo de trabalho do submódulo sem usar os submódulos (o que cobriremos em Submodules). Nós podemos manter filiais com outros projetos relacionados em nosso repositório e sub-árvore, mesclando-os em nosso projeto de vez em quando. É legal em alguns aspectos, por exemplo, todo o código está comprometido com um único lugar. No entanto, tem outras desvantagens na medida em que é um pouco mais complexo e mais fácil cometer erros na reintegração de mudanças ou acidentalmente empurrando um ramo para um repositório não relacionado.
Outro aspecto um pouco estranho é o de obter uma diferença entre o que você tem no subdiretório do rack e o código em seu ramo rack_branch - para ver se você precisa mesclar-los - você não pode usar o comando normal diff. Em vez disso, você deve executar o Git diff-tree com o ramo que deseja comparar com:
Ou, para comparar o que está em seu subdiretório de rack com o que o ramo mestre no servidor foi a última vez que você buscou, você pode executar.
No comments:
Post a Comment