Obter o hash git atual em um script Python

164

Gostaria de incluir o hash git atual na saída de um script Python (como o número da versão do código que gerou essa saída).

Como posso acessar o hash git atual no meu script Python?

Vencedor
fonte
7
Comece com git rev-parse HEADna linha de comando. A sintaxe de saída deve ser óbvia.
Mel-Nicholson

Respostas:

96

O git describecomando é uma boa maneira de criar um "número de versão" apresentável por humanos do código. Dos exemplos na documentação:

Com algo como a árvore atual do git.git, recebo:

[torvalds@g5 git]$ git describe parent
v1.0.4-14-g2414721

ou seja, o cabeçalho atual do meu ramo "pai" é baseado na v1.0.4, mas como ele tem alguns commits além disso, o description adicionou o número de commits adicionais ("14") e um nome de objeto abreviado para o commit próprio ("2414721") no final.

No Python, você pode fazer algo como o seguinte:

import subprocess
label = subprocess.check_output(["git", "describe"]).strip()
Greg Hewgill
fonte
3
Isso tem a desvantagem de que o código de impressão da versão será quebrado se o código for executado sem o repositório git presente. Por exemplo, em produção. :)
JosefAssad
5
@ JosefAssad: Se você precisar de um identificador de versão em produção, o procedimento de implantação deve executar o código acima e o resultado deve ser "incorporado" ao código implantado na produção.
22813 Greg Hewgill
14
Note-se que git descrever irá falhar se não houver Tag atuais:fatal: No names found, cannot describe anything.
Kynan
40
git describe --alwaysfará o fallback para o último commit se nenhuma tag for encontrada
Leonardo
5
@CharlieParker: git describenormalmente requer pelo menos uma tag. Se você não possui tags, use a --alwaysopção Veja a documentação de descrição do git para mais informações.
Greg Hewgill 01/07/19
189

Não há necessidade de obter dados do gitcomando por conta própria. O GitPython é uma maneira muito boa de fazer isso e muitas outras gitcoisas. Ele ainda oferece suporte ao "melhor esforço" para o Windows.

Depois que pip install gitpythonvocê pode fazer

import git
repo = git.Repo(search_parent_directories=True)
sha = repo.head.object.hexsha
kqw
fonte
9
@crishoj Não tenho certeza como você pode chamá-lo portátil quando isso acontece: ImportError: No module named gitpython. Você não pode contar com a instalação do usuário final gitpython, e exigir que ele o instale antes que seu código funcione não o torna portátil. A menos que você inclua protocolos de instalação automática, nesse ponto, não será mais uma solução limpa.
User5359531
39
@ user5359531 Eu imploro para diferir. O GitPython fornece uma implementação pura do Python, abstraindo os detalhes específicos da plataforma, e é instalável usando ferramentas de pacote padrão ( pip/ requirements.txt) em todas as plataformas. O que não é "limpo"?
2741717
22
Esta é a maneira normal de fazer as coisas em Python. Se o OP precisar desses requisitos, eles o teriam dito. Não somos leitores de mentes, não podemos prever todas as eventualidades de cada pergunta. Dessa maneira está a loucura.
precisa saber é o seguinte
14
@ user5359531, não sei por que import numpy as nppode ser assumido em todo o fluxo de pilha, mas a instalação do gitpython está além de 'clean' e 'portable'. Eu acho que essa é de longe a melhor solução, porque não reinventa a roda, oculta a implementação feia e não sai por aí cortando a resposta do git do subprocesso.
JBLasco #
7
@ user5359531 Embora eu concorde em geral que você não deve lançar uma nova biblioteca brilhante em todos os pequenos problemas, sua definição de "portabilidade" parece negligenciar os cenários modernos em que os desenvolvedores têm controle total sobre todos os ambientes em que os aplicativos são executados. Em 2018, temos Contêineres do Docker, ambientes virtuais e imagens de máquinas (por exemplo, AMIs) com pipou a capacidade de instalar facilmente pip. Nesses cenários modernos, uma pipsolução é tão portátil quanto uma solução de "biblioteca padrão".
Ryan
106

Esta postagem contém o comando, a resposta de Greg contém o comando subprocesso.

import subprocess

def get_git_revision_hash():
    return subprocess.check_output(['git', 'rev-parse', 'HEAD'])

def get_git_revision_short_hash():
    return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
