Получить текущий хэш git в скрипте Python

Я хотел бы включить текущий хеш git в вывод скрипта Python (как номер версии кода, который генерировал этот вывод).

Как я могу получить доступ к текущему git-хешу в моем скрипте Python?

12 ответов

Решение

git describe Команда - это хороший способ создания презентабельного "номера версии" кода. Из примеров в документации:

С чем-то вроде текущего дерева git.git я получаю:

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

то есть текущий заголовок моей "родительской" ветви основан на v1.0.4, но так как он имеет несколько коммитов, к ним добавлено число дополнительных коммитов ("14") и сокращенное имя объекта для фиксации. сам ("2414721") в конце.

Из Python вы можете сделать что-то вроде следующего:

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

Не нужно взламывать получение данных от git командуй собой. GitPython - очень хороший способ сделать это и множество других git вещи. У него даже есть поддержка "наилучшего усилия" для Windows.

После pip install gitpython ты можешь сделать

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

Этот пост содержит команду, а ответ Грега содержит команду подпроцесса.

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

Вот более полная версия ответа Грега:

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

Или, если скрипт вызывается из-за пределов репо:

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

Если подпроцесс не переносим и вы не хотите устанавливать пакет, чтобы сделать что-то такое простое, вы также можете сделать это.

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

Я только протестировал это на своих репозиториях, но, похоже, работает довольно стабильно.

numpy имеет приятную многоплатформенную рутину 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

Это улучшенный ответ Юдзи Томита Томита .

      import subprocess

def get_git_revision_hash():
    full_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
    full_hash = str(full_hash, "utf-8").strip()
    return full_hash

def get_git_revision_short_hash():
    short_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
    short_hash = str(short_hash, "utf-8").strip()
    return short_hash

print(get_git_revision_hash())
print(get_git_revision_short_hash())

Я столкнулся с этой проблемой и решил ее, реализовав эту функцию. https://gist.github.com/NaelsonDouglas/9bc3bfa26deec7827cb87816cad88d59

      from pathlib import Path

def get_commit(repo_path):
    git_folder = Path(repo_path,'.git')
    head_name = Path(git_folder, 'HEAD').read_text().split('\n')[0].split(' ')[-1]
    head_ref = Path(git_folder,head_name)
    commit = head_ref.read_text().replace('\n','')
    return commit


r = get_commit('PATH OF YOUR CLONED REPOSITORY')
print(r)

Если вам нужно немного больше данных, чем хэш, вы можете использовать git-log:

import subprocess

def get_git_hash():
    return subprocess.check_output(['git', 'log', '-n', '1', '--pretty=tformat:%H']).strip()

def get_git_short_hash():
    return subprocess.check_output(['git', 'log', '-n', '1', '--pretty=tformat:%h']).strip()

def get_git_short_hash_and_commit_date():
    return subprocess.check_output(['git', 'log', '-n', '1', '--pretty=tformat:%h-%ad', '--date=short']).strip()

полный список вариантов форматирования - см. git log --help

Если по какой-то причине у вас нет доступного git, но у вас есть репозиторий git (найдена папка.git), вы можете получить хеш фиксации из.git/fetch/ Heads/[ветка]

Например, я использовал следующий быстрый и грязный фрагмент Python, запущенный в корне репозитория, чтобы получить идентификатор фиксации:

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]

У меня была проблема, похожая на OP, но в моем случае я доставляю исходный код своему клиенту в виде zip-файла, и, хотя я знаю, что у них будет установлен python, я не могу предположить, что у них будет git. Поскольку OP не указал свою операционную систему, и если у него установлен git, я думаю, что могу внести здесь свой вклад.

Чтобы получить только хэш фиксации, ответ Наельсона Дугласа был идеальным, но чтобы иметь имя тега, я использую Python пакетdulwich . Это упрощенный клиент git на Python.

После установки пакета с pip install dulwich --global-option="--pure" можно сделать:

      from dulwich import porcelain

def get_git_revision(base_path):
    return porcelain.describe(base_path)

r = get_git_revision("PATH OF YOUR REPOSITORY's ROOT FOLDER")
print(r)

Я только что запустил этот код в одном репозитории, и он показал результат v0.1.2-1-gfb41223, аналогично тому, что возвращает git describe , что означает, что я совершаю 1 коммит после тега v0.1.2, а семизначный хеш фиксации равен fb41223.

У него есть некоторые ограничения: в настоящее время у него нет возможности показать, загрязнен ли репозиторий, и он всегда показывает 7-значный хэш , но нет необходимости устанавливать git, поэтому можно выбрать компромисс.

Если вы похожи на меня:

  • Мультиплатформенность, поэтому подпроцесс может однажды выйти из строя
  • Использование Python 2.7, поэтому GitPython недоступен
  • Не хочу использовать Numpy только для этого
  • Уже используется Sentry (старая устаревшая версия: raven)

Затем (это не будет работать в оболочке, потому что оболочка не определяет текущий путь к файлу, замените BASE_DIR на текущий путь к файлу):

import os
import raven

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(raven.fetch_git_sha(BASE_DIR))

Вот и все.

Я искал другое решение, потому что хотел перейти на sentry_sdk и оставить raven, но, возможно, некоторые из вас захотят продолжить использовать raven какое-то время.

Вот обсуждение, которое привело меня к этой проблеме с переполнением стека

Таким образом, использование кода raven без raven также возможно (см. Обсуждение):

from __future__ import absolute_import

import os.path

__all__ = 'fetch_git_sha'


def fetch_git_sha(path, head=None):
    """
    >>> fetch_git_sha(os.path.dirname(__file__))
    """
    if not head:
        head_path = os.path.join(path, '.git', 'HEAD')

        with open(head_path, 'r') as fp:
            head = fp.read().strip()

        if head.startswith('ref: '):
            head = head[5:]
            revision_file = os.path.join(
                path, '.git', *head.split('/')
            )
        else:
            return head
    else:
        revision_file = os.path.join(path, '.git', 'refs', 'heads', head)

    if not os.path.exists(revision_file):
        # Check for Raven .git/packed-refs' file since a `git gc` may have run
        # https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery
        packed_file = os.path.join(path, '.git', 'packed-refs')
        if os.path.exists(packed_file):
            with open(packed_file) as fh:
                for line in fh:
                    line = line.rstrip()
                    if line and line[:1] not in ('#', '^'):
                        try:
                            revision, ref = line.split(' ', 1)
                        except ValueError:
                            continue
                        if ref == head:
                            return revision

    with open(revision_file) as fh:
        return fh.read().strip()

Я назвал этот файл versioning.py и импортирую "fetch_git_sha", где мне нужно, чтобы он передавал путь к файлу в качестве аргумента.

Надеюсь, это поможет некоторым из вас;)

Другие вопросы по тегам