Dapatkan hasit git saat ini dalam skrip Python

164

Saya ingin memasukkan hash git saat ini dalam output skrip Python (sebagai nomor versi dari kode yang menghasilkan keluaran itu).

Bagaimana saya bisa mengakses hash git saat ini di skrip Python saya?

Pemenang
sumber
7
Mulai dengan git rev-parse HEADdari baris perintah. Sintaks output harus jelas.
Mel Nicholson

Jawaban:

96

The git describeperintah adalah cara yang baik untuk menciptakan "nomor versi" manusia-rapi kode. Dari contoh-contoh dalam dokumentasi:

Dengan sesuatu seperti pohon git.git saat ini, saya mendapatkan:

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

yaitu kepala saat ini dari cabang "induk" saya didasarkan pada v1.0.4, tetapi karena memiliki beberapa commit di atas itu, jelaskan telah menambahkan jumlah komit tambahan ("14") dan nama objek singkatan untuk kom sendiri ("2414721") di akhir.

Dari dalam Python, Anda dapat melakukan sesuatu seperti berikut:

import subprocess
label = subprocess.check_output(["git", "describe"]).strip()
Greg Hewgill
sumber
3
Ini memiliki kelemahan bahwa kode pencetakan versi akan rusak jika kode pernah dijalankan tanpa kehadiran git repo. Misalnya dalam produksi. :)
JosefAssad
5
@ JosefAssad: Jika Anda memerlukan pengenal versi dalam produksi, maka prosedur penyebaran Anda harus menjalankan kode di atas dan hasilnya harus "dimasukkan" ke kode yang digunakan untuk produksi.
Greg Hewgill
14
Perhatikan bahwa git menjelaskan akan gagal jika tidak ada tag yang ada:fatal: No names found, cannot describe anything.
kynan
40
git describe --alwaysakan mundur ke komit terakhir jika tidak ada tag yang ditemukan
Leonardo
5
@CharlieParker: git describebiasanya membutuhkan setidaknya satu tag. Jika Anda tidak memiliki tag, gunakan --alwaysopsi. Lihat git menjelaskan dokumentasi untuk informasi lebih lanjut.
Greg Hewgill
189

Tidak perlu meretas mendapatkan data dari gitperintah sendiri. GitPython adalah cara yang sangat bagus untuk melakukan ini dan banyak githal lainnya . Bahkan memiliki dukungan "upaya terbaik" untuk Windows.

Setelah pip install gitpythonkamu bisa melakukannya

import git
repo = git.Repo(search_parent_directories=True)
sha = repo.head.object.hexsha
kqw
sumber
9
@crishoj Tidak yakin bagaimana Anda dapat menyebutnya portable ketika hal ini terjadi: ImportError: No module named gitpython. Anda tidak dapat mengandalkan pengguna akhir yang telah gitpythondiinstal, dan mengharuskan mereka untuk menginstalnya sebelum kode Anda berfungsi membuatnya tidak portabel. Kecuali jika Anda akan menyertakan protokol instalasi otomatis, pada saat itu tidak lagi merupakan solusi bersih.
user5359531
39
@ user5359531 saya mohon berbeda. GitPython menyediakan implementasi Python murni, mengabstraksi detail spesifik platform, dan dapat diinstal menggunakan alat paket standar ( pip/ requirements.txt) pada semua platform. Apa yang tidak "bersih"?
crishoj
22
Ini adalah cara normal untuk melakukan hal-hal dengan Python. Jika OP membutuhkan persyaratan itu, maka mereka akan mengatakannya. Kami bukan pembaca pikiran, kami tidak dapat memprediksi setiap kemungkinan dalam setiap pertanyaan. Dengan begitu ada kegilaan.
OldTinfoil
14
@ user5359531, saya tidak jelas mengapa import numpy as npdapat diasumsikan di seluruh stackoverflow tetapi menginstal gitpython melampaui 'bersih' dan 'portabel'. Saya pikir ini sejauh ini solusi terbaik, karena tidak menemukan kembali roda, menyembunyikan implementasi yang buruk dan tidak berkeliling meretas jawaban git dari subproses.
Jblasco
7
@ user5359531 Meskipun saya setuju secara umum bahwa Anda tidak boleh melempar perpustakaan baru yang mengkilap di setiap masalah kecil, definisi Anda tentang "portabilitas" tampaknya mengabaikan skenario modern di mana pengembang memiliki kontrol penuh atas semua lingkungan yang mengatakan aplikasi dijalankan. Pada 2018 kami memiliki Wadah Docker, lingkungan virtual, dan gambar mesin (mis. AMI) dengan pipatau kemampuan untuk menginstal dengan mudah pip. Dalam skenario modern ini, pipsolusi sama portabelnya dengan solusi "perpustakaan standar".
Ryan
106