Yuji 'Tomita' Tomita
fonte
32
Adicionar uma faixa () ao resultado a obter isso sem quebras de linha :)
gafanhoto
Como você executaria isso para um repositório git em um caminho específico?
pkamb 26/02
2
@pkamb Use os.chdir cd para o caminho do git repo você está interessado em trabalhar com
Zac Crites
Isso não daria a resposta errada se a revisão com check-out atualmente não for a cabeça do ramo?
máx
7
Adicione a .decode('ascii').strip()para decodificar a sequência binária (e remova a quebra de linha).
pfm
13

numpypossui uma rotina multiplataforma de boa aparência em setup.py:

import os
import subprocess

# Return the git revision as a string
def git_version():
    def _minimal_ext_cmd(cmd):
        # construct minimal environment
        env = {}
        for k in ['SYSTEMROOT', 'PATH']:
            v = os.environ.get(k)
            if v is not None:
                env[k] = v
        # LANGUAGE is used on win32
        env['LANGUAGE'] = 'C'
        env['LANG'] = 'C'
        env['LC_ALL'] = 'C'
        out = subprocess.Popen(cmd, stdout = subprocess.PIPE, env=env).communicate()[0]
        return out

    try:
        out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'])
        GIT_REVISION = out.strip().decode('ascii')
    except OSError:
        GIT_REVISION = "Unknown"

    return GIT_REVISION
ryanjdillon
fonte
2
Eu gosto deste, muito limpos e não externos bibliotecas
13aal
A resposta de Yuji fornece uma solução semelhante em apenas uma linha de código que produz o mesmo resultado. Você pode explicar por que numpyconsiderou necessário "construir um ambiente mínimo"? (supondo que eles tinham um bom motivo para) #
MD004
Acabei de notar isso no repo e decidi adicioná-lo a essa pergunta para as pessoas interessadas. Como não desenvolvo no Windows, não testei isso, mas presumi que a configuração do envditado era necessária para a funcionalidade de plataforma cruzada. A resposta de Yuji não, mas talvez isso funcione no UNIX e no Windows.
Ryanjdillon
Observando a culpa do git, eles fizeram isso como uma correção de bug do SVN há 11 anos: github.com/numpy/numpy/commit/… É possível que a correção do bug não seja mais necessária para o git.
gparent 22/01
@ MD004 @ryanjdillon Eles definem o código do idioma para que .decode('ascii')funcione - caso contrário, a codificação é desconhecida.
z0r 26/04
7

Se o subprocesso não for portátil e você não quiser instalar um pacote para fazer algo tão simples, você também poderá fazer isso.

import pathlib

def get_git_revision(base_path):
    git_dir = pathlib.Path(base_path) / '.git'
    with (git_dir / 'HEAD').open('r') as head:
        ref = head.readline().split(' ')[-1].strip()

    with (git_dir / ref).open('r') as git_hash:
        return git_hash.readline().strip()

Eu só testei isso nos meus repositórios, mas parece funcionar de maneira consistente.

kagronick
fonte
Às vezes, o / refs / não é encontrado, mas o atual ID de confirmação é encontrado em "compactado-refs".
am9417 3/02
7

Aqui está uma versão mais completa da resposta de Greg :

import subprocess
print(subprocess.check_output(["git", "describe", "--always"]).strip().decode())

Ou, se o script estiver sendo chamado de fora do repositório:

import subprocess, os
os.chdir(os.path.dirname(__file__))
print(subprocess.check_output(["git", "describe", "--always"]).strip().decode())
AndyP
fonte
1
Em vez de usar os.chdir, o cwd=arg pode ser usado check_outputpara alterar temporariamente o diretório de trabalho antes de executar.
Marc Marc
0

Se você não tiver o git disponível por algum motivo, mas tiver o repositório git (a pasta .git foi encontrada), poderá buscar o hash de confirmação em .git / fetch / heads / [branch]

Por exemplo, usei o seguinte snippet rápido e sujo do Python executado na raiz do repositório para obter o ID de confirmação:

git_head = '.git\\HEAD'

# Open .git\HEAD file:
with open(git_head, 'r') as git_head_file:
    # Contains e.g. ref: ref/heads/master if on "master"
    git_head_data = str(git_head_file.read())

# Open the correct file in .git\ref\heads\[branch]
git_head_ref = '.git\\%s' % git_head_data.split(' ')[1].replace('/', '\\').strip()

# Get the commit hash ([:7] used to get "--short")
with open(git_head_ref, 'r') as git_head_ref_file:
    commit_id = git_head_ref_file.read().strip()[:7]
am9417
fonte