Git
Chapters ▾ 2nd Edition

10.3 Funcionamento Interno do Git - Referências do Git

Referências do Git

Você pode executar algo como git log 1a410e para ver todo o seu histórico, mas você ainda precisa lembrar que 1a410e é o último commit para poder caminhar nesse histórico para encontrar todos esses objetos. Você precisa de um arquivo em que você possa armazenar o valor do SHA-1 com um simples nome para que você possa usar essa referência em vez do valor de um SHA-1 puro.

No Git, chamamos isso de “referências” (references) ou “refs”; você pode encontrar os arquivos que contém os valores SHA-1 no diretório .git/refs. No projeto atual, este diretório não contém nenhum arquivo, mas contém uma simples estrutura:

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f

Para criar uma nova referência que irá te ajudar a lembrar onde está seu último commit, você pode tecnicamente fazer algo tão simples quanto isto:

$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master

Agora, você pode usar a referência head que você acabou de criar em vez do valor SHA-1 nos seus comandos Git:

$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Nós não encorajamos você a editar diretamente arquivos de referência. O Git provê um comando mais seguro para fazer isso se você quiser atualizar uma referência, chamado update-ref:

$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9

Isto é o que uma branch é basicamente: uma simples referência para a cabeça de uma linha de trabalho. Para criar uma branch no segundo commit, você pode fazer isto:

$ git update-ref refs/heads/test cac0ca

Sua branch irá conter apenas o trabalho a partir desse commit:

$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Agora, seu banco de dados do Git conceitualmente aparenta ser algo assim:

Objetos do diretório Git com todas as referências _head_ incluídas.
Figure 152. Git directory objects with branch head references included.

Quando você executa comandos como git branch (nome da branch), o Git basicamente executa esse comando update-ref para adicionar o SHA-1 do último commit da branch que você está em qualquer nova referência que você quer criar.

A HEAD

A questão agora é, quando você executa git branch (nome da branch), como o Git sabe o SHA-1 do último commit? A resposta é o arquivo HEAD.

O arquivo HEAD é uma referência simbólica para a branch que você está no momento. Queremos dizer por referência simbólica que, ao contrário de uma referência normal, em geral ela não contém um valor de um SHA-1, mas um ponteiro para outra referência. Se você olhar para o arquivo, normalmente você verá algo como isto:

$ cat .git/HEAD
ref: refs/heads/master

Se você executar git checkout test, o Git atualizará o arquivo de forma que ele ficará assim:

$ cat .git/HEAD
ref: refs/heads/test

Quando você executa git commit, ele cria um objeto commit, especificando como pai desse objeto commit o valor do SHA-1 que a referência contida em HEAD aponta.

Você pode alterar manualmente esse arquivo mas, novamente, um comando mais seguro existe para fazer isso: symbolic-ref. Você pode ler esse arquivo de sua HEAD através deste comando:

$ git symbolic-ref HEAD
refs/heads/master

Você também pode definir o valor de HEAD:

$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test

Você não pode definir o valor de uma referência simbólica fora do estilo refs:

$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/

Tags

Nós acabamos de falar sobre os três principais tipos de objetos, mas existe também um quarto. O objeto tag é bem parecido com um objeto commit, ele contém um tagger, a data, a mensagem, e uma referência. A principal diferença é que um objeto tag geralmente aponta para um commit em vez de uma tree. Ele é bem parecido com uma referência do tipo branch, mas ele nunca se move - ele sempre aponta para o mesmo commit mas dá a ele um nome mais amigável.

Como discutimos em [ch02-git-basics], exitem dois tipos de tags: anotada e leve. Você pode criar uma tag leve executando algo assim:

$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d

Isso é tudo que uma tag leve é - uma referência que nunca se move. Entretanto, uma tag anotada é mais complexa. Se você criar uma tag anotada, o Git cria um objeto tag e então escreve uma referência que aponta para ele em vez de apontar diretamente para o commit. Você pode ver isso criando uma tag anotada (-a especifica que é uma tag anotada):

$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'

Aqui está o valor SHA-1 do objeto que foi criado:

$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2

Agora, execute o comando cat-file no valor SHA-1:

$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700

test tag

Note que o item object aponta ao SHA-1 do commit que você criou a tag. Note também que ele não precisa apontar para um commit; você pode adicionar qualquer objeto do Git. No código-fonte do Git, por exemplo, o mantenedor adicionou sua chave GPG pública como um objeto blob e então criou uma tag para ele. Você pode ver a chave pública executando isto em um clone do repositório do Git:

$ git cat-file blob junio-gpg-pub

O repositório do kernel Linux também tem um objeto tag que não aponta para um commit: a primeira tag criada aponta para a tree inicial da importação do código-fonte.

Remotes

O terceiro tipo de referência que você verá é o remote (remoto). Se você adicionar um remote e fizer um push para ele, o Git armazenará o valor que você fez o push para ele para cada branch no diretório refs/remotes. Por exemplo, você pode adicionar um remote chamado origin e fazer o push da sua master para ele:

$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
  a11bef0..ca82a6d  master -> master

Então, você pode ver onde a branch master no remote origin estava na última vez que você se comunicou com o servidor, olhando o arquivo refs/remotes/origin/master:

$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949

Remotes se diferenciam de branches (referências em refs/heads) principalmente pelo fato de normalmente serem somente-leitura. Você pode executar git checkout para um remote, mas o Git não irá apotar a HEAD para um, então você nunca irá atualizá-la com um commando commit. O Git gerencia elas como marcadores para a último estado conhecido de onde essas branches estavam nos servidores.