在Git中,我如何将当前提交的哈希写入同一提交中的文件?

174

我正在尝试使用Git钩子进行一些高级操作,但实际上我不知道该如何实现(或者是否可能实现)。

我需要做的是:在每次提交时获取其哈希值,然后使用此哈希值更新提交中的一个文件。

有什么想法吗?


16
我有一个网络应用程序,希望将已安装的应用程序版本与该版本关联的确切提交相关联。我最初的想法是使用提交哈希更新类似about.html的文件。但是在研究git的对象模型后,我意识到这几乎是不可能的 =/ - Felipe Kamakura
37
这是一个非常实际的问题。我也遇到过! - Li Dong
8
对于我来说,我希望我的程序能够在日志中写入以下信息:"myprog 启动,版本号为 v.56c6bb2"。这样,如果有人报告一个 bug 并发送给我日志文件,我就可以确切地知道是哪个版本的程序在运行。 - Edward Falk
5
@Jefromi,实际用例非常普遍,易于影响初学者。将真实版本“印刻”到基线文件中是一种基本需求,为什么这样做是错误的并不明显,尤其是在手动修订控制方面,这几乎是您唯一的选择(请记住初学者)。此外,许多项目根本没有任何构建/安装/部署步骤,可以将版本号添加到现有文件中。无论如何,与其使用pre-commit,post-checkout钩子即使在这些情况下也能提供帮助。 - Sz.
1
这是不可能的!如果你能做到这一点,那么你就破解了SHA-1哈希算法... https://ericsink.com/vcbe/html/cryptographic_hashes.html - betontalpfa
8个回答

106

我建议您做与您想法类似的事情:将SHA1放在一个未跟踪的文件中,该文件是作为构建/安装/部署过程的一部分生成的。这很容易实现(git rev-parse HEAD > filename 或者 git describe [--tags] > filename),并且避免了像最终的文件与Git跟踪的不同等诡异行为。

当需要版本号时,您的代码可以引用此文件,或者构建过程可以将该信息合并到最终产品中。实际上,这正是Git本身获取其版本号的方式 - 构建过程从存储库中获取版本号,然后将其构建到可执行文件中。


4
有没有人能够逐步详细说明如何做这件事?或者至少指点一下正确的方向? - Joel Worsham
1
@Joel 怎么做什么?我提到了如何将哈希放入文件中;其余部分可能与您的构建过程有关?如果您想询问那部分内容,也许可以新开一个问题。 - Cascabel
1
在我的情况下,我添加了一个规则到我的Makefile中,在每次构建时生成一个“gitversion.h”文件。请参见https://dev59.com/ImQn5IYBdhLWcg3w1qIc#38087913。 - Edward Falk
1
你可能可以使用“git-checkout”钩子自动化此过程。问题在于,这些钩子必须手动安装。 - Edward Falk

27

无法编写当前提交哈希:如果您设法预先计算未来的提交哈希,则只要修改任何文件,它就会更改。

但是,有三个选项:

  1. 使用脚本递增“提交ID”并在某个地方包含它。不太美观。
  2. 将要存储哈希值的文件添加到.gitignore中。不太方便。
  3. pre-commit 中,存储上一个提交哈希 :) 99.99% 的情况下,您不会修改/插入提交,因此,这将起作用。在最坏的情况下,您仍然可以识别源修订版。

我正在编写钩子脚本,'when it's done',将在这里发布,但是——比Duke Nukem Forever发布还要早:))

更新: .git/hooks/pre-commit的代码:

#!/usr/bin/env bash
set -e

#=== 'prev-commit' solution by o_O Tync
#commit_hash=$(git rev-parse --verify HEAD)
commit=$(git log -1 --pretty="%H%n%ci") # hash \n date
commit_hash=$(echo "$commit" | head -1)
commit_date=$(echo "$commit" | head -2 | tail -1) # 2010-12-28 05:16:23 +0300

branch_name=$(git symbolic-ref -q HEAD) # https://dev59.com/5HI-5IYBdhLWcg3w9thw
branch_name=${branch_name##refs/heads/}
branch_name=${branch_name:-HEAD} # 'HEAD' indicates detached HEAD situation

# Write it
echo -e "prev_commit='$commit_hash'\ndate='$commit_date'\nbranch='$branch'\n" > gitcommit.py

现在我们需要的是一个工具,将prev_commit,branch对转换为真正的提交哈希值 :)

我不知道这种方法是否可以区分合并提交。很快就会去检查一下。


13

有人向我指出了关于ident的“man gitattributes”部分,其中包括以下内容:

ident

当为路径设置属性ident时,git会将blob对象中的$Id$替换为$Id:,后跟40个字符的十六进制blob对象名称,再跟一个美元符号$。在检出时,任何以$Id:开头并以$结尾的字节序列都会被替换为$Id$。

如果你仔细想一想,这也是CVS、Subversion等工具所做的。如果你查看存储库,你会发现存储库中的文件始终包含例如$Id$的标识符。它从不包含该标识符的扩展。只有在检出时,文本才会被扩展。


