Git refs: An overview
Aprenda o básico sobre o Git com este tutorial com temática espacial.
Ao entender as várias maneiras de se referir a um commit, você torna todos esses comandos muito mais poderosos. Neste capítulo, vamos esclarecer sobre o funcionamento interno de comandos comuns, como git checkout
, git branch
e git push
, explorando os muitos métodos de referência a um commit.
Também vamos aprender como reviver commits aparentemente “perdidos” acessando-os através do mecanismo reflog do Git.
Sinais numéricos
A maneira mais direta de fazer referência a um commit é por meio do hash SHA-1. Ele atua como o ID exclusivo para cada commit. Você pode encontrar o hash de todos os seus commits na saída do git log
.
commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message
Ao passar o commit para outros comandos do Git, você só precisa especificar caracteres suficientes para identificar exclusivamente o commit. Por exemplo, você pode inspecionar o commit acima com o git show
executando o seguinte comando:
git show 0c708f
Às vezes, é necessário resolver um branch, uma tag ou outra referência indireta no hash de commit correspondente. Para chegar lá, você pode usar o comando git rev-parse
. O comando a seguir retorna o hash do commit apontado pelo branch main
:
Material relacionado
Como mover um Repositório do Git completo
VER SOLUÇÃO
Aprenda a usar o Git com o Bitbucket Cloud
git rev-parse main
Esse comando é particularmente útil ao escrever scripts personalizados que aceitam uma referência de commit. Em vez de analisar a referência do commit manualmente, você pode deixar o git rev-parse
normalizar a entrada para você.
Refs
Um ref é a maneira indireta de se referir a um commit. Você pode pensar nisso como um alias intuitivo para um hash de commit. Esse é o mecanismo interno do Git de representar branches e tags.
Refs são armazenados como arquivos de texto normais no diretório .git/refs
, onde .git
costuma ser chamado de .git
. Para explorar as referências em um dos seus repositórios, navegue até .git/refs
. Você deve ver a estrutura a seguir, mas ela vai conter arquivos diferentes, dependendo de quais branches, tags e remotos você tem em seu repositório:
.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9
O diretório heads
define todos os branches locais no repositório. Cada nome de arquivo corresponde ao nome do branch correspondente, e dentro do arquivo você vai encontrar um hash de commit. Esse hash de commit é a localização da ponta do branch. Para verificar que é assim, tente executar os dois comandos a seguir a partir da raiz do repositório do Git:
# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main
O hash do commit retornado pelo comando cat
deve corresponder ao ID do commit exibido pelo git log
.
Para alterar a localização do branch main
, tudo o que o Git precisa fazer é alterar o conteúdo do arquivo refs/heads/main
. Da mesma forma, criar um novo branch é apenas uma questão de escrever um hash de commit em um novo arquivo. Essa característica é parte da razão pela qual os branches do Git são tão leves em comparação com o SVN.
O diretório de tags
funciona exatamente da mesma maneira, mas contém tags em vez de branches. O diretório remotes
lista todos os repositórios remotos que você criou com o git remote
como subdiretórios separados. Dentro de cada um, você vai encontrar todos os branches remotos que foram buscados em seu repositório.
Specifying refs
Ao passar uma ref para um comando do Git, você pode definir o nome completo dela ou usar um nome curto e deixar o Git pesquisar uma ref correspondente. Você já deve estar familiarizado com nomes curtos para refs, pois é o que você está usando a cada vez que se refere a um branch pelo nome.
git show some-feature
O argumento some-feature
no comando acima é, na verdade, um nome abreviado para o branch. O Git resolve esse argumento para refs/heads/some-feature
antes de usá-lo. Você também pode especificar a referência completa na linha de comando, assim:
git show refs/heads/some-feature
Esse comando evita qualquer ambiguidade em relação à localização do ref, o que é necessário, por exemplo, se você tiver uma tag e um branch chamados some-feature
. No entanto, se você estiver usando convenções de nomenclatura adequadas, a ambiguidade entre tags e branches quase nunca vai ser um problema.
Vamos ver mais nomes de ref completos na seção Refspecs.
Packed refs
Para repositórios grandes, o Git vai executar periodicamente uma coleta de lixo para remover objetos desnecessários e compactar refs em um único arquivo para um desempenho mais eficiente. Você pode forçar essa compactação com o comando de coleta de lixo:
git gc
Esse comando move todos os arquivos individuais de branch e tag na pasta refs
para um único arquivo chamado packed-refs
localizada na parte superior do diretório .git
. Se você abrir esse arquivo, vai encontrar um mapeamento de hashes de commit para refs:
00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9
Por fora, a funcionalidade normal do Git não vai ser afetada de forma alguma. Mas, se você está se perguntando por que sua pasta .git/refs
está vazia, é para lá que os refs foram.
Special refs
Além do diretório refs
, existem algumas refs especiais que residem no diretório .git
de nível superior . Eles estão listados abaixo:
HEAD
– O commit/branch do checkout atual.FETCH_HEAD
– O branch obtido há menos tempo de um repositório remoto.ORIG_HEAD
– Uma referência de backup paraHEAD
antes de acontecerem mudanças drásticas nele.MERGE_HEAD
– O(s) commit(s) que você está passando por merge para o branch atual comgit merge
.CHERRY_PICK_HEAD
– O commit que você está fazendo cherry-pick.
Essas referências são todas criadas e atualizadas pelo Git quando necessário. Por exemplo, o comando git pull
primeiro executa o git fetch
, que atualiza a referência FETCH_HEAD
. Em seguida, ele executa git merge FETCH_HEAD
para terminar de extrair os branches buscados para o repositório. Claro, você pode usar todas essas como qualquer outra ref, como tenho certeza de que você fez com o HEAD
.
Esses arquivos contêm conteúdo diferente, dependendo do tipo e do estado do repositório. A referência HEAD
pode conter uma referência simbólica, que é apenas uma referência a outra ref em vez de um hash de commit, ou um hash de commit. Por exemplo, dê uma olhada no conteúdo do HEAD
quando você estiver no branch main
:
git checkout main cat .git/HEAD
Assim vai ser gerado o resultado ref: refs/heads/main
, o que significa que HEAD
aponta para a ref refs/heads/main
. É assim que o Git sabe que o branch main
está sendo verificado no momento. Se você mudasse para outro branch, o conteúdo do HEAD
seria atualizado para refletir o novo branch. Mas, se você fosse consultar um commit em vez de um branch, HEAD
conteria um hash de commit em vez de um ref simbólico. É assim que o Git sabe que está em um estado HEAD desanexado.
Em geral, HEAD
é a única referência que você vai usar diretamente. Os outros geralmente só são úteis ao escrever scripts de nível inferior que precisam de hook ao funcionamento interno do Git.
Especificações de refs
Um refspec mapeia um branch no repositório local para um branch em um repositório remoto. Assim é possível gerenciar branches remotos usando comandos locais do Git e configurar alguns comportamentos avançados de git push
e git fetch
.
Um refspec é especificado como [+]
<src>
:
<dst>
. O parâmetro <src>
é o branch de origem no repositório local, e o parâmetro <dst>
é o branch de destino no repositório remoto. O sinal +
opcional é para forçar o repositório remoto a realizar uma atualização sem avanço rápido.
Refspecs podem ser usados com o comando git push
para dar um nome diferente ao branch remoto. Por exemplo, o comando a seguir envia o branch main
para o repositório remoto origin
como um git push
comum, mas usa qa-main
como o nome do branch no repositório origin
. É um recurso útil para equipes de controle de qualidade que precisam enviar seus próprios branches para um repositório remoto.
git push origin main:refs/heads/qa-main
You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:
git push origin :some-feature
É um recurso muito conveniente, pois você não precisa entrar no repositório remoto e excluir manualmente o branch remoto. Observe que, a partir do Git v1.7.0, você pode usar a marcação --delete
em vez do método acima. O comando a seguir vai ter o mesmo efeito que o comando acima:
git push origin --delete some-feature
Ao adicionar algumas linhas ao arquivo de configuração do Git, você pode usar refspecs para alterar o comportamento do git fetch
. Por padrão, o git fetch
busca todos os branches no repositório remoto. A razão é a seguinte seção do arquivo .git/config
:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*
A linha de fetch
diz ao git fetch
para baixar todos os branches do repositório origin
. Mas alguns fluxos de trabalho não precisam de todos eles. Por exemplo, muitos fluxos de trabalho de integração contínua só se preocupam com o branch main
. Para buscar somente o branch main
, altere a linha de fetch
para corresponder ao seguinte:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main
Você também pode configurar o git push
de maneira semelhante. Por exemplo, se você quiser sempre enviar o branch main
para qa-main
no remoto origin
(como fizemos acima), você mudaria o arquivo de configuração para:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main
Refspecs dão a você controle completo sobre como vários comandos do Git transferem branches entre repositórios. Eles permitem que você renomeie e exclua branches do seu repositório local, busque/envie para branches com nomes diferentes e configure git push
e git fetch
para trabalhar apenas com os branches que você quer.
Relative refs
Você também pode fazer referência a commits relativos a outro commit. O caractere ~
permite que você alcance os commits pais. Por exemplo, o seguinte mostra o avô de HEAD
:
git show HEAD~2
Mas, ao trabalhar com commits de merge, as coisas ficam um pouco mais complicadas. Como os commits de merge têm mais de um pai, há mais de um caminho que você pode seguir. Para merges de 3 vias, o primeiro pai é do branch em que você estava quando executou o merge, e o segundo pai é do branch que você passou para o comando git merge
.
O caractere ~
sempre vai seguir o primeiro pai de um commit de merge. Se você quiser seguir um pai diferente, você precisa especificar qual deles com o caractere ^
. Por exemplo, se HEAD
for um commit de merge, o seguinte vai retornar o segundo pai de HEAD
.
git show HEAD^2
Você pode usar mais de um caractere ^
para mover mais de uma geração. Por exemplo, assim você exibe o avô do HEAD
(supondo que seja um commit de merge) que repousa sobre o segundo pai.
git show HEAD^2^1
Para esclarecer como ~
e ^
funcionam, a figura a seguir mostra como alcançar qualquer commit de A
usando referências relativas. Em alguns casos, existem várias maneiras de chegar a um commit.
Refs relativos podem ser usados com os mesmos comandos que um ref normal pode ser usado. Por exemplo, todos os comandos a seguir usam uma referência relativa:
# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3
The reflog
O reflog é a rede de segurança do Git. Ele registra quase todas as alterações feitas no repositório, independentemente de você ter feito o commit de um instantâneo ou não. Você pode pensar nisso como uma história cronológica de tudo o que você fez em seu repositório local. Para visualizar o reflog, execute o comando git reflog
. Ele deve produzir algo parecido com o seguinte:
400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature
Essas informações podem ser traduzidas da seguinte forma:
- Você acabou de verificar
HEAD~2
- Antes disso, você alterou uma mensagem de commit
- Antes disso, você fez o merge do branch
feature
nomain
- Antes disso, você fez o commit de um instantâneo
A sintaxe HEAD{
permite referenciar commits armazenados no reflog. Funciona muito como as referências HEAD~
da seção anterior, mas o
se refere a uma entrada no reflog em vez do histórico de commits.
Você pode usar essa função para reverter para um estado que, do contrário, seria perdido. Por exemplo, digamos que você acabou de descartar uma nova função com o git reset
. O reflog deve ficar assim:
ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature
Os três commits antes do git reset
agora estão pendentes, o que significa que não há como referenciá-los, exceto por meio do reflog. Agora, digamos que você perceba que não deveria ter jogado fora todo o seu trabalho. Tudo o que você precisa fazer é consultar o commit HEAD@ {1}
para voltar ao estado do seu repositório antes de executar o git reset
.
git checkout HEAD@{1}
Essa ação coloca você em um estado de HEAD
separado. A partir daqui, você pode criar um novo branch e continuar trabalhando na característica.
Resumo
Agora você deve se sentir confortável se referindo a commits em um repositório do Git. Aprendemos como branches e tags foram armazenados como refs no subdiretório .git
, como ler um arquivo packed-refs
, como HEAD
é representado, como usar refspecs para push e fetching avançados e como usar os operadores ~
e ^
relativos para percorrer uma hierarquia de branches.
Também demos uma olhada no reflog, que é uma forma de referenciar commits que não estão disponíveis por nenhum outro meio. Essa é uma ótima maneira de se recuperar daquelas pequenas situações em que você pensa: “Opa, eu não deveria ter feito isso”.
O objetivo do artigo era ser capaz de escolher o commit exato de que você precisa em qualquer cenário de desenvolvimento. É muito fácil aproveitar as habilidades que você aprendeu neste artigo em relação ao seu conhecimento existente do Git, já que alguns dos comandos mais comuns aceitam refs como argumentos, incluindo git log
, git show
, git checkout
, git reset
, git revert
, git rebase
e muitos outros.
Compartilhar este artigo
Próximo tópico
Leitura recomendada
Marque esses recursos para aprender sobre os tipos de equipes de DevOps ou para obter atualizações contínuas sobre DevOps na Atlassian.