228

我想在 Python 脚本的输出中包含当前的 git 哈希(作为生成该输出的代码的版本号)。

如何在我的 Python 脚本中访问当前的 git 哈希?

4

12 回答 12

269

无需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 分解为一个单独的进程,该进程可以定期删除

于 2016-12-18T16:03:44.110 回答
144

这篇文章包含命令,格雷格的答案包含子进程命令。

import subprocess

def get_git_revision_hash() -> str:
    return subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('ascii').strip()

def get_git_revision_short_hash() -> str:
    return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode('ascii').strip()

跑步时

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

你得到输出:

fd1cd173fc834f62fa7db3034efc5b8e0f3b43fe
fd1cd17
于 2014-02-20T07:40:24.757 回答
116

git describe命令是创建人类可展示的代码“版本号”的好方法。从文档中的示例:

使用 git.git current tree 之类的东西,我得到:

[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()
于 2013-02-20T21:06:49.187 回答
16

这是格雷格答案的更完整版本:

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

或者,如果从 repo 外部调用脚本:

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

或者,如果从存储库外部调用脚本并且您喜欢pathlib

import subprocess
from pathlib import Path
print(subprocess.check_output(["git", "describe", "--always"], cwd=Path(__file__).resolve().parent).strip().decode())
于 2019-08-28T00:40:41.307 回答
15

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
于 2016-10-21T06:56:43.257 回答
11

如果 subprocess 不可移植,并且您不想安装软件包来做这么简单的事情,您也可以这样做。

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

我只在我的 repos 上对此进行了测试,但它似乎工作得相当一致。

于 2019-05-21T20:11:50.900 回答
6

这是对Yuji 'Tomita' Tomita答案的改进。

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())
于 2021-02-20T14:57:59.903 回答
4

如果您想要比哈希更多的数据,您可以使用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

于 2020-08-26T14:37:21.903 回答
2

如果由于某种原因您没有可用的 git,但您有 git repo(找到 .git 文件夹),您可以从 .git/fetch/heads/[branch] 获取提交哈希

例如,我使用了以下在存储库根目录运行的快速而简单的 Python 片段来获取提交 id:

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]
于 2020-01-28T14:13:05.767 回答
1

如果你像我一样:

  • 多平台,因此子进程可能有一天会崩溃
  • 使用 Python 2.7 所以 GitPython 不可用
  • 不想仅仅为此使用 Numpy
  • 已经在使用 Sentry(旧版本:raven)

然后(这在 shell 上不起作用,因为 shell 没有检测到当前文件路径,请将 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 一段时间。

这是让我进入这个stackoverflow问题的讨论

所以使用没有 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”。

希望它会帮助你们中的一些人;)

于 2020-09-07T09:44:51.713 回答
1

我遇到了这个问题并通过实现这个功能解决了它。 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)
于 2021-07-01T19:08:49.973 回答
1

我遇到了类似于 OP 的问题,但在我的情况下,我将源代码作为 zip 文件提供给我的客户端,虽然我知道他们会安装 python,但我不能假设他们会有 git。由于 OP 没有指定他的操作系统,并且如果他安装了 git,我想我可以在这里做出贡献。

为了只获得提交的哈希值,Naelson Douglas 的回答是完美的,但要获得标签名称,我使用的是dulwich python 包。它是 python 中一个简化的 git 客户端。

用一个安装包后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 返回的内容,这意味着我是标签v0.1.2之后的1次提交,提交的 7 位哈希是fb41223 .

它有一些限制:目前它没有显示存储库是否脏的选项,它总是显示一个 7 位数的 hash,但不需要安装 git,因此可以选择权衡。

编辑:pip install如果由于选项导致命令错误--pure(问题在此处解释),请选择两种可能的解决方案之一:

  1. 首先安装 Dulwich 包的依赖: pip install urllib3 certifi && pip install dulwich --global-option="--pure"
  2. 安装不带选项 pure: pip install dulwich。这将在您的系统中安装一些平台相关文件,但会提高包的性能
于 2021-07-28T20:30:17.123 回答