9
ident 是文件本身的哈希值,而不是提交的哈希值。从 http://git-scm.com/book/zh/v2/ 自定义 Git - Git 属性 中得知:"但这个结果的用处有限。如果你曾经使用过 CVS 或 Subversion 中的关键字替换,你可以包含一个日期时间戳——SHA 值并没有太大的帮助,因为它相当随机,并且你无法确定一个 SHA 值比另一个 SHA 值旧还是新。" filter 需要一些工作,但可以将提交信息输入(和输出)到文件中。 - Zach Young

13
这可以通过在gitattributes中使用filter属性来实现。您需要提供一个smudge命令来插入提交ID,以及一个clean命令来删除它,这样插入的文件不会仅仅因为提交ID而更改。
因此,提交ID从未存储在文件的blob中;它只是在您的工作副本中扩展。(实际上将提交ID插入blob中将变成无限递归的任务。☺)任何克隆此树的人都需要为自己设置属性。

9
不可能的任务,不是递归任务。提交哈希值取决于树哈希值,而树哈希值又取决于文件哈希值,文件哈希值则取决于文件内容。你必须确保自洽性。除非你能找到 SHA-1 哈希的一种[广义]不动点。 - Jakub Narębski
1
@Jakub,在git中有什么技巧可以创建被跟踪的文件,而不会修改结果哈希值吗?也许有一种方法可以覆盖其哈希值。那将是一个解决方案 :) - kolypto
1
@o_O Tync:不可能。更改文件意味着更改哈希(文件的哈希)-这是按设计和哈希函数的定义而确定的。 - Jakub Narębski
2
这是一个相当不错的解决方案,但请记住,这涉及到钩子,每当您克隆存储库时都必须手动安装。 - Edward Falk

12

跳出提交盒子的思维!

将此放入文件 hooks/post-checkout 中。

#!/bin/sh
git describe --all --long > config/git-commit-version.txt

这个版本将在您使用它的任何地方都可用。


我稍微修改了你的答案,以确保版本文件始终包含在提交中,方法是在末尾添加以下内容:git add config/git-commit-version.txt - Jason Wheeler

3

我认为你实际上不想这样做,因为当提交中的文件被更改时,提交的哈希值也会随之更改。


实际上,如果需要在最终构建中显示此 Git 提交版本,则 @Cascabel 建议的是最佳选项。git describe --all --long > src/assets/git-commit-version.txt 这就是我在 GitHub actions 构建步骤中正在执行的操作。 - Rajendra

2

让我通过git内部机制来探究为什么这是一个具有挑战性的问题。您可以通过以下方式获取当前提交的sha1:

#!/bin/bash
commit=$(git cat-file commit HEAD) #
sha1=($((printf "commit %s\0" $(echo "$commit" | wc -c); echo "$commit") | sha1sum))
echo ${sha1[0]}

基本上,您需要对git cat-file commit HEAD返回的消息运行sha1校验和。当您检查此消息时,立即出现两个问题。一个是tree sha1,另一个是提交时间。

现在,通过修改消息并猜测提交所需的时间或安排在特定时间提交,可以轻松解决提交时间的问题。真正的问题是tree sha1,您可以从git ls-tree $(git write-tree) | git mktree获取它。基本上,您正在对来自ls-tree的消息进行sha1校验和,该消息是所有文件及其sha1校验和的列表。

因此,您的提交sha1校验和取决于您的tree sha1校验和,后者直接取决于文件sha1校验和,这样就完成了循环,并且依赖于提交sha1。因此,我所拥有的技术仍存在循环问题。

使用不太安全的校验和,已经证明可以通过暴力方法将文件的校验和写入文件本身;但是,我不知道是否有任何工作能够使用sha1完成此任务。这并非不可能,但是在我们目前的理解下几乎不可能(但是谁知道也许在几年后它将变得微不足道)。然而,即使更难以暴力破解,因为您必须将(blob)校验和的(tree)校验和的(commit)校验和写入文件中。


有没有一种方法可以提交文件,然后进行检出,并在每个源代码文件的开头放置最新的提交哈希作为注释?然后从那里构建和运行? - John Wooten

0
我更喜欢简单地写下确切的日期时间和父提交的哈希值,所以在hooks/pre-commit中我写下了以下代码:
#!/bin/bash
ver_file=version.txt
> $ver_file
date +"%Y-%m-%d %T %:z" >> $ver_file
echo -n "Parent: " >> $ver_file
git rev-parse HEAD >> $ver_file
git add $ver_file
echo "Date-time and parent commit added to '$ver_file'"
exit 0

示例自动生成的version.txt文件:
2023-05-24 01:24:12 +03:30
Parent: 35acd10240a55d164b371aa28812e8e988ab0c8d


这种方法在你切换到另一个提交时仍然有效,因为version.txt文件的存储方式与其他文件完全相同,并且也存储在remote仓库和submodule中,出于同样的原因,但是你必须确保同样的文件也存在于.git/hooks/pre-commit中,以供你的子模块或远程仓库使用。
缺点:
  1. GitHub和其他一些网站不支持此方法。

  2. 执行合并操作时,version.txt总会产生冲突,但是除了需要进行commit -am <message>之外,不需要采取进一步的操作,因为在执行合并的commit之前,pre-commit也会被运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接