Dev (Back & Front)ARTIGO

Dica Git da Semana: Git Notes

Em uma palestra do ano passado para a Comunidade Java de Londres, apresentei Git e Gerrit (com base nos screencasts que fiz anteriormente). Uma das coisas que eu demonstrei foi o uso de git notes, então eu pensei em escrever isso.

Quando os arquivos são comitados em um repositório Git, eles são abordados por um hash do conteúdo. O mesmo é verdadeiro para trees e commits. Um dos benefícios dessa estrutura é que os objetos não podem ser modificados depois de terem sido comitados (pois isso mudaria esse hash).

No entanto, às vezes é desejável ser capaz de adicionar metadados a um commit depois de ele já ter sido comitado. Existem três formas de fazer isso:

  1. Corrigir a mensagem de commit para adicionar metadados adicionais, aceitando que isso mudará o branch.
  2. Criar um merge node com um commit mais detalhado, e fazer um push (para que o commit anterior seja retido e possa ser fast-foward).
  3. Adicionar metadados adicionais na forma de git notes.

Dessas três opções, apenas a última não mudará o branch atual.

Git Notes

Git Notes são, na verdade, um ‘branch’ separado do repositório (armazenado em .git/refs/notes). Eles não aparecem no comando git branch (que lista .git/refs/heads por padrão). No entanto, embora você possa conferi-lo e atualizá-lo manualmente, existe um comando que ajuda você a fazer isso; git notes.

(master) $ git log --oneline
056ca11 More Stuff Again
9defb31 MoreStuff
0c7ff4f Additional
19b6cdf Initial
(master) $ git notes show
(master) $ git notes add -m "ToDo: Fix stuff"
(master) $ git notes show
ToDo: Fix stuff
(master) $ git log
(master) $ git log
commit 056ca11c01b47e2bfe1e51178b65c80bbdeef7b0


More Stuff Again

Notes:
ToDo: Fix stuff

Quando você olha para a saída do git log, ela confere para ver se há um note associado e, se houver, imprime-o como se ele fosse um apêndice do commit. Além disso, as notas são mutáveis e podem ser atualizados ao longo do tempo:

(master) $ git notes add --force -m "ToDone: Fixed stuff"
Overwriting existing notes for object 056ca11c01b47e2bfe1e51178b65c80bbdeef7b0
(master) $ git notes show
ToDone: Fixed stuff

A vantagem das notas é que elas podem ser atualizadas sem alterar a mensagem de commit (e, portanto, o hash) do item ao qual se referem. Claro, isso pode ser usado tanto para o bem quanto para o mal, mas é importante ter em mente a mutabilidade caso precise depender dos conteúdos das notas.

Gits até o fim…

Na verdade, um título melhor poderia ter sido “objetos até o fim”, mas eu gostei mais desse.

Uma vez que o Git é um banco de dados de conteúdo endereçável, os próprios notes são objetos do git. Você pode até mesmo ver o histórico do branch usando git log e até verificá-lo. Mas como as notas são armazenadas?

(master) $ git log --oneline notes/commits
d6ac2b2 Notes added by 'git notes add'
5eb0ee5 Notes added by 'git notes add'
(master) $ git checkout notes/commits
Note: checking out 'notes/commits'.

You are in 'detached HEAD' state. You can look around, make experimental

HEAD is now at d6ac2b2... Notes added by 'git notes add'
((d6ac2b2...)) $ ls
056ca11c01b47e2bfe1e51178b65c80bbdeef7b0
((d6ac2b2...)) $ cat 056ca11c01b47e2bfe1e51178b65c80bbdeef7b0
ToDone: Fixed stuff

O branch contém uma lista de notas, com nomes de arquivos referenciados pela ID do commit (ou outro objeto) à qual correspondem. Nós podemos fazer uma mudança aqui e atualizar nossas notas:

((d6ac2b2...)) $ echo Note: Git notes are just objects >> 056ca11c01b47e2bfe1e51178b65c80bbdeef7b0
((d6ac2b2...)) $ git commit -a -m "Note added by me"
[detached HEAD 89e6afa] Note added by me
1 files changed, 1 insertions(+), 0 deletions(-)
((89e6afa...)) $ git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

89e6afa Note added by me

(master) $ git log HEAD^..HEAD
commit 056ca11c01b47e2bfe1e51178b65c80bbdeef7b0

More Stuff Again

Notes:
ToDone: Fixed stuff

Então, nós adicionamos um novo commit e depois voltamos para o master; mas como a mensagem de aviso nos disse, isso deixou o commit para trás. Nós precisamos realmente de atualizar a referência refs/notes/comits se quisermos ver os novos valores:

(master) $ git update-ref refs/notes/commits 89e6afa
(master) $ git log HEAD^..HEAD
commit 056ca11c01b47e2bfe1e51178b65c80bbdeef7b0

More Stuff Again

Notes:
ToDone: Fixed stuff
Note: Git notes are just objects

