No Git, como escrever o hash de confirmação atual em um arquivo na mesma confirmação

131

Estou tentando fazer uma coisa chique aqui com ganchos Git, mas realmente não sei como fazê-lo (ou se é possível).

O que eu preciso fazer é: em cada confirmação, quero pegar seu hash e atualizar um arquivo no commit com esse hash.

Alguma ideia?

Felipe Kamakura
fonte
12
Basicamente, tenho um aplicativo Web e quero associar uma versão instalada desse aplicativo ao commit exato ao qual a versão está associada. Minha ideia inicial foi atualizar uma espécie de arquivo about.html com o hash de confirmação. Mas depois de estudar modelo de objetos do git, eu percebi que isso é meio impossível = /
Felipe Kamakura
29
Este é um problema muito prático. Eu também encontrei!
Li Dong
7
Quanto a mim, gostaria que meu programa escrevesse uma mensagem como esta nos logs: "myprog startup up, v.56c6bb2". Dessa forma, se alguém arquivar um bug e me enviar os arquivos de log, posso descobrir exatamente qual versão do meu programa estava em execução.
Edward Falk
5
@ Jeffefi, o caso de uso real é de fato muito comum e atinge iniciantes com muita facilidade. Ter a versão real de alguma forma "impressa" em arquivos de linha de base é uma necessidade básica, e está longe de ser óbvio por que seria uma idéia errada, por exemplo, porque essa é praticamente a sua única opção com hacks de controle de revisão manual. (Lembre-se de iniciantes.) Acrescente a isso que muitos projetos simplesmente não têm nenhum tipo de etapa de compilação / instalação / implantação que possa capturar e imprimir a versão em arquivos ativos. Independentemente, em vez de pré-confirmar, o gancho pós-checkout pode ajudar mesmo nesses casos.
Sz.
Isto é impossível! Se você puder fazer isso, quebrou o algoritmo de hash SHA-1 ... ericsink.com/vcbe/html/cryptographic_hashes.html
betontalpfa

Respostas:

82

Eu recomendaria fazer algo semelhante ao que você tem em mente: colocar o SHA1 em um arquivo não rastreado , gerado como parte do processo de compilação / instalação / implantação. É obviamente fácil de fazer ( git rev-parse HEAD > filenameou talvez git describe [--tags] > filename), e evita fazer qualquer coisa maluca, como acabar com um arquivo diferente do rastreamento do git.

Seu código pode fazer referência a esse arquivo quando precisar do número da versão ou um processo de criação pode incorporar as informações no produto final. O último é, na verdade, como o próprio git obtém seus números de versão - o processo de compilação pega o número da versão do repositório e depois o cria no executável.

Cascabel
fonte
3
Alguém poderia explicar mais detalhadamente como fazer isso? Ou pelo menos um empurrão na direção certa?
Joel Worsham
1
@Joel Como fazer o que? Eu mencionei como colocar o hash em um arquivo; o resto é presumivelmente algo sobre o seu processo de compilação? Talvez uma nova pergunta se você está tentando perguntar sobre essa parte.
Cascabel
1
No meu caso, adicionei uma regra ao meu Makefile que gera um arquivo "gitversion.h" em cada build. Veja stackoverflow.com/a/38087913/338479
Edward Falk
1
Você pode automatizar isso com um gancho "git-checkout". O problema é que os ganchos precisariam ser instalados manualmente.
Edward Falk
14

É impossível escrever o hash de confirmação atual: se você conseguir pré-calcular o hash de confirmação futuro - ele será alterado assim que você modificar qualquer arquivo.

No entanto, existem três opções:

  1. Use um script para incrementar 'id de confirmação' e incluí-lo em algum lugar. Feio
  2. .gitignore o arquivo no qual o hash será armazenado. Não é muito útil
  3. Em pre-commit, armazene o hash de confirmação anterior :) Você não modifica / insere confirmações em casos de 99,99%, portanto, isso funcionará. Na pior das hipóteses, você ainda pode identificar a revisão de origem.

Estou trabalhando em um script de gancho, publicarei aqui 'quando terminar', mas ainda assim - antes do lançamento do Duke Nukem Forever :))

Atualização : código para .git/hooks/pre-commit:

#!/usr/bin/env bash
set -e

#=== 'prev-commit' solution by o_O Tync
#commit_hash=$(git rev-parse --verify HEAD)
commit=$(git log -1 --pretty="%H%n%ci") # hash \n date
commit_hash=$(echo "$commit" | head -1)
commit_date=$(echo "$commit" | head -2 | tail -1) # 2010-12-28 05:16:23 +0300

branch_name=$(git symbolic-ref -q HEAD) # http://stackoverflow.com/questions/1593051/#1593487
branch_name=${branch_name##refs/heads/}
branch_name=${branch_name:-HEAD} # 'HEAD' indicates detached HEAD situation

# Write it
echo -e "prev_commit='$commit_hash'\ndate='$commit_date'\nbranch='$branch'\n" > gitcommit.py

Agora, a única coisa que precisamos é de uma ferramenta que converta prev_commit,branch par em um hash de confirmação real :)

Não sei se essa abordagem pode diferenciar commits de fusão. Irá verificar em breve

Kolypto
fonte
13

