Obtenga el hash git actual en un script de Python

164

Me gustaría incluir el hash git actual en la salida de un script Python (como el número de versión del código que generó esa salida).

¿Cómo puedo acceder al hash git actual en mi script Python?

Víctor
fuente
77
Comience con git rev-parse HEADdesde la línea de comando. La sintaxis de salida debería ser obvia.
Mel Nicholson

Respuestas:

96

El git describecomando es una buena forma de crear un "número de versión" del código presentable por humanos. De los ejemplos en la documentación:

Con algo como el árbol actual de git.git, obtengo:

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

es decir, el encabezado actual de mi rama "principal" se basa en v1.0.4, pero dado que tiene algunas confirmaciones además de eso, describe ha agregado el número de confirmaciones adicionales ("14") y un nombre de objeto abreviado para la confirmación en sí ("2414721") al final.

Desde Python, puede hacer algo como lo siguiente:

import subprocess
label = subprocess.check_output(["git", "describe"]).strip()
Greg Hewgill
fuente
3
Esto tiene el inconveniente de que el código de impresión de la versión se romperá si el código se ejecuta sin el repositorio git presente. Por ejemplo, en producción. :)
JosefAssad
55
@JosefAssad: si necesita un identificador de versión en producción, entonces su procedimiento de implementación debe ejecutar el código anterior y el resultado debe "integrarse" en el código implementado en producción.
Greg Hewgill
14
Tenga en cuenta que git describe fallará si no hay etiquetas presentes:fatal: No names found, cannot describe anything.
kynan
40
git describe --alwaysrecurrirá a la última confirmación si no se encuentran etiquetas
Leonardo
55
@CharlieParker: git describenormalmente requiere al menos una etiqueta. Si no tiene ninguna etiqueta, use la --alwaysopción. Consulte la documentación de git describe para obtener más información.
Greg Hewgill
189

No es necesario hackear para obtener datos del gitcomando usted mismo. GitPython es una muy buena manera de hacer esto y muchas otras gitcosas. Incluso tiene soporte de "mejor esfuerzo" para Windows.

Después de pip install gitpythonque puedas hacer

import git
repo = git.Repo(search_parent_directories=True)
sha = repo.head.object.hexsha
kqw
fuente
9
@crishoj No sabe cómo se le puede llamar portátil cuando esto sucede: ImportError: No module named gitpython. No puede confiar en que el usuario final haya gitpythoninstalado, y exigirles que lo instalen antes de que su código funcione no lo hace portátil. A menos que vaya a incluir protocolos de instalación automática, en ese momento ya no es una solución limpia.
user5359531
39
@ user5359531 Le ruego que difiera. GitPython proporciona una implementación pura de Python, abstrayendo detalles específicos de la plataforma, y ​​es instalable usando herramientas de paquete estándar ( pip/ requirements.txt) en todas las plataformas. ¿Qué no es "limpio"?
crishoj
22
Esta es la forma normal de hacer cosas en Python. Si el OP necesita esos requisitos, entonces lo habrían dicho. No somos lectores de la mente, no podemos predecir cada eventualidad en cada pregunta. De esa manera se encuentra la locura.
OldTinfoil
14
@ user5359531, no estoy claro por qué import numpy as npse puede suponer en todo el stackoverflow, pero la instalación de gitpython está más allá de 'clean' y 'portable'. Creo que esta es, con mucho, la mejor solución, porque no reinventa la rueda, oculta la implementación fea y no anda pirateando la respuesta de git del subproceso.
Jblasco
77
@ user5359531 Si bien estoy de acuerdo en general en que no debe arrojar una nueva biblioteca brillante a cada pequeño problema, su definición de "portabilidad" parece descuidar los escenarios modernos en los que los desarrolladores tienen control total sobre todos los entornos en los que se ejecutan dichas aplicaciones. En 2018 tenemos Contenedores Docker, entornos virtuales e imágenes de máquinas (por ejemplo, AMI) con pipla capacidad de instalarse fácilmente pip. En estos escenarios modernos, una pipsolución es tan portátil como una solución de "biblioteca estándar".
Ryan
106

Esta publicación contiene el comando, la respuesta de Greg contiene el comando de subproceso.

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
fuente
32
Agregue una tira () al resultado para obtener esto sin saltos de línea :)
grasshopper
¿Cómo ejecutarías esto para un repositorio de git en un camino particular?
pkamb
2
@pkamb Use os.chdir para cd a la ruta del repositorio de git con el que está interesado en trabajar
Criterios de Zac
¿No daría eso una respuesta incorrecta si la revisión actualmente revisada no es el encabezado de la sucursal?
max
77
Agregue a .decode('ascii').strip()para decodificar la cadena binaria (y elimine el salto de línea).
pfm
13

numpytiene una bonita rutina multiplataforma en su 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
fuente
2
Me gusta esto, bastante limpio y sin bibliotecas externas
13aal
La respuesta de Yuji proporciona una solución similar en una sola línea de código que produce el mismo resultado. ¿Puede explicar por qué numpyconsideró necesario "construir un entorno mínimo"? (suponiendo que tenían una buena razón para hacerlo)
MD004
Acabo de notar esto en su repositorio y decidí agregarlo a esta pregunta para las personas interesadas. No desarrollo en Windows, así que no he probado esto, pero asumí que configurar el envdict era necesario para la funcionalidad multiplataforma. La respuesta de Yuji no, pero quizás eso funcione tanto en UNIX como en Windows.
ryanjdillon
Mirando la culpa de git, hicieron esto como una corrección de errores para SVN hace 11 años: github.com/numpy/numpy/commit/… Es posible que la corrección de errores ya no sea necesaria para git.
gparent
@ MD004 @ryanjdillon Establecen la configuración regional para que .decode('ascii')funcione; de ​​lo contrario, la codificación es desconocida.
z0r
7

Si el subproceso no es portátil y no desea instalar un paquete para hacer algo así de simple, también puede hacerlo.

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()

Solo he probado esto en mis repositorios, pero parece funcionar de manera bastante consistente.

kagronick
fuente
A veces no se encuentra / refs /, pero el id de confirmación actual se encuentra en "package-refs".
am9417
7

Aquí hay una versión más completa de la respuesta de Greg :

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

O, si el script se llama desde fuera del repositorio:

import subprocess, os
os.chdir(os.path.dirname(__file__))
print(subprocess.check_output(["git", "describe", "--always"]).strip().decode())
AndyP
fuente
1
En lugar de usar os.chdir, el cwd=argumento puede usarse check_outputpara realizar cambios temporales en el directorio de trabajo antes de ejecutarlo.
Marc
0

Si no tiene git disponible por alguna razón, pero tiene el repositorio git (se encuentra la carpeta .git), puede obtener el hash de confirmación de .git / fetch / heads / [branch]

Por ejemplo, he usado un siguiente fragmento de Python rápido y sucio en la raíz del repositorio para obtener la identificación de confirmación:

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
fuente