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

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

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


person Victor    schedule 20.02.2013    source источник
comment
Начните с git rev-parse HEAD в командной строке. Синтаксис вывода должен быть очевиден.   -  person Mel Nicholson    schedule 21.02.2013


Ответы (11)


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

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

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

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

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

import subprocess
label = subprocess.check_output(["git", "describe"]).strip()
person Greg Hewgill    schedule 20.02.2013
comment
Это имеет недостаток, заключающийся в том, что код печати версии будет нарушен, если код когда-либо запускается без репозитория git. Например, в производстве. :) - person JosefAssad; 21.02.2013
comment
@JosefAssad: если вам нужен идентификатор версии в производственной среде, тогда ваша процедура развертывания должна запускать приведенный выше код, и результат должен быть включен в код, развернутый в производственной среде. - person Greg Hewgill; 21.02.2013
comment
Это только один способ добиться этого; есть и другие способы. Атрибуты git могут использоваться для ввода информации о версии при оформлении заказа. Несмотря на то, что атрибуты git не передаются клонам, если они определены в главной копии, а операторы берут свой код от мастера, это было бы более простым решением. - person JosefAssad; 21.02.2013
comment
Обратите внимание, что git describe завершится ошибкой, если теги отсутствуют: fatal: No names found, cannot describe anything. - person kynan; 26.09.2014
comment
git describe --always вернется к последней фиксации, если теги не найдены - person Leonardo; 06.03.2015
comment
Работает ли это, если сценарий находится где-то в моей переменной $ PATH, но я запускаю его из другого места в файловой системе? - person Christian Herenz; 22.01.2016
comment
Чтобы получить формат, подобный приведенному выше: <last tag>-<num commits after tag>-<hash> Мне пришлось использовать git describe --long --tags - person djangonaut; 18.02.2016
comment
не сработало: >>> label = subprocess.check_output(["git", "describe"]) fatal: No names found, cannot describe anything. Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 573, in check_output raise CalledProcessError(retcode, cmd, output=output) subprocess.CalledProcessError: Command '['git', 'describe']' returned non-zero exit status 128 - person Charlie Parker; 01.07.2016
comment
@CharlieParker: git describe обычно требует хотя бы одного тега. Если у вас нет тегов, используйте параметр --always. Дополнительную информацию см. В документации git description. - person Greg Hewgill; 01.07.2016
comment
@Dims: использование имени ветки parent является примером, приведенным в документации. Вы бы использовали там свое собственное имя ветки. - person Greg Hewgill; 09.10.2018
comment
@GregHewgill, тогда это должно быть указано <branch_name> или выделено в тексте - person Dims; 20.12.2018
comment
Не работает, если нет тега, в отличие от ответа Юджи «Томита» Томиты ниже, который точно дает то, о чем спрашивает вопрос. - person Syed Priom; 03.05.2019
comment
Если вам всегда нужен хеш, git describe --always бесполезен, потому что он возвращает аннотированный тег, если он существует. - person RobinL; 14.10.2020

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

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

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

Что следует учитывать при использовании этой библиотеки. Следующее взято из gitpython.readthedocs.io.

Утечка системных ресурсов

GitPython не подходит для длительных процессов (например, демонов), так как имеет тенденцию к утечке системных ресурсов. Он был написан в то время, когда деструкторы (реализованные в методе __del__) все еще работали детерминированно.

Если вы все еще хотите использовать его в таком контексте, вам нужно будет поискать в кодовой базе __del__ реализации и вызвать их самостоятельно, когда сочтете нужным.

Другой способ обеспечить надлежащую очистку ресурсов - выделить GitPython в отдельный процесс, который можно периодически отбрасывать.

