Obtenez le hachage git actuel dans un script Python

165

Je voudrais inclure le hachage git actuel dans la sortie d'un script Python (en tant que numéro de version du code qui a généré cette sortie).

Comment puis-je accéder au hachage git actuel dans mon script Python?

Victor
la source
7
Commencez par à git rev-parse HEADpartir de la ligne de commande. La syntaxe de sortie doit être évidente.
Mel Nicholson

Réponses:

97

La git describecommande est un bon moyen de créer un "numéro de version" présentable par l'homme du code. D'après les exemples de la documentation:

Avec quelque chose comme l'arbre actuel de git.git, j'obtiens:

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

c'est-à-dire que la tête actuelle de ma branche "parent" est basée sur la v1.0.4, mais comme elle a quelques commits en plus de cela, describe a ajouté le nombre de commits supplémentaires ("14") et un nom d'objet abrégé pour le commit lui-même ("2414721") à la fin.

Depuis Python, vous pouvez faire quelque chose comme ce qui suit:

import subprocess
label = subprocess.check_output(["git", "describe"]).strip()
Greg Hewgill
la source
3
Cela a l'inconvénient que le code d'impression de la version sera cassé si le code est exécuté sans le dépôt git présent. Par exemple, en production. :)
JosefAssad
5
@JosefAssad: Si vous avez besoin d'un identifiant de version en production, votre procédure de déploiement doit exécuter le code ci-dessus et le résultat doit être "intégré" au code déployé en production.
Greg Hewgill
15
Notez que git describe échouera s'il n'y a pas de balises présentes:fatal: No names found, cannot describe anything.
kynan
41
git describe --alwaysreviendra au dernier commit si aucune balise n'est trouvée
Leonardo
5
@CharlieParker: git describenécessite normalement au moins une balise. Si vous n'avez pas de balises, utilisez l' --alwaysoption. Consultez la documentation de git describe pour plus d'informations.
Greg Hewgill
190

Pas besoin de pirater pour obtenir des données de la gitcommande vous-même. GitPython est un très bon moyen de faire cela et bien d'autresgit choses. Il a même un support "best effort" pour Windows.

Après avoir pip install gitpythonpu faire

import git
repo = git.Repo(search_parent_directories=True)
sha = repo.head.object.hexsha
kqw
la source
9
@crishoj ne savez pas comment vous pouvez l' appeler portable lorsque cela se produit: ImportError: No module named gitpython. Vous ne pouvez pas compter sur l'utilisateur final qui a gitpythoninstallé, et exiger d'eux qu'il l'installe avant que votre code fonctionne le rend non portable. À moins que vous n'incluiez des protocoles d'installation automatique, ce n'est plus une solution propre.
user5359531
39
@ user5359531 Je ne suis pas d'accord. GitPython fournit une implémentation pure de Python, en supprimant les détails spécifiques à la plate-forme, et il peut être installé à l'aide des outils de package standard ( pip/ requirements.txt) sur toutes les plates-formes. Qu'est-ce qui n'est pas «propre»?
crishoj
22
C'est la manière normale de faire les choses en Python. Si le PO avait besoin de ces exigences, il l'aurait dit. Nous ne sommes pas des lecteurs d'esprit, nous ne pouvons pas prédire toutes les éventualités dans chaque question. C'est comme ça que réside la folie.
OldTinfoil
14
@ user5359531, je ne comprends pas pourquoi import numpy as npon peut supposer tout au long de stackoverflow, mais l'installation de gitpython va au-delà de «propre» et «portable». Je pense que c'est de loin la meilleure solution, car elle ne réinvente pas la roue, cache la vilaine implémentation et ne pirate pas la réponse de git du sous-processus.
Jblasco
7
@ user5359531 Bien que je convienne en général que vous ne devriez pas lancer une nouvelle bibliothèque brillante à chaque petit problème, votre définition de la «portabilité» semble négliger les scénarios modernes où les développeurs ont un contrôle total sur tous les environnements dans lesquels les applications fonctionnent. En 2018, nous avons Conteneurs Docker, environnements virtuels et images de machine (par exemple les AMI) avec pipou possibilité d'installation facile pip. Dans ces scénarios modernes, une pipsolution est tout aussi portable qu'une solution de «bibliothèque standard».
Ryan
106

Ce message contient la commande, la réponse de Greg contient la commande de sous-processus.

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
la source
32
Ajoutez une bande () au résultat pour obtenir cela sans sauts de ligne :)
grasshopper
Comment exécuteriez-vous cela pour un dépôt git sur un chemin particulier?
pkamb
2
@pkamb Utilisez os.chdir pour accéder au chemin du repo git avec
lequel
Cela ne donnerait-il pas la mauvaise réponse si la révision actuellement extraite n'est pas la tête de branche?
max
7
Ajoutez un .decode('ascii').strip()pour décoder la chaîne binaire (et supprimez le saut de ligne).
pfm
13

numpya une belle routine multi-plateforme dans son 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
la source
2
J'aime ça, assez propre et pas de bibliothèques externes
13a
La réponse de Yuji fournit une solution similaire en une seule ligne de code qui produit le même résultat. Pouvez-vous expliquer pourquoi a numpyjugé nécessaire de «construire un environnement minimal»? (en supposant qu'ils avaient de bonnes raisons de le faire)
MD004
Je viens de le remarquer dans leur repo et j'ai décidé de l'ajouter à cette question pour les personnes intéressées. Je ne développe pas sous Windows, donc je n'ai pas testé cela, mais j'avais supposé que la configuration du envdict était nécessaire pour la fonctionnalité multiplateforme. La réponse de Yuji ne le fait pas, mais peut-être que cela fonctionne à la fois sous UNIX et Windows.
ryanjdillon
En regardant le blâme de git, ils l'ont fait comme une correction de bogue pour SVN il y a 11 ans: github.com/numpy/numpy/commit/ ... Il est possible que la correction de bogue ne soit plus nécessaire pour git.
gparent le
@ MD004 @ryanjdillon Ils définissent les paramètres régionaux pour que cela .decode('ascii')fonctionne - sinon le codage est inconnu.
z0r
7

Si le sous-processus n'est pas portable et que vous ne voulez pas installer un package pour faire quelque chose d'aussi simple, vous pouvez également le faire.

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

Je n'ai testé cela que sur mes dépôts, mais cela semble fonctionner de manière assez cohérente.

Kagronick
la source
Parfois, le / refs / n'est pas trouvé, mais l'ID de validation actuel se trouve dans "packing-refs".
am9417 le
7

Voici une version plus complète de la réponse de Greg :

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

Ou, si le script est appelé depuis l'extérieur du dépôt:

import subprocess, os
os.chdir(os.path.dirname(__file__))
print(subprocess.check_output(["git", "describe", "--always"]).strip().decode())
AndyP
la source
1
Au lieu d'utiliser os.chdir, l' cwd=argument peut être utilisé check_outputpour modifier temporairement le répertoire de travail avant de s'exécuter.
Marc
0

Si vous n'avez pas git disponible pour une raison quelconque, mais que vous avez le repo git (le dossier .git est trouvé), vous pouvez récupérer le hachage de validation à partir de .git / fetch / heads / [branch]

Par exemple, j'ai utilisé un extrait de code Python rapide et sale suivant à la racine du référentiel pour obtenir l'ID de validation:

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
la source