Terminologia confusa do Git
(jvns.ca)- Mesmo usando Git há muito tempo, há muita sobreposição e reutilização de termos, então memorizar comandos não basta para prever o comportamento real
- Termos e símbolos como
HEAD,heads, detached HEAD,ours/theirs,../...mudam de significado conforme o contexto index,stagedecachedestão todos ligados a.git/index, masreset,reverterestore, apesar dos nomes parecidos, afetam de formas diferentes o histórico de commits, a árvore de trabalho e os ponteiros de branch- O fato de
mainrastrear o remoto e um remote-tracking branch comoorigin/mainsão coisas diferentes, echeckoutreúne em um só comando a troca de branch e a restauração de arquivos - Para entender Git, é preciso olhar não só para comandos isolados, mas também para o modelo interno em que branches, referências, commits, índice e estado de rastreamento remoto se encaixam
HEAD, branch e detached HEAD
- No Git, heads se refere a branches, e internamente os branches ficam armazenados no diretório
.git/refs/heads- O glossário oficial do Git distingue branch como todos os commits sobre ele e head como o commit mais recente, mas dá para ver isso como duas formas de olhar para o mesmo objeto
HEADaponta para o branch atual e fica armazenado em.git/HEAD- A própria expressão “
headé um branch, eHEADé o branch atual” mostra bem como a terminologia do Git pode ser confusa
- A própria expressão “
- O estado de detached HEAD é quando
HEADaponta diretamente para um ID de commit, e não para um branch- Ao fazer checkout de uma tag, como a tag não é um branch, você pode entrar em detached HEAD
- Nesse estado,
git pullnão funciona, egit pushtambém não funciona a menos que seja feito de uma forma especial git commit,git merge,git rebaseegit cherry-pickfuncionam, mas os novos commits podem ficar difíceis de encontrar porque não ficam ligados a nenhum branch- Você pode sair do detached HEAD criando um novo branch ou trocando para um branch existente
ours e theirs mudam entre merge e rebase
- Na resolução de conflitos,
git checkout --ours file.txtescolhe o arquivo do lado “ours”, mas a referência muda entre merge e rebase - No merge, o branch atual é
ours, e o branch que está sendo incorporado étheirs- Exemplo: se você fizer
git checkout merge-into-ourse depoisgit merge from-theirs, o branch atual seráours
- Exemplo: se você fizer
- No rebase, o branch atual é
theirs, e o branch de destino do rebase éours- Exemplo: se você fizer
git checkout theirse depoisgit rebase ours, o branch de destino seráours
- Exemplo: se você fizer
- Essa diferença tem relação com o fato de que
git rebase maininternamente faz merge repetido dos commits do branch atual sobre uma cópia do branchmain- Há um exemplo relacionado em um script shell que implementa
git rebasecomgit merge
- Há um exemplo relacionado em um script shell que implementa
- ourstheirs explica rapidamente os termos
oursetheirs- No VSCode, “current change” e “incoming change” também podem confundir pelo mesmo motivo
Mensagens de estado remoto e fast-forward
Your branch is up to date with 'origin/main'não quer dizer que omainatual esteja no estado mais recente do remoto- Quer dizer apenas que o branch local é igual ao
origin/maindo momento do últimogit fetchougit pull - Se o último fetch foi há 5 dias, então ele está “atualizado” segundo o estado de 5 dias atrás
- Quer dizer apenas que o branch local é igual ao
- O Git guarda no reflog informações sobre os fetches recentes, mas a mensagem de estado não mostra o horário “com base no último fetch”
can be fast-forwardedsignifica que o branch remoto está à frente do branch local e que não há commits locais extras, então dá para mover só o ponteiro para frente- Exemplo: se
mainéA-B-Ceorigin/mainéA-B-C-D-E, então depois degit pullambos viramA-B-C-D-E - Nesse caso não há possibilidade de conflito de merge
- Exemplo: se
- Quando fast-forward não é possível, é porque os dois branches divergiram
- Exemplo: se
maintem um commitXqueorigin/mainnão tem, eorigin/maintemDeEquemainnão tem, então não dá para fazer fast-forward - Nessa situação o Git informa que os dois branches estão em estado de divergence e que cada lado tem uma quantidade diferente de commits
- Exemplo: se
Sintaxe para selecionar commits: ^, ~, .., ...
HEAD^eHEAD~apontam ambos para o commit anterior imediatoHEAD^^^,HEAD~~~eHEAD~3apontam todos para o commit de 3 passos atrás
- Em commits de merge, que têm múltiplos pais, o significado de
HEAD^2eHEAD^3mudaHEAD^é o primeiro paiHEAD^2é o segundo paiHEAD^3é o terceiro paiHEAD~3significa pai do pai do pai, então é diferente deHEAD^3
git log main..testegit log main...testtambém funcionam de forma diferentemain..testmostra os commits que existem só emtesttest..mainmostra os commits que existem só emmainmain...testmostra juntos os commits exclusivos de cada lado
- Em
git diff,..e...são interpretados de forma diferente degit loggit diff test..maincompara incluindo tanto as mudanças do ladotestquanto do ladomaingit diff test...maincompara o ancestral comum com a ponta demain, mostrando diff de um lado só
- A confusão com a notação de pontos também é tratada em pain in dots
reference, refspec, tree-ish
- No Git, reference é usada com vários sentidos
- Branches e tags como
mainev0.2 HEAD, que aponta para o branch atual- Expressões como
HEAD^^^, que são interpretadas como IDs de commit
- Branches e tags como
- A documentação do Git chama com mais rigor expressões como
HEAD^^^de “revision parameters”, mas no uso cotidiano elas podem ser tratadas como referências - Uma symbolic reference é uma referência que aponta para outra referência, como
HEADHEADé um conceito central que afeta bastante o comportamento dos comandos principais do Git
- As configurações de remoto em
.git/configincluem um refspec- Exemplo:
+refs/heads/main:refs/remotes/origin/main - Em muitos casos, usa-se o valor padrão criado por
git cloneougit remote add
- Exemplo:
tree-ishse refere ao tipo de objeto que pode ocupar o lugar deTHINGemgit checkout THING .- ID de commit
- Referência de commit como
main,HEAD^^ouv0.3.2 - Um subdiretório dentro de um commit, como
main:./docs - Na prática, muitas vezes basta entender como “um commit ou uma referência de commit”
index, staged, cached
index,stagedecachedestão todos relacionados a.git/index.git/indexé o arquivo usado quando você faz stage das mudanças comgit add
- Existem vários termos espalhados por comandos diferentes para se referir à mesma coisa
git diff --cachedgit rm --cachedgit diff --staged.git/index
- Mas as flags
--indexe--cachednem sempre significam a mesma coisa- Um texto de Junio Hamano, maintainer principal do Git, explica as diferenças em detalhe
- Por motivos de desempenho, o index também lista arquivos untracked, mas em geral staging area não é entendida como incluindo arquivos untracked
reset, revert, restore
reset,reverterestoreparecem palavras parecidas, mas no Git afetam coisas diferentesgit revert COMMITcria um novo commit no branch atual que faz o oposto deCOMMIT- Se
COMMITadicionou 3 linhas, o novo commit remove essas 3 linhas
- Se
git reset --hard COMMITforça o branch atual a voltar para o estado deCOMMIT- É uma operação muito perigosa, porque apaga as mudanças novas feitas depois de
COMMIT
- É uma operação muito perigosa, porque apaga as mudanças novas feitas depois de
git restore --source=COMMIT PATHrestaura os arquivos dePATHpara o estado que tinham emCOMMIT- Não muda outros arquivos nem o histórico de commits
git reset --hardegit restore .sozinhos costumam se comportar de forma parecida, masgit reset --hard COMMITegit restore --source COMMIT .funcionam de forma completamente diferente
track, remote-tracking branch, untracked files
- O Git usa a palavra track de três formas diferentes
Untracked files:emgit statussignifica arquivos que o Git não gerencia e que não estão incluídos em commit nenhum- Um remote-tracking branch como
origin/mainé uma referência local com o ID do commit para o qual omaindo remotooriginapontava no momento do últimogit pullougit fetch - Um branch local pode ser configurado para rastrear um branch remoto, como em “branch foo set up to track remote branch bar from origin”
- Dizer que
mainé um branch que rastreia um remoto e dizer queorigin/mainé um remote-tracking branch são coisas diferentes mainé um branch de verdade, então você pode fazer commits e merges nele- Se em
.git/configele estiver configurado para rastrear omainremoto,git pullegit pushficam mais práticos de usar
- Se em
origin/main, apesar do nome “remote-tracking branch”, não é um branch comum- Você não pode fazer commit diretamente nele
- Ele só é atualizado quando
git pullougit fetchtrazem o estado mais recente domainremoto
checkout, reflog, merge/rebase/cherry-pick
git checkoutcoloca duas operações diferentes no mesmo comandogit checkout BRANCHtroca de branchgit checkout file.txtdescarta mudanças não staged emfile.txt
- Para reduzir essa confusão, o Git separou essas funções em
git switchegit restore, mas ocheckoutantigo ainda pode ser usado reflogsignifica reference log e é o registro do que uma referência apontou no passado- Referência é um termo amplo que inclui branches, tags e
HEAD - O reflog pode ajudar a sair de situações muito ruins no Git, como apagar por engano um branch importante
- Referência é um termo amplo que inclui branches, tags e
- merge, rebase e cherry-pick combinam commits de formas diferentes
- merge cria um novo commit que junta dois branches
- rebase copia, um por um, os commits do branch atual sobre o branch de destino
- cherry-pick é parecido com rebase, mas com sintaxe diferente, e copia commits para o branch atual
git rebase --onto main otherbranch mybranchpode ser usado para mover só parte dos commits demybranchpara cima demain- Na estrutura de exemplo, isso serviria quando você quiser fazer rebase apenas de
FeGsobremain - A sintaxe é difícil de memorizar porque envolve nomes de três branches
- Na estrutura de exemplo, isso serviria quando você quiser fazer rebase apenas de
commit e outros termos confusos
commité usado no Git tanto como verbo quanto como substantivo- Verbo: “faça commit com frequência”
- Substantivo: o commit mais recente de
main
- Um commit do Git pode ser visto de três formas
- Como um snapshot do estado atual de todos os arquivos
- Como um diff em relação ao commit pai
- Como o histórico de todos os commits anteriores
- Cada comando lida com commit de um jeito diferente
git showtrata commit como diffgit logtrata commit como históricogit restoretrata commit como snapshot
blob,tree,origin,upstream,downstream, a relação entrefetchepull,stash,worktree,subtreeesubmoduletambém entram na lista de termos confusos do Git- Entre os termos do GitHub, “pull request”, “squash and merge” e “rebase and merge” são citados como exemplos de nomenclatura confusa
Os três eixos da confusão recorrente
- Os termos mais confusos do Git podem ser resumidos em três pontos
headé branch, eHEADé o branch atual- “remote tracking branch” e “branch that tracks a remote” são coisas diferentes
index,stagedecachedapontam todos para a mesma coisa
- O texto de 2012 the most confusing git terminology também trata de como a terminologia do Git se conecta com a do CVS e do Subversion
- A confusão de termos no Git não aparece só em funcionalidades específicas, mas se repete em boa parte das funções centrais
- Especialmente ao falar de branches, “tracking” é usado de várias formas, o que dificulta perceber a diferença à primeira vista
- Algumas explicações incluem cantos raros do Git, então fica registrado o aviso de que pode haver algum engano
1 comentários
Comentários do Hacker News
Nomes de branch,
HEADe tags são todos ponteiros.HEADaponta para o commit que você está vendo agora, e um nome de branch comomy-featureaponta para o commit mais recente daquela branch. Se você fizergit checkout -b my-featurea partir demain, entãomain,my-featureeHEADpassarão a apontar para o commit mais recente demainSempre que você faz commit em
my-branch,HEADemy-branchse movem para apontar para o novo commit. detached HEAD significa que o commit para o qualHEADaponta não é apontado por nenhuma branch. A diferença entre tags e branches é que tags apontam para um commit específico e não se movemOutra coisa que me confundia era por que muitos comandos pareciam inconsistentes, e o motivo é que o Git assume argumentos padrão.
git checkout file.txté o mesmo quegit checkout HEAD -- file.txt, egit rebase mainquando você está emmy-branché o mesmo quegit rebase main my-branch. Só que a forma mais longa também pode ser executada a partir de outra branchPor fim, quando tudo vira um caos, o comando que te tira de praticamente qualquer estado estranho é
git reflog. Ele mostra todos os commits para os quaisHEADjá apontou. Mesmo assim, é bom ver que o Git reconhece essa confusão e está criando comandos com interfaces menos surpreendentes e mais simplesreflogOutra coisa que muita gente que usa Git não percebe é que as refs de branches e tags remotas que você buscou também são refs, então podem ser referenciadas em qualquer lugar onde um nome de ref seja aceito
Por exemplo, se você quiser dizer “não me importo com o que tem nesta branch, não faça fast-forward, merge nem rebase; só sobrescreva minha branch local com o conteúdo remoto”, pode fazer assim:
git checkout foo,git reset --hard origin/fooHEADnão aponta diretamente para um commit; normalmente ele aponta para um ponteiro de branch, comomy-branch, e esse ponteiro de branch é que aponta para o commit. O estado detached HEAD é a exceçãoQuando você faz commit em
my-branch, não é queHEADemy-branchse movam ambos para o novo commit;HEADcontinua apontando para o ponteiromy-branch, e sómy-branché movido para apontar para o novo commit. Claro, se você seguirHEAD, depoismy-branch, e então o commit, vai chegar ao novo commitdetached HEAD é quando
HEADaponta diretamente para um commit em vez de para um ponteiro de branch. Mesmo no estado detached HEAD,HEADe um ponteiro de branch podem ambos apontar para o commit mais recente. Se você olhargit log --decorate, em vez do usual(HEAD -> my-branch), verá algo como(HEAD, my-branch)no commit mais recenteJá uma tag anotada pode conter mensagem, timestamp, SHA etc. Elas são objetos Git de verdade e se comportam de forma bem parecida com objetos commit, mas normalmente ainda assim referenciam outro objeto Git, geralmente um commit
git rev-parse HEADantes de começar um procedimento complicado com muitos conflitos ou várias movimentações, e copiar essa string para algum lugar seguroNo mínimo, isso dá a tranquilidade de ter marcado exatamente um estado que você sabe que está correto. Em vez de voltar para algum ponto já várias etapas dentro do caos, normalmente dá para voltar para o último estado bom. Claro, assumindo que você não tenha usado coisas como
git gcougit filter-branchde forma descuidadaParece piada, mas não é bem piada dizer que, para usar Git, você realmente precisa de um diploma em ciência da computação. Concordo que os argumentos padrão do Git criam uma experiência de uso muito inconsistente
git checkout .,git reset --hard/softegit cleanuptambém parecem todos absurdamente parecidos, mas na prática são muito diferentes. Ainda assim, continuo preferindo Git a Perforce ou SVN. Antigamente eu talvez gostasse um pouco mais do Mercurial, mas não sei se ainda seria assim hojegit reflogé rei e mostra a verdadeLembro de ter visto um texto em que Linus Torvalds dizia estar surpreso que as pessoas começaram a usar Git diretamente, em vez de colocarem uma camada mais amigável por cima. Se a indústria fosse capaz de fazer autocrítica, admitiria que escolher Git foi um erro e migraria para outro sistema de controle de versão. Em vez de desperdiçar uma quantidade absurda de tempo com uma ferramenta cujo único trabalho é armazenar texto
Claro que pode melhorar. Mesmo assim, o ódio que o Git recebe sempre me surpreende. Git é uma ferramenta fantástica e está muito à frente de ferramentas feitas para não desenvolvedores, como Word ou Google Docs
Talvez eu diga isso porque já tenho idade suficiente para lembrar da época em que o Subversion era rei
Toda semana vejo colegas se confundindo por causa do Git, vejo pedidos de ajuda online e vejo mais um texto como este tentando explicar de novo algo que deveria ser simples
Não me lembro de ver gente desperdiçando horas para desfazer a bagunça que fizeram em SVN, TFVC, Perforce etc. enquanto estavam programando
Ferramentas existem para facilitar a vida. Se não fazem isso, não vale a pena gastar tempo com elas
Ainda queria que existisse essa diversidade. A monocultura do Git também significa que qualquer tentativa de substituir o Git precisa primeiro reimplementá-lo. Então acabamos ficando presos ao Git quase para sempre, como ASCII ou a tecla Scroll Lock.
A preocupação central é que o GitHub é bom demais, então vamos ficar presos ao Git a menos que o GitHub separe o Git do motor de gerenciamento de texto e passe a permitir outros motores.
https://trolololo.xyz/github
https://news.ycombinator.com/item?id=38098109
É uma terminologia de abstração com vazamento.
cache, e era o termo mais próximo da ciência da computação.Depois, ao que parece, o Junio trocou para
index, mais específico para um sistema de controle de versão distribuído, e a maioria dos usuários passou a chamar isso de staging area. Agora essa evolução da terminologia ficou fossilizada na UI e nos internals do Git.pull.origin/masternão significa o master que está na origin, e sim o commit para o qual o últimoorigin/masterconhecido apontava. Esse valor é atualizado quando você fazfetch, epullfazfetchautomaticamente.Se você olhar
ls -l .git/refs/remotes/origin/master, vai ver queorigin/masteré só um arquivo no seu sistema. Também dá para ver quando ele mudou. Ele não é atualizado magicamente.Quando você faz
git fetch origin, se houve mudança, o timestamp e o conteúdo mudam. Você pode confirmar comcat .git/refs/remotes/origin/master.A base do Git é bem simples; dá para implementar as estruturas de dados centrais e algumas operações em um dia. Vale muito a pena entender isso. De algum jeito, o Git conseguiu construir uma interface de usuário muito complexa em cima de um núcleo bastante simples.
master. Normalmente não posso fazer push paramaster, ou simplesmente não quero trabalhar direto emmaster. Nesse caso, não há motivo para manter uma branch local mutável chamadamaster.Então só preciso manter
origin/masteratualizado, e para issofetchbasta.git fetch && git pullEstou esperando o dia em que vou me aposentar daqui a 3 anos e finalmente lavar as mãos desse lixo quebrado.
git status, e adicionar a flag--porcelainajudou muito. Ela gera uma saída mais concisa e mais fácil de processar por programa.Fiquei curioso sobre quantos comandos têm saídas mais amigáveis para máquina e acabei indo até a página “Git Internals - Plumbing and Porcelain” do git-scm. Em resumo, o Git foi escrito desde o início mais como um conjunto de ferramentas para lidar com controle de versão do que como um sistema de versionamento refinado.
Muita gente que usou Git desde o começo aprendeu tarefas de controle de versão com esses comandos de baixo nível e acabou propagando esse fluxo de trabalho para outras pessoas. Essa é a camada de plumbing. Depois o Git criou uma camada mais polida, chamada porcelain.
Ainda não entendo totalmente quais comandos pertencem a qual camada, mas isso ajudou a entender os fluxos de trabalho mais novos recomendados nos últimos anos. Também me ajuda a julgar melhor quando vou mudar meu próprio fluxo de trabalho.
https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
Infelizmente, eles acabaram hardcodando essa UI para ficar mais rápida, mas sem melhorá-la.
Olhando para trás, devo ter ficado extremamente confuso quando comecei, mas provavelmente fingi que não estava para parecer descolado.
https://news.ycombinator.com/item?id=28143078
“Your branch is up to date with ‘origin/main’” na verdade é um pouco enganoso. Dá para entender como se o branch
mainestivesse atualizado, mas na prática não é issoTambém não precisa exagerar no “falando estritamente”. Não se aplica aqui o problema dos dois generais? Se for realmente minucioso, não dá para saber se está “atualizado” exatamente neste instante. Mesmo fazendo
fetchantes dostatus, continua sendo assimEntão, qual seria uma expectativa razoável? Algo como: a ref no banco de objetos do meu computador é igual, da última vez que verifiquei, à ref de um outro banco de objetos lá do outro lado
Uma simples chamada de
statusnão pode fazer umfetchpela rede, nem ficar relembrando toda vez os princípios básicos da ferramenta que você está usando. Pelo menos eu acho isso. Mas, surpreendentemente, muitos comentários na resposta do StackOverflow acham que essas duas coisas seriam necessáriasAlgo como “is up to date with the origin’s main as of your last fetch 5 days ago” parece mais razoável. Porque só informa há quanto tempo foi o último
fetch[1] https://stackoverflow.com/questions/27828404/why-does-git-st...
Outra coisa: eu achava que
ORIG_HEADtinha relação com a “head da origin”, comoFETCH_HEAD. Mas não era isso. Essa “pseudoref” parece ser usada como ponto de salvamento antes de reescritas mais complexas, como rebase, e foi implementada antes de existir reflog. Provavelmente significa “original head”“Your branch is up to date with ‘origin/main’” está tecnicamente correto, mas a expressão up to date, lida por alto, sugere duas coisas. Que
mainé igual aorigin/maine queorigin/maintambém está atualizado, isto é, que foi atualizado durante o comando ou mantido automaticamente em dia e sincronizado com sucesso recentementeIsso não é um sinal legível por máquina que precisa ser verdadeiro por um ciclo de CPU; é uma mensagem de estado mostrada ao usuário. Mesmo sem entrar em teoria de redes, essa interpretação é razoável
“Up to date” parece mais algo de ter alcançado no tempo do que no espaço. A posição do branch local está mais perto de um “para onde este branch aponta” no espaço, enquanto o estado da ref remota está mais perto de um “quando foi a última vez que atualizei essa ref remota” no tempo. Claro, isso é bem subjetivo, e ambas podem ter os dois sentidos
Formulações melhores poderiam ser “Your branch matches origin/main”, “Your branch’s head is the same as origin/main”, “Your branch is pointing to the same commit as origin/main”. É um meio-termo entre clareza e verbosidade. Também daria para acrescentar entre parênteses algo como “remote ref last updated 5 days ago”, como sugerido pelo autor
statusfazer umfetchpela rede. Eu acho que operações de rede devem ser explícitas, não implícitas. Quando façogit status, quero saber o estado do meu repositório no sistema de arquivosSe eu quiser saber o que está acontecendo em outros remotos, posso eu mesmo fazer
fetche depois compararstatuscomeça. Pode existir uma condição de corrida enquanto a informação está sendo impressa no terminal, mas é uma janela de tempo muito pequenaEm geral isso significa querer um
fetch. O problema dos dois generais só se aplica ao lado remoto. O remoto pode continuar retransmitindo por um tempo se não receber confirmação de que o cliente recebeu os dados. Isso não é problema do clienteSe o cliente não conseguir receber os dados do servidor, o comportamento correto seria ficar esperando e, depois de um timeout, mostrar um erro; e o
fetchatual provavelmente já faz issoEsta ferramenta é compartilhada com frequência e se encaixa bem aqui também. Ela visualiza o modelo interno do Git e o efeito de vários comandos
https://learngitbranching.js.org/
Aprendi mais usando essa ferramenta por 10 minutos do que em cerca de 10 anos de experiência anterior com Git. Recomendo muito
“pull” é a ação de mesclar mudanças remotas no repositório local. O que o usuário está realmente pedindo é que o servidor mescle suas mudanças remotas em algum branch. O “merge request” do GitLab é o termo correto
Já vi gente fazer
fetche achar que commits tinham sumido. Eles só ficaram escondidos porque o repositório andou para trás no tempo. O Git deixa esse tipo de detalhe escondido nostatus; se você não usagit-promptoupowerline-shell, é como trabalhar no escurogit-request-pull(1)era que cada um de nós teria seu próprio repositório Git em algo como kernel.org/git ou redhat.com/git, e pediria por email que outras pessoas fizessem pull de repositórios hospedados em servidores, organizações e domínios diferentesO GitHub se inspirou no comando
request-pulldo Git, mas reinterpretou isso como uma mesclagem de um repositório GitHub para outro repositório GitHubPelo menos, eu sempre li dessa forma
Resumindo, não é um pedido ao servidor, e sim a outra pessoa. Você está pedindo que a outra pessoa faça pull do seu branch para revisar as mudanças com as quais você quer contribuir para o projeto
[0] https://git-scm.com/book/en/v2/Distributed-Git-Contributing-...