Close

Git refs: An overview

Aprenda o básico sobre o Git com este tutorial com temática espacial.

Muitas formas de se referir a um commit

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:

bancos de dados
Material relacionado

Como mover um Repositório do Git completo

Logotipo do Bitbucket
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 para HEAD antes de acontecerem mudanças drásticas nele.
  • MERGE_HEAD – O(s) commit(s) que você está passando por merge para o branch atual com git 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.

Acessar commits usando refs relativos

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 no main
  • 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.

Pessoas colaborando usando uma parede cheia de ferramentas

Blog do Bitbucket

Ilustração do DevOps

Caminho de aprendizagem de DevOps

Demonstrações de funções no Demo Den com parceiros da Atlassian

Como o Bitbucket Cloud funciona com o Atlassian Open DevOps

Inscreva-se para receber a newsletter de DevOps

Thank you for signing up