在Python脚本中获取当前的git hash

问题:在Python脚本中获取当前的git hash

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

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

I would like to include the current git hash in the output of a Python script (as a the version number of the code that generated that output).

How can I access the current git hash in my Python script?


回答 0

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

使用git.git之类的当前树,我得到:

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

The git describe command is a good way of creating a human-presentable “version number” of the code. From the examples in the documentation:

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

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

i.e. the current head of my “parent” branch is based on v1.0.4, but since it has a few commits on top of that, describe has added the number of additional commits (“14”) and an abbreviated object name for the commit itself (“2414721”) at the end.

From within Python, you can do something like the following:

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

回答 1

无需费力地git自己从命令中获取数据。GitPython是执行此操作以及许多其他工作的非常好方法git。它甚至对Windows具有“尽力而为”的支持。

之后pip install gitpython,你可以做

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

No need to hack around getting data from the git command yourself. GitPython is a very nice way to do this and a lot of other git stuff. It even has “best effort” support for Windows.

After pip install gitpython you can do

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

Something to consider when using this library. The following is taken from gitpython.readthedocs.io

Leakage of System Resources

GitPython is not suited for long-running processes (like daemons) as it tends to leak system resources. It was written in a time where destructors (as implemented in the __del__ method) still ran deterministically.

In case you still want to use it in such a context, you will want to search the codebase for __del__ implementations and call these yourself when you see fit.

Another way assure proper cleanup of resources is to factor out GitPython into a separate process which can be dropped periodically


回答 2

这篇文章包含命令,Greg的答案包含subprocess命令。

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

This post contains the command, Greg’s answer contains the subprocess command.

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

回答 3

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

numpy has a nice looking multi-platform routine 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

    try:
        out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'])
        GIT_REVISION = out.strip().decode('ascii')
    except OSError:
        GIT_REVISION = "Unknown"

    return GIT_REVISION

回答 4

如果子进程不是可移植的,并且您不想安装软件包来执行此简单操作,则也可以执行此操作。

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

我只在我的存储库上测试过此功能,但它似乎工作得相当稳定。

If subprocess isn’t portable and you don’t want to install a package to do something this simple you can also do this.

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

I’ve only tested this on my repos but it seems to work pretty consistantly.


回答 5

这是Greg答案的更完整版本:

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

Here’s a more complete version of Greg’s answer:

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

Or, if the script is being called from outside the repo:

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

回答 6

如果由于某种原因没有可用的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]

If you don’t have git available for some reason, but you have the git repo (.git folder is found), you can fetch the commit hash from .git/fetch/heads/[branch]

For example, I’ve used a following quick-and-dirty Python snippet run at the repository root to get the commit 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]