在Python脚本中获取当前的git哈希值

246 人关注

我想在Python脚本的输出中包含当前的git hash(作为一个 version number 产生该输出的代码的)。

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

2 个评论
在命令行中以 git rev-parse HEAD 开始。 输出的语法应该很明显。
do subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode('ascii').strip() after having import subprocess
python
git
git-hash
Victor
Victor
发布于 2013-02-21
12 个回答
the
the
发布于 2021-07-29
已采纳
0 人赞同

不需要自己黑着脸从 git 命令中获取数据。 GitPython 是一个非常好的方法来做这个和其他很多 git 的东西。它甚至对Windows有 "最大努力 "的支持。

After pip install gitpython you can do

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

在使用这个库时需要考虑的问题。以下内容取自gitpython.readthedocs.io

系统资源的渗漏

GitPython 不适合长时间运行的进程(如守护进程),因为它往往会泄露系统资源。它是在一个解构器(如在__del__方法中实现的)仍然确定地运行的时代编写的。

如果你仍然想在这样的情况下使用它,你将想在代码库中搜索__del__的实现,并在你认为合适的时候自己调用这些代码。

另一个确保适当清理资源的方法是将GitPython纳入一个单独的进程,可以定期放弃。

@crishoj 不知道当这种情况发生时,你怎么能称它为便携式。 ImportError: No module named gitpython 。你不能依靠终端用户已经安装了 gitpython ,要求他们在你的代码工作之前安装它,这就使得它不具有可移植性。除非你要包括自动安装协议,在这一点上它不再是一个干净的解决方案。
@user5359531 我不敢苟同。GitPython提供了一个纯粹的Python实现,抽象出了特定平台的细节,它可以使用标准的软件包工具( pip / requirements.txt )在所有平台上安装。什么是不 "干净 "的?
这是在Python中做事情的正常方式。如果 OP 需要这些要求,那么他们会这么说的。我们不是读心者,我们不能预测每个问题中的每一个可能的情况。那样的话,就会出现疯狂的情况。
@user5359531,我不清楚为什么 import numpy as np 可以在整个stackoverflow中假设,但安装gitpython是超越 "干净 "和 "可移植 "的。我认为这是迄今为止最好的解决方案,因为它没有重新发明轮子,隐藏了丑陋的实现,没有从子进程中去黑掉git的答案。
Ryan
@user5359531 虽然我总体上同意,你不应该在每个小问题上都扔一个闪亮的新库,但你对 "可移植性 "的定义似乎忽略了现代场景,即开发者可以完全控制所述应用程序运行的所有环境。在2018年,我们有Docker容器、虚拟环境和机器镜像(例如AMI),有 pip 或者能够轻松安装 pip 。在这些现代场景中, pip 解决方案与 "标准库 "解决方案一样具有可移植性。
Yuji 'Tomita' Tomita
Yuji 'Tomita' Tomita
发布于 2021-07-29
0 人赞同

This post 含有该命令。 格雷格的回答 包含子进程命令。

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

when running

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

you get output:

fd1cd173fc834f62fa7db3034efc5b8e0f3b43fe
fd1cd17
    
在结果中添加一个 strip() 来得到这个没有换行的结果 :)
你如何对一个特定路径的git repo运行这个?
@pkamb 使用os.chdir来cd到你感兴趣的git repo的路径。
pfm
添加一个 .decode('ascii').strip() 来解码二进制字符串(并删除换行符)。
Kipr
如果你的代码是从另一个目录中运行的,你可能想添加 cwd=os.path.dirname(os.path.realpath(__file__)) 作为 check_output 的参数。
Greg Hewgill
Greg Hewgill
发布于 2021-07-29
0 人赞同

The git describe 命令是一个很好的方法,可以为代码创建一个可供人类参考的 "版本号"。从文档中的例子来看。

With something like git.git current tree, I get:

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

也就是说,我的 "父 "分支的当前头部是基于v1.0.4的,但由于它在此基础上有一些提交,所以describe在最后加入了额外的提交数量("14")和提交本身的缩写对象名称("2414721")。

在Python中,你可以做如下事情。

import subprocess
label = subprocess.check_output(["git", "describe"]).strip()
    
这样做的缺点是,如果代码在没有git repo的情况下运行,版本打印代码会被破坏。例如,在生产中。)
Greg Hewgill
@JosefAssad: 如果你在生产中需要一个版本标识符,那么你的部署程序应该运行上述代码,其结果应该被 "烘烤 "到部署到生产中的代码中。
注意,如果没有标签存在,git describe会失败。替换代码0
替换代码0】如果没有找到标签,将退回到最后一次提交。
@CharlieParker: git describe 通常要求至少有一个标签。如果你没有任何标签,请使用 --always 选项。请看 git describe documentation 欲了解更多信息。
mathandy
mathandy
发布于 2021-07-29
0 人赞同