person kqw    schedule 18.12.2016
comment
@crishoj Не уверен, как это можно назвать переносимым, когда это происходит: ImportError: No module named gitpython. Вы не можете полагаться на то, что конечный пользователь установил gitpython, и требование установить его до того, как ваш код заработает, сделает его непереносимым. Если вы не собираетесь включать протоколы автоматической установки, в этот момент это уже не чистое решение. - person user5359531; 26.05.2017
comment
@ user5359531 Прошу не согласиться. GitPython предоставляет чистую реализацию Python, абстрагируя детали, специфичные для платформы, и его можно установить с помощью стандартных инструментов пакета (pip / requirements.txt) на всех платформах. Что не чистое? - person crishoj; 27.05.2017
comment
pip доступен не во всех системах. В этом отношении pip не нуждается и во внешнем доступе в Интернет для установки указанных пакетов. - person user5359531; 28.05.2017
comment
Это нормальный способ делать что-то в Python. Если OP нужны эти требования, они бы так и сказали. Мы не читатели мыслей, мы не можем предсказать все варианты ответа на каждый вопрос. В этом безумие. - person OldTinfoil; 31.01.2018
comment
@ user5359531, мне непонятно, почему import numpy as np можно предполагать на протяжении всего stackoverflow, но установка gitpython выходит за рамки «чистого» и «переносимого». Я думаю, что это, безусловно, лучшее решение, потому что оно не изобретает велосипед, скрывает уродливую реализацию и не пытается взломать ответ git из подпроцесса. - person Jblasco; 10.04.2018
comment
Если его нет в стандартной библиотеке, он не «переносимый». Numpy не исключение. subprocess - это стандартный метод взаимодействия с программами CLI из Python. Установка сторонних библиотек в качестве ключевого момента для решения каждой простой проблемы в Python не является хорошей практикой и вызывает проблемы в тот момент, когда вам нужно запустить свой код в любой другой системе. Если вы хотите скрыть «уродливую реализацию», используйте функцию. Если код никогда не будет запускаться кем-либо или где-либо еще, тогда, конечно, используйте любое решение, которое вам нравится. - person user5359531; 10.04.2018
comment
@ user5359531 Хотя я в целом согласен с тем, что вам не следует бросать блестящую новую библиотеку на каждую небольшую проблему, ваше определение переносимости, похоже, игнорирует современные сценарии, в которых разработчики имеют полный контроль над всеми средами, в которых работают приложения. В 2018 году у нас есть контейнеры Docker , виртуальные среды и образы машин (например, AMI) с pip или возможностью простой установки pip. В этих современных сценариях решение pip так же переносимо, как и стандартное библиотечное решение. - person Ryan; 02.08.2018
comment
@Ryan, спасибо, но я разработчик и не контролирую свою среду разработки, Docker запрещен на оборудовании компании из-за проблем с безопасностью, необходимости разрабатывать и запускать программы на старых серверах, разработчикам не хватает прав администратора на сервере и т. Д. И т. Д. Сейчас может быть 2018 год, но многие системы не обновлялись с 2012 года или ранее, и не все разработчики обладают этими роскошными возможностями, которые вы описываете. Virtualenv также имеет проблемы совместимости между разными версиями Python. - person user5359531; 03.08.2018
comment
Если его нет в стандартной библиотеке, значит, он не« переносимый ». Мне очень жаль, но в этом нет никакого смысла. Использование языковой реализации (пакета), которая абстрагирует программиста от платформ, которые он использует, намного более переносимо, чем вызов подпроцессов, которые зависят от базовой платформы и наличия таких точных команд, которые могут отличаться в Mac. , Linux, Windows и BSD. По определению, использование интерфейсов абстракции - это само понятие переносимости, в то время как вызов подкоманд из ваших программ - нет. - person José L. Patiño; 26.03.2019
comment
Невозможность импортировать библиотеки - это патологический, а не общий случай. Обычным случаем в подавляющем большинстве программирования является использование библиотек - особенно во избежание неуклюжего взаимодействия с внешними программами. Если вы не можете, вам следует использовать подпроцесс, но в случае неудачи по той или иной веской причине это лучшее решение: используйте надежную библиотеку, созданную для обработки рассматриваемого варианта использования. - person Nathaniel Ford; 04.11.2019
comment
Согласен с обоими мнениями. Я вошел в подпроцесс, потому что GitPython нужен Python ›3.4, а я все еще использую Python 2.7. Возможно, позже воспользуюсь GitPython ... - person HamzDiou; 03.09.2020
comment
Я тестировал подпроцесс на своем локальном компьютере и при развертывании разбитой машины разработчика! Теперь я лучше разбираюсь в этом вопросе и вернусь с raven, который просто позволяет это на Python 2.7: import raven - ›raven.fetch_git_sha (BASE_DIR) - person HamzDiou; 07.09.2020

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

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'])
person Yuji 'Tomita' Tomita    schedule 20.02.2014
comment
Добавьте к результату полоску (), чтобы получить его без разрывов строк :) - person grasshopper; 02.09.2014
comment
Как бы вы запустили это для репозитория git по определенному пути? - person pkamb; 26.02.2015
comment
@pkamb Используйте os.chdir, чтобы указать путь к репозиторию git, с которым вы хотите работать - person Zac Crites; 27.07.2015
comment
Разве это не дало бы неправильный ответ, если текущая проверенная ревизия не является головкой ветки? - person max; 01.02.2016
comment
и subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) для названия ветки - person Ryan Allen; 23.05.2018
comment
Добавьте .decode('ascii').strip() для декодирования двоичной строки (и удалите разрыв строки). - person pfm; 09.11.2018
comment
Или добавьте universal_newlines=True, чтобы получить строку. - person z0r; 26.04.2020
comment
Это позволяет избежать утечек ресурсов, упомянутых в gitpython, и это довольно чистое двухстрочное определение для получения хэша. Мне это нравится. - person Wayne Workman; 20.02.2021

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
person ryanjdillon    schedule 21.10.2016
comment
Мне это нравится, довольно чисто и без внешних библиотек - person 13aal; 21.10.2017
comment
answer Юджи предлагает аналогичное решение только в одной строке кода, которое дает тот же результат. Можете ли вы объяснить, почему numpy счел необходимым создать минимальную среду? (при условии, что у них были на то веские причины) - person MD004; 18.07.2018
comment
Я только что заметил это в их репо и решил добавить это к этому вопросу для заинтересованных людей. Я не занимаюсь разработкой в ​​Windows, поэтому я не тестировал это, но я предполагал, что настройка env dict необходима для кросс-платформенной функциональности. Ответ Юдзи - нет, но, возможно, это работает как в UNIX, так и в Windows. - person ryanjdillon; 05.08.2018
comment
Глядя на вину git, они сделали это как исправление ошибки для SVN 11 лет назад: github.com / numpy / numpy / commit / Возможно, исправление ошибки больше не требуется для git. - person gparent; 22.01.2020
comment
@ MD004 @ryanjdillon Они устанавливают локаль так, чтобы .decode('ascii') работал - иначе кодировка неизвестна. - person z0r; 26.04.2020
comment
Есть ли способ импортировать эту функцию и использовать ее? Я попробовал: from numpy.setup import git_version, и это не сработало - person jlansey; 02.10.2020
comment
Поскольку функция объявлена ​​в setup.py, она не является частью пакета numpy, поэтому ее невозможно импортировать из numpy. Чтобы использовать его, вам нужно будет где-нибудь добавить этот метод в свой собственный код. - person ryanjdillon; 04.10.2020

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

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())
person AndyP    schedule 28.08.2019
comment
Вместо использования os.chdir в check_output можно использовать cwd= arg для временного изменения рабочего каталога перед выполнением. - person Marc; 19.09.2019

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

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

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

person kagronick    schedule 21.05.2019
comment
Иногда / refs / не обнаруживается, но текущий идентификатор фиксации находится в упакованных-refs. - person am9417; 03.02.2020

Это улучшение ответа Юджи 'Томита' Томита.

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())
person Wayne Workman    schedule 20.02.2021

если вам нужно немного больше данных, чем хэш, вы можете использовать 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

person Ohad Cohen    schedule 26.08.2020

Если по какой-то причине у вас нет доступного 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]
person am9417    schedule 28.01.2020
comment
Это сработало для меня, хотя мне пришлось изменить '\\' на '/'. Должна быть вещь Windows? - person chrislondon; 20.08.2020
comment
@Reishin Я думаю, вы имели в виду кодирование для конкретной среды. Я так думаю, потому что это снизит риск быть отмеченным за ненормативную лексику. (Что, кстати, я не сделал - из-за того, что был слишком медленным ....) - person Yunnosch; 09.11.2020

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

  • Мультиплатформенность, поэтому в один прекрасный день подпроцесс может дать сбой
  • Использование 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, где мне нужно, чтобы он передавал путь к файлу в качестве аргумента.

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

person HamzDiou    schedule 07.09.2020