Aqui, o git update-ref está atribuindo ao conteúdo de refs/notes/commits o valor 89e6afa… (embora esteja resolvendo-o para um hash completo de 40 caracteres e verificando que ele existe primeiro).

Convenções

Apenas uma nota rápida sobre convenções; uma vez que o arquivo de git notes está essencialmente no seu branch, o conteúdo não foi mesclado com merges entre os branches. Caso queira fazer um merge com git notes, então seguir Key: Value em linhas separadas é o caminho para alcançar o nirvana do merge do git note. As opções de merge para git notes permitem anexar notas (ou seja, semelhante a cat noteV1 noteV2) ou classificar e unificar os dados (ou seja, cat noteV1 noteV2 | sort | uniq).

No entanto, as notas não têm que ser textuais, nem precisam ser algo que seja mesclável. Elas nem sequer precisam estar nos notes/commits ref, você pode criar notas com base em qualquer referência.

Na verdade, é assim que o Gerrit funciona. Ele armazena suas informações de revisão no repositório Git em notes/review. Normalmente, isso não aparece (o git log só mostra notas nos notes/commits refspace), mas se quiser você pode fazer com que ele faça isto:

(BARE:master) $ git show refs/notes/review
commit bb7cba258eaaf4851b20b66c7ef56775f0cb4367

Update notes for submitted changes

* Goodbye world

diff --git a/f7f38314247063271631cfddf560ea99214cd438 b/…
@@ -0,0 +1,7 @@
+Code-Review+2: Alex Blewitt
+Verified+1: Jenkins
+Submitted-by: Alex Blewitt
+Submitted-at: Thu, 20 Oct 2011 20:11:16 +0100
+Reviewed-on: http://localhost:9080/7
+Project: SkillsMatter
+Branch: refs/heads/master
(BARE:master) $ git log HEAD^..HEAD
commit f7f38314247063271631cfddf560ea99214cd438

Goodbye world

Change-Id: I692f8de08938f22da9d6e26005ba44c95a1479d7
(BARE:master) $ git log --show-notes=* HEAD^..HEAD
commit f7f38314247063271631cfddf560ea99214cd438

Goodbye world

Change-Id: I692f8de08938f22da9d6e26005ba44c95a1479d7

Notes (review):
Code-Review+2: Alex Blewitt
Verified+1: Jenkins
Submitted-by: Alex Blewitt
Submitted-at: Thu, 20 Oct 2011 20:11:16 +0100
Reviewed-on: http://localhost:9080/7
Project: SkillsMatter
Branch: refs/heads/master

Nesse caso, eu revisei o commit com dois meus, e um de Jenkins e ele é armazenado no repositório Git, juntamente com tudo mais. Normalmente, ele não é recebido pelo usuário quando se faz pulli ou clone, mas é um registro permanente no repositório (e será visível se você fizer, por exemplo, um clone git –mirror). No entanto, se você quiser fazer um fetch nas notas, então você pode fazer isto:

[remote "origin"]
fetch = +refs/notes/*:refs/notes/*
fetch = +refs/heads/*:refs/remotes/origin/*
url = ssh://localhost:29418/SkillsMatter.git
push = refs/heads/master:refs/for/master

O fetch refspec em negrito me permite fazer um pull de quaisquer/todos os reviews a partir do repositório e disponibilizá-los no meu clone local.

Exercício para o leitor…

Uma vez que as notas Git podem conter qualquer blob e não são clonadas por padrão (a menos que você revise especificamente), você pode criar uma distribuição e verificá-la em um repositório. Em vez de armazená-lo em refs/notes/commit, armazene em refs/notes/dist e tenha o binário gerado a partir do seu sistema de compilação; exporte como uma Git Note apontando para a tag. Dessa forma, se você quiser conferir o pacote pré-construído para uma determinada tag, você pode usar refs/notes/dist para apontar para a tag que deseja e extrair o binário completo.

Claro, você realmente não precisa usar git notes para armazenar qualquer blob no repositório em todos os casos; não há nenhuma razão para que você não possa ter uma tree refs/dists, com um arquivo por tag.

Git notes demonstra o fato de que o Git não é apenas um sistema de controle de código, como Hg ou Bzr. Em vez disso, ele é um sistema de arquivos de conteúdo endereçável, que por acaso é capaz de representar trees e arquivos (blobs) de uma maneira fácil. Como resultado, o Git será sempre capaz de ser estendido com funcionalidade como Gerrit e git notes, porque não se limita ao que ele pode armazenar em um repositório – além disso, a clonagem do repositório pode ainda ser eficiente, uma vez que os dados que você puxa a partir de um clone são apenas os objetos acessíveis a partir de um commit específico. Como resultado, review notes (e/ou distribuições binárias) nunca precisam fazer parte de um repositório clonado, mesmo se forem persistentes e disponibilizadas no mesmo back-end do Git.

?

Texto original disponível em http://alblue.bandlem.com/2011/11/git-tip-of-week-git-notes.html

é guru em Eclipse e fanático pelo Mac.

Ver perfil