git 是否能够直接从源文件中获取提交消息?

Is git able to take the commit message directly from source file?

我正在寻找一种直接从提交的源文件中提取 git 提交信息的方法,而无需调用编辑器或类似工具。

我们的部门刚刚开始使用 git,由于遗留原因,所做的更改写在源文件的顶部:

#!/usr/local/bin/php
<?php
//
// @(#)insert_AX_serialno.php   Arrow/ECS/EMEA/EA/nb    1.6     2018-03-14, 13:41:20 CET
//
// V 1.6:       Now also check the Warehouse Code of an item before inserting the serial
// 2018-03-07   number. The Warehouse Code must not be equal to any of three values (see below).
//
// V 1.5:       Now also check the Storage Dimensiaon of an item before inserting the serial
// 2018-03-07   number. The Storage Dimension must be equal to the constant "PHYSICAL".
//
// V 1.4:       introduced an "Environment" variable which determines the target of the GetAXPO...
// 2018-02-21   functions (DEV, UAT or PROD). The variable can either be set explicitly or gets
//              its value from the $_ENV array.
//
// V 1.3:       stop processing if a line does not have the necessary Approval Status and
// 2018-02-20   PO Status
//
// V 1.2:       Every insert requires now a RECID; either for the line or for the header.
// 2017-12-20   So we're selecting the RECID from the AX table if it's not provided as

现在我想直接从源代码中获取提交消息而不是再次输入,例如提交消息应该(在这个例子中)读作“V 1.6 - 2018-03-07 现在在插入序列号之前还要检查项目的仓库代码。仓库代码不得等于三个值中的任何一个(见下文)。"

我是 git 的新手,我能从 githooks 手册页中摘录的是我可以 准备 消息挂钩,但不替换它。

我的想法是,我可以使用 git commit <filename> 提交一个文件,然后 git 从源文件中获取相关消息 ...

问题是:
1) 钩子是否知道提交了哪个文件is/are?如果是,它是挂钩的参数还是环境变量?
2) 挂钩能否从源文件中准备一个消息文件并使 git 使用该文件而不是打开编辑器(当然不使用“-m”参数)?

all I could excerpt from the githooks man page was that I can prepare the message with a hook, but not replace it.

您可以准备 消息,包括完全替换

1) Does a hook know which file(s) is/are being committed?

没有,但是你可以自己查询git。新提交的文件在索引中。使用命令 git diff --name-only.

列出文件

2) Can a hook prepare a message file out of the source file

不,但是您可以为此编写自己的脚本。

and make git use that file instead of opening the editor (of course without using the "-m" parameter)?

没有。当 git 执行 prepare-commit-msg 挂钩时,下一步总是打开编辑器。

您可以使用显式选项 git commit --no-edit 来阻止打开编辑器。或者您可以在提交之前准备一个带有提交消息的文件,而不使用 prepare-commit-msg 挂钩,而是调用 git commit -F message.txt.

我AFK的时候你也看看吧,不过我想看完这个,所以:

I'm new to git, and all I could excerpt from the githooks man page was that I can prepare the message with a hook, but not replace it.

事实并非如此——prepare-commit-msg 挂钩可以对消息文件做任何它喜欢的事情,包括完全替换它的内容。但是,您可能将通常只是 .git/COMMIT_EDITMSG 消息文件 git log 稍后显示的 而不是 .git/COMMIT_EDITMSG.

要了解正在发生的事情(以及您需要做什么),您需要了解 Git 实际放入提交中的内容以及提交的工作方式。

首先,您所做的每个提交至少在逻辑上包含1一个完整、独立的快照,与其他所有提交分开。也就是说,通过从某个顶级目录开始并枚举其中的文件和目录,可以找到一些源代码 tree-of-files-and-directories。2 Git 提交所有文件, 包括 sub-directories.3

因此,如果您有一个 Git 存储库,您可以 运行:

git log

查看各种提交,然后 select 通过哈希 ID(例如用鼠标剪切和粘贴)和 运行:

git ls-tree -r <hash-id>

并且您会看到该特定提交包含每个文件,而不仅仅是与之前提交不同的文件。

尽管如此,git show <hash-id> 将向您显示该提交中 更改 的内容,就好像该提交只存储了 更改。提交不存储更改——它完整地存储所有内容——但 git show 显示 更改。 git show 实现这一点的方法是将提交与其前身提交进行比较。

提交的前身是提交的。因此,提交是该父项的 child。对于每个文件,如果父提交中的文件与子提交中的文件匹配,则 git show 不说明该文件。如果文件不匹配,git show 会生成一组指令,用于更改父版本以使其成为子版本。 Git 在 git show 操作时产生此差异列表 *,这意味着您可以将各种标志传递给 git show 以更改 how 它计算并显示差异。

让我们看一下来自 Git 存储库的 Git 的实际原始提交对象,只是为了具体说明:

$ git rev-parse HEAD
e3a80781f5932f5fea12a49eb06f3ade4ed8945c
$ git cat-file -p e3a80781f5932f5fea12a49eb06f3ade4ed8945c | sed 's/@/ /'
tree 8e229ef2136e53a530ef74802f83d3b29a225439
parent 66023bbd78fe93c4704b3df754f9f7dc619ebaad
author Junio C Hamano <gitster pobox.com> 1519245935 -0800
committer Junio C Hamano <gitster pobox.com> 1519245935 -0800

Fourth batch for 2.17

这次提交的日志消息是最后一行。它位于 提交对象 中,哈希 ID 为 e3a80781f5932f5fea12a49eb06f3ade4ed8945c。如果我 运行 git show 提交,Git 会告诉我 Documentation/RelNotes/2.17.0.txt,但实际上,提交中的文件是 tree 8e229ef2136e53a530ef74802f83d3b29a225439 中的文件。如果我 运行 git ls-tree -r 8e229ef2136e53a530ef74802f83d3b29a225439,它产生 3222 行输出:

$ git ls-tree -r 8e229ef2136e53a530ef74802f83d3b29a225439 | wc
    3222   12900  259436

所以提交中有超过三千个文件。这些文件中有 3221 个与 父级 中的版本 100% 相同,即 66023bbd78fe93c4704b3df754f9f7dc619ebaad,其中也有 3222 个文件。

无论如何,这里的关键位是:

  • 提交是 Git 对象: 四种类型之一。完整集添加treeblob(仅file-data:文件的name,如果有一个,而是在树对象中)和 annotated-tag。最后一个在这里无关紧要。
  • 每个提交都有一些 parent 提交(通常只有一个)。
  • 每次提交都会保存一棵树。该树列出了文件名及其 blob 哈希 ID。您可以试验 git ls-tree(并阅读其文档)以了解它们的工作原理,但在这个级别上,细节无关紧要。
  • 每个提交也有其关联但 user-supplied 元数据:作者和提交者(姓名、电子邮件和时间戳),以及从您的挂钩可以编辑的消息文件复制的日志消息。

因此,进行提交是一个过程,涉及构建树对象以用作快照,然后添加元数据以进行新的提交。新提交获得一个新的、唯一的哈希 ID。 (树 ID 不一定是唯一的:如果您进行的新提交与之前的提交具有 完全相同的 树,有时这样做是明智的,您最终 re-using老树。)


1最终,Git 确实开始做与其他版本控制系统相同的 delta-compression。但是这种情况发生在提交完成一个完整的独立快照之后很久。

2这是一个近似值。有关详细信息,请参阅下一节。

3Git 保存任何目录:它只提交 文件。某个目录的存在是通过其中有一个文件来暗示的。如果需要,Git 稍后会 re-create 该目录,当检查提交并发现它必须这样做以便将文件放在那里时。


Git 如何进行提交,或者树对象中的内容

你特别提到你正在 运行ning git commit <em>filename</em>:

My idea is that I can commit a file with git commit and git fetches the relevant message from the source file ...

Git 不会根据传递给 git commit.

的参数构建树

相反,Git 有一个东西4,它称为 indexstaging areacache,这取决于调用者是谁以及他们希望强调索引的哪个方面。该索引是树对象的来源。

这意味着索引最初包含当前提交的所有文件。当你 运行 git 添加 <em>path</em> 时,Git 从 path in the work-tree into the index, overwriting the one that was before.

要为提交创建树,Git 通常只调用 git write-tree,它只是将索引内容打包为树。如果这棵树与某些现有树相同,则您 re-use 旧树;如果是新的,那就是新的;无论哪种方式,它都是 the 树,由索引中的任何内容组成。

一旦树被写入,Git 可以将它与当前提交的哈希 ID 结合起来以获得提交对象的 treeparent 行。 Git 添加你的身份和当前时间作为作者和提交者,你的日志消息作为日志消息,并写出新的提交。最后,Git将新commit的ID写入当前b运行ch名称,这样新commit就是b运行ch的新提示。

当您使用 git 提交 <em>path</em> 时,这里的情况发生了变化。现在细节取决于你是 运行 git commit --only <em>path</em> 还是 git commit --include <em>path</em>。 Git 仍然会从 一个 索引构建树。


4事实上,每个 work-tree 有一个索引。不过,默认情况下,只有一个 work-tree。但也有临时索引,我们稍后会看到。


git 提交 <em>path</em> 和临时索引

当你运行git提交<em>path</em>时,Git必须构建一个临时索引,与普通索引分开。它从复制一些东西开始。它复制的内容取决于 --only--include.

对于--only,Git通过读取当前提交的内容创建临时索引,即HEAD提交,而不是通过读取普通索引的内容.使用--include,Git通过读取普通索引的内容创建临时索引。

在临时索引中,Git 然后将给定 path 的任何条目替换为 work-tree。如果 path 不在临时索引中,Git 会将其添加为新文件。无论哪种方式,此路径现在都在临时索引中。

Git 现在使用临时索引而不是常规索引进行新提交。新提交像往常一样进入存储库,更新当前 b运行ch 名称,以便 b运行ch 的提示提交是新提交。新提交的父级像往常一样是旧的提示提交。但是既然已经提交了,Git就有点进退两难了。

