uma história Git linear arrumada
uma das coisas que é negligenciada em muitos projetos baseados em Git é o valor de um histórico de commit linear. Na verdade, muitas ferramentas e diretrizes desencorajam fluxos de trabalho Git que visam um histórico linear. Acho isso triste, já que uma história organizada é muito valiosa, e há um fluxo de trabalho direto que garante um histórico linear.
história Linear vs não linear
um histórico linear é simplesmente um histórico Git no qual todos os commits vêm um após o outro. Seja. você não encontrará fusões de branches com históricos de commit independentes.
por que você quer um histórico linear?
além de ser arrumado e lógico, uma história linear é útil quando:
- olhando para a história. Uma história não linear pode ser muito difícil de seguir-às vezes a ponto de a história ser simplesmente incompreensível.
- Backtracking alterações. Por exemplo: “o recurso a foi introduzido antes ou depois do bugfix B?”.
- rastreando bugs. Git tem uma função muito legal chamada Git bisect, que pode ser usada para encontrar rapidamente qual commit introduziu um bug ou regressão. No entanto, com uma história não linear, Git bisect torna-se difícil ou mesmo impossível de usar.
- reverter alterações. Digamos que você encontrou um commit que causou uma regressão ou deseja remover um recurso que não deveria sair em uma versão específica. Com alguma sorte(se o seu código não mudou muito), você pode simplesmente reverter o(s) commit (s) indesejado (s) usando Git revert. No entanto, se você tiver um histórico não linear, talvez com muitas fusões entre ramos, isso será significativamente mais difícil.
provavelmente há outro punhado de situações em que um histórico linear é muito valioso, dependendo de como você usa o Git.
o ponto é: quanto menos linear for sua história, menos valiosa será.
causas de um histórico não linear
em suma, cada commit de mesclagem é uma fonte potencial de um histórico não linear. No entanto, existem diferentes tipos de commits de mesclagem.
Mesclar de um ramo de tópico em Mestre
quando você terminar com seu ramo de tópico e quiser integrá-lo ao mestre, um método comum é mesclar o ramo de tópico em Mestre. Talvez algo ao longo das linhas de:
git checkout mastergit pullgit merge --no-ff my-topic-branch
Uma bela propriedade, com este método é que você preserve as informações sobre o que compromete faziam parte de seu tópico ramo (uma alternativa seria deixar de fora “–não-ff”, o que permitiria o Git para fazer um avanço rápido em vez de uma série, caso em que ele pode não ser tão claro qual das compromete-se, na verdade, pertenciam ao seu tópico ramo).
o problema com a fusão com o mestre surge quando o ramo de tópico é baseado em um mestre antigo em vez da última dica do mestre. Nesse caso, você inevitavelmente obterá uma história não linear.
se isso será ou não um problema comum ou não depende em grande parte de quão ativo é o repositório Git, quantos desenvolvedores estão trabalhando simultaneamente, etc.
mesclar do mestre em uma ramificação de tópico
às vezes você deseja atualizar sua ramificação para corresponder ao mestre mais recente (por exemplo , existem alguns novos recursos no master que você deseja entrar em seu Branch de tópico ou descobre que não pode mesclar seu branch de tópico no master porque há conflitos).
um método comum, que é até recomendado por alguns, é mesclar a ponta do mestre em seu ramo de tópico. Esta é uma importante fonte de história não linear!
a solução: Rebase!Git rebase é uma função muito útil que você deve usar se quiser um histórico linear. Alguns acham o conceito de rebasing estranho, mas é realmente muito simples: repita as alterações (commits) em seu branch em cima de um novo commit.
por exemplo, você pode usar Git rebase para alterar a raiz do seu ramo de tópico de um mestre antigo para a ponta do mestre mais recente. Supondo que você tenha seu branch de tópico verificado, você pode fazer:
git fetch origingit rebase origin/master
observe que a operação de mesclagem correspondente seria mesclar a ponta do mestre em seu ramo de tópico (conforme descrito na figura anterior). O conteúdo resultante dos arquivos em seu branch de tópico seria o mesmo, independentemente de você fazer um rebase ou uma mesclagem. No entanto, a história é diferente (linear vs não linear!).
isso soa bem e bem. No entanto, existem algumas ressalvas com rebase que você deve estar ciente.
ressalva 1: Rebase cria novos commits
Rebasing um branch realmente criará novos commits. Os novos commits terão Sha:S diferente dos commits antigos. Isso geralmente não é um problema, mas você terá problemas se rebase um branch que existe fora do seu repositório local (por exemplo, se o seu branch já existe na origem).
se você enviar um branch com base em um repositório remoto que já contém o mesmo branch (mas com o histórico antigo), você:
- tem que forçar o push do branch (por exemplo,
git push --force-with-lease
), já que o Git não permitirá que você empurre um novo histórico para um branch existente de outra forma. Isso efetivamente substitui o histórico do ramo remoto fornecido por um novo histórico. - possivelmente fazer alguém muito infeliz, uma vez que a sua versão local do ramo dado já não corresponde ao ramo no controle remoto, o que pode levar a todos os tipos de problemas.
em geral, evite sobrescrever o histórico de uma ramificação em um controle remoto (a única exceção é reescrever o histórico de uma ramificação que está sob revisão de código – dependendo de como seu sistema de revisão de código funciona – mas essa é uma discussão diferente).
se você precisar rebase um branch que é compartilhado com outras pessoas em um controle remoto, um fluxo de trabalho simples é criar um novo branch que você rebase, em vez de rebasing o branch original. Supondo que você tenha my-topic-branch
verificado, você pode fazer:
git checkout -b my-topic-branch-2git fetch origingit rebase origin/mastergit push -u origin my-topic-branch-2
…e, em seguida, diga às pessoas que trabalham em my-topic-branch
para continuar trabalhando em my-topic-branch-2
. O ramo antigo é efetivamente obsoleto e não deve ser mesclado de volta ao mestre.
ressalva 2: Resolver conflitos em um rebase pode ser mais trabalho do que em uma mesclagem
se você receber um conflito em uma operação de mesclagem, resolverá todos os conflitos como parte desse commit de mesclagem.
no entanto, em uma operação de rebase, você pode potencialmente obter um conflito para cada commit no branch que você rebase.
na verdade, muitas vezes você descobrirá que, se tiver um conflito em um commit, encontrará conflitos relacionados (muito semelhantes) em commits subsequentes em seu branch, simplesmente porque commits em um branch de tópico tendem a estar relacionados (por exemplo, Modificando as mesmas partes do Código).
a melhor maneira de minimizar conflitos é acompanhar o que está acontecendo no master e evitar permitir que um branch de tópico seja executado por muito tempo sem rebasing. Lidar com pequenos conflitos de vez em quando é mais fácil do que lidar com todos eles em uma grande bagunça de conflito feliz no final.
alguns avisos para usuários do GitHub
GitHub é ótimo em muitas coisas. É fantástico para Git hosting, e tem uma interface web maravilhosa com navegação de código, boa funcionalidade de Markdown, Gist, etc.
Pull requests, por outro lado, tem algumas funções que frustram ativamente o histórico linear do Git. Seria muito bem-vindo se o GitHub realmente corrigisse esses problemas, mas até então você deveria estar ciente das deficiências:
“Merge pull request” permite fusões não lineares para master
pode ser tentador pressionar o botão verde e amigável “Merge pull request” para mesclar um branch de tópico em master. Especialmente quando se lê ” este branch está atualizado com o branch base. A fusão pode ser realizada automaticamente”.
o que o GitHub está realmente dizendo aqui é que o branch pode ser mesclado ao master sem conflitos. Ele não verifica se o branch pull request é ou não baseado no mestre mais recente.
em outras palavras, se você quiser um histórico linear, você precisa ter certeza de que o ramo pull request é reiniciado em cima do último mestre você mesmo. Tanto quanto eu posso dizer, nenhuma dessas informações está disponível através da interface da web do GitHub (a menos que você esteja usando “branches protegidos” – veja abaixo), então você precisa fazer isso a partir do seu cliente Git local.
mesmo que a solicitação pull seja devidamente reiniciada, não há garantia de que a operação de mesclagem na interface da web do GitHub será atômica (ou seja , alguém pode enviar alterações para master antes que sua operação de mesclagem passe-e o GitHub não reclamará).
então, na verdade, a única maneira de ter certeza de que suas ramificações estão devidamente rebased em cima do mestre mais recente é fazer a operação de mesclagem localmente e empurrar o mestre resultante manualmente. Algo ao longo das linhas:
git checkout mastergit pullgit checkout my-pullrequest-branchgit rebase mastergit checkout mastergit merge --no-ff my-pullrequest-branchgit push origin master
se você não tiver sorte e alguém conseguir fazer alterações para dominar entre suas operações de puxar e empurrar, sua operação de empurrar será negada. Isso é bom, no entanto, pois garante que sua operação seja atômica. Apenas git reset --hard origin/master
e repita as etapas acima até passar.
Nota: respeite as diretrizes do seu projeto W. R. T. revisão e teste de código. E. g. se você estiver executando testes automáticos (cria, análise estática, testes de unidade, …), como parte de uma solicitação, você provavelmente deve re-enviar o seu realocados ramo (usando git push -f, ou pela abertura de um novo PR) em vez de apenas atualizar o branch master manualmente.
a funcionalidade de ramificações protegidas incentiva fusões do master
se você estiver usando ramificações protegidas e verificações de status em seu projeto GitHub, você realmente terá proteção contra a fusão de um branch pull request no master, a menos que seja baseado no master mais recente (acho que a lógica é que as verificações de status realizadas no branch PR ainda devem ser válidas após a fusão com o master).
no entanto… se o branch pull request não for baseado no mestre mais recente, você será presenteado com um botão amigável chamado “update branch”, com o texto “Este branch está desatualizado com o branch base. Mesclar as últimas alterações do mestre neste ramo”.
neste ponto, sua melhor opção é rebase o ramo localmente e força-empurrá-lo para o pull request. Supondo que você tenha my-pullrequest-branch
verificado, Faça:
git fetch origingit rebase origin/mastergit push -f
infelizmente, GitHub pull requests não funcionam bem com force pushs, então algumas das informações de revisão de código podem se perder no processo. Se isso não for aceitável, considere criar uma nova solicitação de pull (ou seja, enviar sua ramificação com base em rebase para uma nova ramificação remota e criar uma solicitação de pull da nova ramificação).
conclusão
se você se preocupa com um histórico linear:
- Rebase seu ramo de tópico em cima do mestre mais recente antes de mesclá-lo para mestre.
- não mesclar mestre em seu ramo tópico. Rebase em vez disso.
- ao compartilhar sua ramificação de tópico com outras pessoas, crie uma nova ramificação sempre que precisar reiniciá-la (
my-topic-branch-2
,my-topic-branch-3
, …). - se você estiver usando solicitações pull do GitHub, esteja ciente:
- o botão “Mesclar” não garante que a ramificação PR seja baseada no mestre mais recente. Rebase manualmente quando necessário.
- se você estiver usando branches protegidos com verificações de status, nunca pressione o botão “Atualizar branch”. Rebase manualmente em vez disso.
se você não se importa muito com um histórico Git linear-fusão feliz!
lista de desejos para GitHub
para você boas pessoas que trabalham no GitHub: adicione suporte para fluxos de trabalho de rebase em solicitações pull (essas podem ser Opções de repositório opt-in).
- adicione a possibilidade de desativar o botão/Operação de mesclagem em solicitações pull se a ramificação não for baseada no mestre mais recente (isso não deve exigir o uso de verificações de status).
- adicione um botão” Rebase no último mestre”. Em muitos casos, esta deve ser uma operação livre de conflitos que pode ser facilmente feita através da interface da web.
- Preserve o histórico de solicitações pull (commits, comentários, …) após um push rebase / force.
Leave a Reply