Posting ini berisi perintah, jawaban Greg berisi perintah subproses.

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
sumber
32
Tambahkan strip () ke hasil untuk mendapatkan ini tanpa jeda :)
belalang
Bagaimana Anda menjalankan ini untuk repo git di jalur tertentu?
pkamb
2
@ pkamb Gunakan os.chdir untuk melakukan cd ke jalur repo git yang Anda tertarik untuk bekerja dengannya
Zac Crites
Bukankah itu memberikan jawaban yang salah jika revisi yang saat ini diperiksa bukan kepala cabang?
Maks
7
Tambahkan a .decode('ascii').strip()untuk mendekode string biner (dan menghapus jeda baris).
pfm
13

numpymemiliki rutinitas multi-platform yang terlihat bagus di 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
sumber
2
Saya suka ini, sangat bersih dan tidak ada perpustakaan eksternal
13al
Jawaban Yuji memberikan solusi serupa hanya dalam satu baris kode yang menghasilkan hasil yang sama. Bisakah Anda menjelaskan mengapa numpymerasa perlu untuk "membangun lingkungan minimal"? (dengan asumsi mereka punya alasan kuat untuk)
MD004
Saya hanya memperhatikan ini di repo mereka, dan memutuskan untuk menambahkannya ke pertanyaan ini untuk orang-orang yang tertarik. Saya tidak mengembangkan di Windows, jadi saya belum menguji ini, tapi saya berasumsi bahwa pengaturan envdict diperlukan untuk fungsionalitas lintas platform. Jawaban Yuji tidak, tapi mungkin itu bekerja pada UNIX dan Windows.
ryanjdillon
Melihat kesalahan git, mereka melakukan ini sebagai perbaikan bug untuk SVN 11 tahun yang lalu: github.com/numpy/numpy/commit/…. Mungkin perbaikan bug tidak lagi diperlukan untuk git.
gparent
@ MD004 @ryanjdillon Mereka mengatur lokal agar .decode('ascii')berfungsi - jika tidak pengkodean tidak diketahui.
z0r
7

Jika subproses tidak portabel dan Anda tidak ingin menginstal paket untuk melakukan sesuatu sesederhana ini, Anda juga dapat melakukannya.

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

Saya hanya menguji ini pada repo saya tetapi tampaknya bekerja cukup konsisten.

kagronick
sumber
Terkadang / refs / tidak ditemukan, tetapi komit saat ini ditemukan di "packed-refs".
am9417
7

Berikut versi jawaban Greg yang lebih lengkap :

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

Atau, jika skrip dipanggil dari luar repo:

import subprocess, os
os.chdir(os.path.dirname(__file__))
print(subprocess.check_output(["git", "describe", "--always"]).strip().decode())
AndyP
sumber
1
Alih-alih menggunakan os.chdir, cwd=arg dapat digunakan check_outputuntuk mengubah sementara direktori kerja sebelum mengeksekusi.
Marc
0

Jika Anda tidak memiliki git karena beberapa alasan, tetapi Anda memiliki git repo (folder .git ditemukan), Anda dapat mengambil hash komit dari .git / fetch / heads / [branch]

Sebagai contoh, saya telah menggunakan snippet Python cepat dan kotor berikut yang dijalankan di root repositori untuk mendapatkan id komit:

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
sumber