索引—— 索引,正常的索引——通常应该与当前提交匹配,在“work-tree 工作”周期的开始.临时索引确实匹配新提交,因为新提交是使用临时索引进行的。但是临时索引几乎肯定在某些方面与 the 索引不同。因此,下一步行动再次取决于 --include--only:

  • 如果您使用了--include临时索引从普通索引开始。临时索引与新提交匹配。所以临时索引变成了真正的索引。

    此操作反映了正常提交:Git 使用名为 .git/index.lock 的临时锁定文件,以确保在执行所有提交工作时没有任何更改。对于不带路径参数的普通提交,临时锁文件和真正的索引除了某些时间戳外内容相同,所以Git只是将锁文件重命名为索引文件路径名,就搞定了。所以这处理了 no-path-arguments 案例和 --include with path arguments 案例。

  • 如果您使用了 --only,Git 会用复制到临时索引中的条目更新普通索引,而保留普通索引的其余条目。这样,您专门提交的文件在当前(正常)索引中的形式与它们在当前提交中的形式相同。当前(正常)索引中的所有其他文件与您之前的一样 运行 git commit:它们仍然匹配或不匹配 HEAD 提交(其 other 条目,对于未在命令行上给出的文件,都与父提交匹配),并且它们仍然匹配或不匹配 [=345= 中的文件], none 所有这些都改变了。

这对您的 prepare-commit-msg 挂钩意味着什么

与 Git 中的所有内容一样,您必须动态发现 发生了什么变化。

你根本不应该看work-tree。您可能已通过 git commit 调用(没有路径名参数),在这种情况下,使用的索引将是普通索引。您可能已通过 git commit --includegit commit --only 调用,在这种情况下,所使用的索引将是一个临时索引。

要找出索引(使用哪个索引)和 HEAD 提交之间的哪些文件不同,请使用 Git 提供的差异引擎之一。

一般来说,在您编写的任何代码中,除了您自己之外,您还应该使用 Git 调用的 管道命令。在这种情况下,所需的命令是 git diff-index。另见

使用 git diff-index -r HEAD 会将当前提交与当前索引文件中的任何内容进行比较,由 $GIT_INDEX_FILE 和由于 [=62 而导致的任何替代 work-tree 情况确定=].方便的是,您无需在此进行任何调整。但是如果用户调用了 git commit --amend,你真的应该与当前提交的父项进行比较。没有什么好的方法可以查明是否是这种情况。5

git diff-index 的输出默认为如下所示:

:100644 100644 f5debcd2b4f05c50d5e70efc95d10d95ca6372cd e736da45f71a37b46d5d46056b74070f0f3d488a M      wt-status.c

您可以使用 --name-status trim 关闭大部分 non-interesting 位,它会生成:

$ git diff-index -r --name-status HEAD
M       wt-status.c

注意状态字母后面的分隔符是一个制表符,但是如果你写一个shell形式的循环:

git diff-index -r --name-status HEAD | while read status path; do ...

你可能总体上没问题。为了使其真正可靠,请使用有趣的路径名进行测试,包括白色 space 和 glob 字符。 bash 或其他智能语言的脚本可以使用 -z 标志来更理智地编码。有关详细信息,请参阅 the documentation

请注意,文件可能会在此处 A 添加或 D 删除,而不仅仅是 M 修改。使用 git diff-index 将使您免于检查 Renamed;使用 git diff 不会,因为那会读取用户的配置,这可能会设置 diff.renames。您还应该准备好处理 Type-change,以防有人用文件替换符号 link,反之亦然。

一旦你有了一个修改过的文件列表,或者如果你愿意,可以与获取列表交错(但这更复杂——你需要保留和使用 :<mode> 的东西以获得强大的 line-by-line解码),你可以检查实际的差异。例如:

$ git diff-index --cached -p HEAD -- wt-status.c
diff --git a/wt-status.c b/wt-status.c
index f5debcd2b..e736da45f 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1,3 +1,4 @@
+
 #include "cache.h"
 #include "wt-status.h"
 #include "object.h"

说明我这里只是在文件顶部加了一个空行。 (你需要 --cached 让 Git 查看索引中的 blob 内容,而不是查看 work-tree 文件。你不需要 --cached 和初始 -r --name-status 变体,尽管包含它是无害的。这是 git diff-index 的一个烦人的特性。)

收集所有 git diff-index 输出并对其进行解析以发现您的日志消息文本后,您将准备好将新的提交日志消息写入日志消息文件。


5应该有吧。这是带有 Git 提交挂钩的主题:它们没有提供足够的信息。 Git 的更高版本可能会向挂钩添加更多参数,或设置特定的环境变量。例如,您可以在进程树中挖掘以尝试找到调用挂钩的 git commit 命令,然后查看它们的 /proc 条目或 ps 输出以找到它们的参数,但是这非常丑陋 error-prone,不太可能在 Windows.

上工作