28 pontos por GN⁺ 2023-11-24 | 1 comentários | Compartilhar no WhatsApp
  • Muitas pessoas acham que o funcionamento das branches do Git não é intuitivo.
  • Explica a diferença entre o modelo intuitivo mais comum sobre branches no Git e como elas são realmente representadas internamente no Git.
  • Mostra que o modelo intuitivo e a forma como o Git realmente funciona estão, na prática, muito intimamente relacionados.
  • Discute as limitações do modelo intuitivo e por que ele pode causar problemas.

Modelo intuitivo de branch

  • Muitas pessoas pensam em uma branch por analogia a um 'galho de macieira'.
  • No Git, uma branch não tem o conceito de 'pai', o que é diferente de pensar que ela se ramificou a partir de main.

No Git, uma branch é o histórico inteiro

  • No Git, uma branch não é apenas os commits ramificados, mas inclui todo o histórico anterior de commits.
  • Mostra, por meio de um repositório de exemplo, que tanto main quanto mybranch têm 4 commits.

Branches são armazenadas como IDs de commit

  • Internamente no Git, uma branch é armazenada como um pequeno arquivo de texto que contém um ID de commit.
  • O commit mais recente de cada branch é registrado nesse arquivo.
  • Como não existe relação de pai e filho entre branches, o Git não conhece a relação entre elas.

A intuição das pessoas normalmente não está tão errada assim

  • Dizer que a intuição das pessoas sobre Git está 'errada' é um tanto tolo.
  • Mesmo um modelo 'errado' pode ser útil na prática.

Rebase usa o conceito 'intuitivo' de branch

  • O rebase reaplica em main apenas os commits da branch 'intuitiva'.
  • O resultado do rebase corresponde ao modelo intuitivo.

Merge também usa o conceito 'intuitivo' de branch

  • O merge não copia commits, mas precisa de um commit base compartilhado.
  • A merge base encontra o commit em que a branch se separou, com base no modelo intuitivo.

Pull requests do GitHub também usam a ideia intuitiva

  • Ao criar no GitHub um pull request para fazer merge de mybranch em main, ele mostra apenas os commits da branch intuitiva.

A intuição é boa, mas tem limites

  • A definição intuitiva de branch combina bem com o trabalho real no Git, mas o Git não consegue reconhecer uma branch ramificada de main como algo diferente.

Trunk e branches derivadas

  • As pessoas percebem main e mybranch de forma diferente, e isso afeta a maneira como usam o Git.
  • O Git não distingue se uma branch é uma 'ramificação' de outra branch.

O Git pode fazer rebase 'ao contrário'

  • Como o Git não informa se uma branch é uma 'ramificação' de outra, o usuário precisa saber qual branch rebasing em qual momento.
  • Tanto git rebase main quanto o rebase inverso git rebase mybranch são possíveis. O mesmo vale para merge.

A ausência de hierarquia entre branches no Git é um pouco estranha

  • Dizer que a branch main não é especial vem do fato de que o Git não reconhece relações entre branches.
  • Existem relações entre as branches, mas o Git não sabe de nada.

A UI de branches do Git também é estranha

  • Quando você quer ver apenas os commits 'ramificados', a forma de usar git log e git diff é diferente.

No GitHub, a branch padrão é especial

  • O GitHub tem uma 'branch padrão', e ela exerce um papel especial.

Opinião do GN⁺

O ponto mais importante deste texto é entender a diferença entre a compreensão intuitiva que as pessoas têm sobre branches no Git e a forma como o Git realmente funciona. Este texto deve ajudar engenheiros de software iniciantes a entender melhor o conceito de branches no Git e a usá-las de maneira mais eficaz. É interessante e útil observar como o modelo intuitivo de branch corresponde ao trabalho real e como o Git não trata as relações entre branches.

1 comentários

 
GN⁺ 2023-11-24
Comentários do Hacker News
  • Um branch é um ponteiro para um commit, e esse ponteiro é atualizado sempre que um novo commit é criado. Dá para pensar em um branch como um nome flutuante, parecido com uma tag. Como o próprio commit aponta para o commit pai, um branch é uma cadeia de commits relacionados com um ponto de entrada nomeado. Ao apagar o branch, esse rótulo nomeado deixa de existir, e restam apenas a cadeia de commits relacionados.
  • É mais fácil entender a linhagem dos commits se você pensar nela como ponteiros que apontam "para trás", e não "para frente". Como um branch é um ID de commit, ao seguir os links para os pais você encontra todo o histórico daquele branch. O "ponto de ramificação" é o ponto em que duas cadeias de commits se encontram, e um commit de merge é especial porque indica que dois históricos foram unidos em um só.
  • Em projetos pessoais, amigos costumam ficar irritados quando me veem usando git reset --hard e git stash para manipular mudanças e ponteiros de branch. Para desfazer um merge errado, uso git reset --hard <último commit antes do merge>; para aplicar pequenas alterações de um branch local no branch principal, uso git stash, depois faço checkout do branch principal e aplico com git stash apply.
  • No Git não existe o conceito de que o main é especial, mas ferramentas como o GitLab oferecem recursos de branch protegido para reduzir erros. A ideia de branches "pai" e "filho" pode ser realmente interessante, e seria preciso oferecer suporte a vários branches "pai" para branches de suporte de longo prazo.
  • Ao fazer merge, rebase ou pull request, é preciso indicar explicitamente o outro branch. O Git não sabe qual branch o usuário considera como base. Às vezes você pode querer fazer merge de um feature branch em outro feature branch, então é importante especificar claramente qual branch será mesclado em qual outro.
  • Mesmo que a intuição das pessoas esteja tecnicamente parcialmente errada, há bons motivos para elas terem essa intuição.
  • Existe um tutorial interativo voltado para quem já sabe usar git add e git commit. Ele ajuda a visualizar os branches durante a leitura.
  • Se você lembrar que, ao executar comandos do Git, você está "sempre" modificando o branch atual, fica "fácil" entender a sintaxe do Git. Por exemplo, git merge my-branch faz merge de my-branch no branch atual, e git rebase my-branch faz rebase do branch atual sobre my-branch.
  • Seria bom se um branch (head) tivesse uma "cauda" que apontasse para o commit-base onde aquele branch começou. Como branches são rebaseados com frequência, às vezes é preciso pensar sobre onde eles começaram. Seria mais conveniente se o Git informasse que o commit-base pertence ao main.
  • Ao enviar um "patch" para uma mailing list, é possível incluir opcionalmente o commit-base. Isso acontece porque pode não estar claro se a mudança se baseia na versão mais recente, no branch principal de desenvolvimento ou em um branch de integração. Também é preciso ter a base em mente ao usar git range-diff. Essa ferramenta compara dois intervalos, como main..previous e main..current.
  • Ao reler minhas opiniões pessoais sobre branches, reaprendi algumas coisas que eu já tinha esquecido.