Alguém me indicou a seção "man gitattributes" no ident, que tem o seguinte:

ident

Quando o atributo ident é definido para um caminho, o git substitui $ Id $ no objeto blob por $ Id :, seguido pelo nome do objeto do blob hexadecimal de 40 caracteres, seguido por um sinal de dólar $ no check-out. Qualquer sequência de bytes que comece com $ Id: e termina com $ no arquivo da árvore de trabalho é substituída por $ Id $ no check-in.

Se você pensar bem, é isso que o CVS, o Subversion, etc, também fazem. Se você olhar para o repositório, verá que o arquivo no repositório sempre contém, por exemplo, $ Id $. Nunca contém a expansão disso. É apenas no check-out que o texto é expandido.

Barão Schwartz
fonte
8
identé o hash do próprio arquivo, não o hast do commit. De git-scm.com/book/en/… : "No entanto, esse resultado é de uso limitado. Se você usou a substituição de palavras-chave no CVS ou no Subversion, pode incluir um carimbo de data - o SHA não é tão útil, porque é bastante aleatório e você não pode dizer se um SHA é mais antigo ou mais recente que outro. " filterdá trabalho, mas pode obter as informações de confirmação em (e fora) de um arquivo.
Zach Young
11

Isso pode ser alcançado usando o filteratributo em gitattributes . Você precisaria fornecer um smudgecomando que insira o ID de confirmação e um cleancomando que o remova, para que o arquivo inserido não seja alterado apenas por causa do ID de confirmação.

Portanto, o ID de confirmação nunca é armazenado no blob do arquivo; acabou de ser expandido em sua cópia de trabalho. (Na verdade, inserir o ID de confirmação no blob se tornaria uma tarefa infinitamente recursiva. ☺) Qualquer pessoa que clone essa árvore precisará configurar os atributos para si mesma.

legoscia
fonte
7
Tarefa impossível , tarefa não recursiva. A confirmação do hash depende do hash da árvore, que depende do hash do arquivo, que depende do conteúdo do arquivo. Você tem que ter auto-consistência. A menos que você encontre um tipo de ponto fixo [generalizado] para o hash SHA-1.
Jakub Narębski
1
@ Jakub, existe algum tipo de truque no git que permitirá criar arquivos rastreados que não modificam o hash resultante? Alguma maneira de substituir seu hash, talvez. Isso vai ser uma solução :) #
2812 kolypto
@o_O Tync: Não é possível. Arquivo alterado significa hash alterado (de um arquivo) - isso ocorre por design e por definição de uma função de hash.
Jakub Narębski
2
Essa é uma solução muito boa, mas lembre-se de que isso envolve ganchos que precisam ser instalados manualmente sempre que você clona um repositório.
Edward Falk
7

Pense fora da caixa de confirmação!

coloque isso no arquivo hooks / pós-checkout

#!/bin/sh
git describe --all --long > config/git-commit-version.txt

A versão estará disponível em qualquer lugar que você a usar.

Keith Patrick
fonte
3

Eu não acho que você realmente queira fazer isso, porque quando um arquivo no commit é alterado, o hash do commit também é alterado.

midtiby
fonte
1

Deixe-me explorar por que esse é um problema desafiador usando os elementos internos do git. Você pode obter o sha1 do commit atual,

#!/bin/bash
commit=$(git cat-file commit HEAD) #
sha1=($((printf "commit %s\0" $(echo "$commit" | wc -c); echo "$commit") | sha1sum))
echo ${sha1[0]}

Basicamente, você executa uma soma de verificação sha1 na mensagem retornada por git cat-file commit HEAD. Duas coisas aparecem imediatamente como um problema quando você examina esta mensagem. Uma é a árvore sha1 e a segunda é o tempo de confirmação.

Agora, o tempo de confirmação é facilmente resolvido alterando a mensagem e adivinhando quanto tempo leva para fazer uma confirmação ou agendamento para confirmar em um horário específico. O verdadeiro problema é a árvore sha1, da qual você pode obter git ls-tree $(git write-tree) | git mktree. Essencialmente, você está fazendo uma soma de verificação sha1 na mensagem de ls-tree, que é uma lista de todos os arquivos e sua soma de verificação sha1.

Portanto, a soma de verificação do commit sha1 depende da soma de verificação da árvore sha1, que depende diretamente dos arquivos sha1 checksum, que completam o círculo e dependem do commit sha1. Assim, você tem um problema circular com técnicas disponíveis para mim.

Com somas de verificação menos seguras , foi mostrado que é possível gravar a soma de verificação do arquivo no próprio arquivo por força bruta; no entanto, não conheço nenhum trabalho que tenha realizado essa tarefa com sha1. Isso não é impossível, mas quase impossível com nosso entendimento atual (mas quem sabe talvez daqui a alguns anos será trivial). No entanto, ainda é mais difícil usar força bruta, pois é necessário gravar a soma de verificação (confirmação) de uma soma de verificação (árvore) de uma soma de verificação (blob) no arquivo.

Iniciante C
fonte
Existe uma maneira de confirmar os arquivos, fazer um check-out e colocar o hash de confirmação mais recente como um comentário no início de cada arquivo de código-fonte? Então construa e corra disso?
John Wooten