Ottieni l'hash git corrente in uno script Python


164

Vorrei includere l'attuale hash git nell'output di uno script Python (come numero di versione del codice che ha generato quell'output).

Come posso accedere all'attuale hash git nel mio script Python?


7
Inizia con git rev-parse HEADdalla riga di comando. La sintassi dell'output dovrebbe essere ovvia.
Mel Nicholson,

Risposte:


96

Il git describecomando è un buon modo per creare un "numero di versione" presentabile dall'uomo del codice. Dagli esempi nella documentazione:

Con qualcosa come git.git albero attuale, ottengo:

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

cioè l'attuale capo del mio ramo "genitore" si basa sulla v1.0.4, ma dato che ha alcuni commit in più, descriva ha aggiunto il numero di commit aggiuntivi ("14") e un nome oggetto abbreviato per il commit stesso ("2414721") alla fine.

Da Python puoi fare qualcosa del tipo:

import subprocess
label = subprocess.check_output(["git", "describe"]).strip()

3
Ciò ha lo svantaggio che il codice di stampa della versione verrà interrotto se il codice viene mai eseguito senza il repository git presente. Ad esempio, in produzione. :)
JosefAssad,

5
@JosefAssad: se è necessario un identificatore di versione in produzione, la procedura di distribuzione deve eseguire il codice sopra riportato e il risultato deve essere "inserito" nel codice distribuito in produzione.
Greg Hewgill,

14
Nota che git descriverà fallirà se non ci sono tag presenti:fatal: No names found, cannot describe anything.
kynan,

40
git describe --alwaystornerà all'ultimo commit se non vengono trovati tag
Leonardo

5
@CharlieParker: git describenormalmente richiede almeno un tag. Se non hai tag, usa l' --alwaysopzione. Vedi la documentazione descrittiva git per maggiori informazioni
Greg Hewgill,

189

Non c'è bisogno di hackerare per ottenere i dati dal gitcomando da soli. GitPython è un modo molto carino per fare questo e molte altre gitcose. Ha anche il supporto "best effort" per Windows.

Dopo pip install gitpythonche puoi farlo

import git
repo = git.Repo(search_parent_directories=True)
sha = repo.head.object.hexsha

9
@crishoj Non so come si può chiamare portatile quando questo accade: ImportError: No module named gitpython. Non puoi fare affidamento sul fatto che l'utente finale abbia gitpythoninstallato e che richieda l'installazione prima che il tuo codice funzioni, non lo rende portatile. A meno che non includerai protocolli di installazione automatica, a quel punto non è più una soluzione pulita.
user5359531,

39
@ user5359531 Mi permetto di dissentire. GitPython fornisce un'implementazione Python pura, sottraendo i dettagli specifici della piattaforma ed è installabile utilizzando strumenti di pacchetto standard ( pip/ requirements.txt) su tutte le piattaforme. Cosa non è "pulito"?
crishoj,

22
Questo è il modo normale di fare le cose in Python. Se il PO necessitasse di tali requisiti, lo avrebbero detto. Non siamo lettori di mente, non possiamo prevedere ogni eventualità in ogni domanda. In questo modo sta la follia.
OldTinfoil

14
@ user5359531, non sono chiaro il motivo per cui si import numpy as nppossa presumere in tutto lo stackoverflow ma l'installazione di gitpython va oltre "clean" e "portable". Penso che questa sia di gran lunga la soluzione migliore, perché non reinventa la ruota, nasconde la brutta implementazione e non va in giro hackerando la risposta di Git dal sottoprocesso.
Jblasco,

7
@ user5359531 Mentre sono d'accordo in generale che non dovresti lanciare una nuova libreria brillante ad ogni piccolo problema, la tua definizione di "portabilità" sembra trascurare gli scenari moderni in cui gli sviluppatori hanno il pieno controllo su tutti gli ambienti in cui sono in esecuzione le applicazioni. Nel 2018 abbiamo Contenitori docker, ambienti virtuali e immagini di macchine (ad es. AMI) con pipo possibilità di installazione semplice pip. In questi scenari moderni, una pipsoluzione è portatile come una soluzione "libreria standard".
Ryan,

106

Questo post contiene il comando, la risposta di Greg contiene il comando sottoprocesso.

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'])

32
Aggiungi una striscia () al risultato per ottenere questo senza interruzioni di riga :)
grasshopper

Come eseguiresti questo per un repository git in un determinato percorso?
sabato

2
@pkamb Usa os.chdir per eseguire il cd sul percorso del repository git con cui sei interessato a lavorare
Zac Crites,

Non darebbe la risposta sbagliata se la revisione attualmente estratta non è la diramazione?
massimo

7
Aggiungi a .decode('ascii').strip()per decodificare la stringa binaria (e rimuovere l'interruzione di riga).
pfm,

13

numpyha una bella routine multipiattaforma nel suo 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

2
Mi piace questo, abbastanza pulito e senza librerie esterne
13aal

La risposta di Yuji fornisce una soluzione simile in una sola riga di codice che produce lo stesso risultato. Puoi spiegare perché ha numpyritenuto necessario "costruire un ambiente minimo"? (supponendo che avessero buone ragioni per farlo)
MD004

L'ho appena notato nel loro repository e ho deciso di aggiungerlo a questa domanda per le persone interessate. Non sviluppo in Windows, quindi non l'ho testato, ma avevo ipotizzato che l'impostazione del envdict fosse necessaria per la funzionalità multipiattaforma. La risposta di Yuji no, ma forse funziona sia su UNIX che su Windows.
ryanjdillon,

Guardando la colpa di git, lo hanno fatto come una correzione di bug per SVN 11 anni fa: github.com/numpy/numpy/commit/… È possibile che la correzione di bug non sia più necessaria per git.
gparent

@ MD004 @ryanjdillon Hanno impostato la locale in modo che funzioni, .decode('ascii')altrimenti la codifica è sconosciuta.
z0r

7

Se il sottoprocesso non è portatile e non vuoi installare un pacchetto per fare qualcosa di così semplice, puoi anche farlo.

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

L'ho provato solo sui miei repository, ma sembra funzionare abbastanza coerentemente.


A volte / refs / non viene trovato, ma l'attuale ID commit si trova in "pacchetti-refs".
am9417,

7

Ecco una versione più completa della risposta di Greg :

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

Oppure, se lo script viene chiamato dall'esterno del repository:

import subprocess, os
os.chdir(os.path.dirname(__file__))
print(subprocess.check_output(["git", "describe", "--always"]).strip().decode())

1
Invece di utilizzare os.chdir, l' cwd=arg può essere utilizzato check_outputper modificare temporaneamente la directory di lavoro prima dell'esecuzione.
Marc

0

Se per qualche motivo non hai git disponibile, ma hai il repository git (viene trovata la cartella .git), puoi recuperare l'hash di commit da .git / fetch / heads / [branch]

Ad esempio, ho usato un frammento di Python veloce e sporco seguente nella radice del repository per ottenere l'ID commit:

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]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.