这里有一个更完整的版本 格雷格的回答 :

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

或者,如果该脚本是从 repo 外部调用的,并且你喜欢 pathlib

import subprocess
from pathlib import Path
print(subprocess.check_output(["git", "describe", "--always"], cwd=Path(__file__).resolve().parent).strip().decode())
    
Marc
可以在 check_output 中使用 cwd= 参数,而不是使用 os.chdir ,以便在执行前临时改变工作目录。
John
谢谢你把从 repo 外部调用脚本的情况包括在内。 这正好咬住了我。
ryanjdillon
ryanjdillon
发布于 2021-07-29
0 人赞同

替换代码0】有一个漂亮的外观 多平台程序 in its 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
        out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'])
        GIT_REVISION = out.strip().decode('ascii')
    except OSError:
        GIT_REVISION = "Unknown"
    return GIT_REVISION
    
Yuji's 答案 只用一行代码就提供了一个类似的解决方案,产生了同样的结果。你能解释一下为什么 numpy 认为有必要 "构建一个最小的环境"?(假设他们有充分的理由这样做)
我刚刚在他们的repo中注意到这一点,并决定将其添加到这个问题中,供感兴趣的人参考。我不在Windows中开发,所以我没有测试过,但我认为设置 env 口令对于跨平台功能是必要的。Yuji的答案没有,但也许这在UNIX和Windows上都适用。
看一下git的责备,他们在11年前就把这个作为SVN的一个错误修复。 github.com/numpy/numpy/commit/… 可能对于git来说,错误修复已经没有必要了。
z0r
@MD004 @ryanjdillon 他们设置了locale,所以 .decode('ascii') 可以使用--否则编码就不知道了。
作为一个在 setup.py 中声明的函数,它不是 numpy 包的一部分。中声明的,它不是 numpy 包的一部分,所以不可能从 numpy 中导入它。要使用它,你需要在你自己的代码中添加这个方法。
kagronick
kagronick
发布于 2021-07-29
0 人赞同

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

我只在我的仓库上测试过这个,但它似乎很稳定地工作。

有时没有找到/refs/,但在 "packed-refs "中可以找到当前的提交id。
Wayne Workman
Wayne Workman
发布于 2021-07-29
0 人赞同

这是对 富田雄二'富田 答案是。

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())
    
Ohad Cohen
Ohad Cohen
发布于 2021-07-29
0 人赞同

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

am9417
am9417
发布于 2021-07-29
0 人赞同

如果你因为某些原因没有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]
    
这对我来说很有效,尽管我不得不把'\\'改为'/'。一定是Windows的问题?
@Reishin 我想你是指 "特定环境编码"。我想是的,因为这将遭受较少的被标记为不恰当语言的风险。(顺便说一下,我没有--因为太慢了....)
HamzDiou
HamzDiou
发布于 2021-07-29
0 人赞同

如果你像我一样:

  • Multiplatform so subprocess may crash one day
  • Using Python 2.7 so GitPython not available
  • Don't want to use Numpy just for that
  • Already using Sentry (old depreciated version : 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一段时间。

    以下是让我陷入这个堆栈流问题的讨论情况

    因此,使用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 ('#', '^'):
                                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",将文件路径作为参数。

    Hope it will help some of you ;)

    Naelson Douglas
    Naelson Douglas
    发布于 2021-07-29
    0 人赞同

    我遇到了这个问题,并通过实现这个功能解决了这个问题。 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)
        
    Akira Cleber Nakandakare
    Akira Cleber Nakandakare
    发布于 2021-07-29
    0 人赞同

    我遇到了和OP类似的问题,但在我的情况下,我是以压缩文件的形式把源代码交给我的客户,虽然我知道他们会安装python,但我不能假设他们会有git。由于OP没有说明他的操作系统,以及他是否安装了git,我想我可以在这里做出贡献。

    为了只得到提交的哈希值。 内尔森-道格拉斯的回答 是完美的,但为了拥有标签名称,我在使用 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的输出,与以下内容类似returned by git describe,意思是说,我是1在标签之后提交v0.1.2而提交的7位数哈希值是fb41223.

    它有一些局限性:目前它没有一个选项来显示一个版本库是否是脏的,并且它总是显示一个7位数的哈希值,但不需要安装git,所以可以选择权衡。

    Edit:如果由于选项--pure导致命令pip install出现错误(该问题解释如下here), pick one of the two possible solutions: