为什么 Git GUI 一直叫我 Jon Skeet?

Why does Git GUI keep calling me Jon Skeet?

开个玩笑,我将笔记本电脑的用户帐户命名为 Jon Skeet。我已经配置了我的每个存储库选项来称呼我 wizzwizz4,但是当我查看我的提交时,我看到了这个:

Author: Jon Skeet <jon@myLaptop>  2018-12-21 22:07:11
Committer: wizzwizz4 <wizzwizz4@users.noreply.github.com>  2018-12-21 22:12:07
Parent: 39c31f5aebe43cdddbe00432207e4bb2cc6a777e (Initial commit)
Branches: master
Follows: 
Precedes: 

当我的存储库设置清楚地表明了我的意图时,为什么它会继续这样做?我不想让 Jon 因 我的 代码而受到赞扬!从命令行提交具有预期的结果。

有多种发生方式。在我们了解所有这些之前,我们需要介绍适当的背景信息。还值得强调一个关键项目:任何现有提交中的数据都不能更改,甚至不能更改任何一位。只要提交 39c31f5aebe43cdddbe00432207e4bb2cc6a777e 存在于您的存储库中,它就会继续拥有相同的信息。 (请注意,这是您显示的提交的 parent。您没有显示提交本身的实际哈希 ID,所以我不能使用那个。)

在 Git-GUI 的非常特殊的情况下(git-gui.sh, which I never use), it looks from the source 好像有一个功能,其中使用 "amend" 读取 HEAD 提交的作者信息并复制它。它通常 应该 在 select "amending" 时这样做(如上文和下文所述,这是一个善意的谎言)并且 不应该 在 not-amending 时执行此操作。与 command-line git commit 不同,似乎没有 Git-GUI 旋钮可以在不保留作者的情况下进行修改。如果它不小心应用了作者保留 所有新 提交,这只是一个错误。

有关更多信息,请继续阅读。

背景

每个提交都有一些与之关联的元数据。原始提交 object 中有两个相关的元数据行,称为 authorcommitter。这两者通常但不一定相同,正如可以从 Git 存储库中针对 Git 本身的各种提交中看到的那样。例如:

$ git cat-file -p 5d826e972970a784bd7a7bdf587512510097b8c7
tree c790c47fe551d5ed812cfefdac243eb972c1fde3
parent b5796d9a3263b26a8ef32eeca76b3c1d62fcedc5
author Junio C Hamano <gitster pobox.com> 1544328981 +0900
committer Junio C Hamano <gitster pobox.com> 1544328981 +0900

Git 2.20

Signed-off-by: Junio C Hamano <gitster pobox.com>

(我已将 @ 替换为 </code> 以可能减少垃圾邮件收集)。但是:</p> <pre><code>$ git cat-file -p 6fcbad87d476d7281832af843dd448c94673fbfc tree aa05bc7af6e92f3db5d5d738adf0d0b1b3dd23b6 parent b00bf1c9a8dd5009d5102aef7af9e2b886b1e5ad author Johannes Sixt <j6t kdbg.org> 1543858489 +0100 committer Junio C Hamano <gitster pobox.com> 1543891852 +0900 rebase docs: fix incorrect format of [... snip]

请注意,这两个字段实际上都包含三个部分:全名、<尖括号> 中的电子邮件地址和 timestamp-with-zone-offset。

当您使用 git commit 进行新提交时,Git 通常将作者和提交者设置为相同的三个字符串 。但是许多 Git 命令将一些现有的提交复制到 new-and-improved 替换。根据定义,新提交有一个新的和不同的散列 ID,但要使用 而不是 旧的。对于这些情况,Git 通常会保留原始的 author 信息并将您(现在)设置为 committer.

作为参考,author-preserving commit-copying 命令是 git commit--amend-c / -C 选项; git cherry-pick;和 git rebasegit am 命令旨在将通过电子邮件发送的补丁转换为提交:它采用 commit 以外的其他内容作为其输入,因此我们可以说它是 author-preserving,但是随后我们必须定义 author 的意思。在这种情况下,git am 通过解析 mailbox-formatted 消息来猜测作者身份信息。

每个字段的机制

有一个底层 Git 命令,git commit-tree,其他命令使用或内置。这实际上构建了包含上述元数据的提交 object。它可以采用各种指令来单独设置每个字段。如果某些字段设置,git commit-tree可以从某处获取默认值。

由于作者和提交者分别有六个部分——名称、email-address 和 time-stamp——所以有六个地方可以获得特定指令,而且很多地方——这次不是六个! - 获得默认值。不过,首先,让我们列举一下主要的六个。

git commit-tree 不是采用 command-line 选项,而是采用 环境变量 中的这六项,如 the documentation 中所述:

GIT_AUTHOR_NAME
GIT_AUTHOR_EMAIL
GIT_AUTHOR_DATE
GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL
GIT_COMMITTER_DATE

如果您设置这些变量中的任何或所有,这将设置将进入所有后续新提交的值(直到您取消设置变量或您与此环境的会话过期,取消设置变量)。

如果不是,那么文档继续说:

In case (some of) these environment variables are not set, the information is taken from the configuration items user.name and user.email, or, if not present, the environment variable EMAIL, or, if that is not set, system user name and the hostname used for outgoing mail (taken from /etc/mailname and falling back to the fully qualified hostname when that file does not exist).

这是一个善意的谎言,因为实际采用的代码路径取决于 compile-time 选项,因此不同的 Git 安装可以有不同的自定义默认值。但总体思路是正确的:Git 将首先使用你的 user.nameuser.email 设置,对于作者和提交者,如果你没有用各种环境变量覆盖一个或两个。

当然,默认时间戳只是您自己的计算机对当前时间的想法。相对较新的 user.useConfigOnly 设置告诉现代 Git 不要 猜测 user.name and/or user.email。在旧版本的 Git 中,Git 没有猜测:如果没有设置这些,git commit-treegit commit 只会失败并显示一条错误消息,说它不知道你是谁

git commit front-end 命令也接受 --author--date 作为参数。这些参数可以指定在新提交中使用的用户名、电子邮件地址、and/or time-stamp; git commit 通过在提交操作期间设置 GIT_AUTHOR_* 变量来有效地实现这些。

当使用带有 --amend 标志的 git commit 前端时——尽管它是的,实际上并没有改变一个提交;它只是创建一个新的使用 而不是 当前的,所有这意味着 - --reset-author 标志告诉前端不要保留原始提交的作者信息。

结论

如果 new 提交得到了错误的作者,同时得到了正确的提交者,则必须是以下两种情况之一:

  • 您正在使用 --author。停止!

  • 您的环境中设置了 GIT_AUTHOR_NAMEGIT_AUTHOR_EMAIL。停止设置它们!

如果您有一些现有的提交,并且您试图通过 git commit --amend 替换为新的和改进的提交,但它保留了其作者设置,只需添加 --reset-author。当然,这仅适用于命令行。如果您使用的是其他东西,请查看它是否有类似的选项。

如果某些 现有 提交有错误的作者,你就坚持下去了。您可以复制现有的 not-so-good,提交到新的和改进的提交,并尝试说服拥有同一存储库副本(克隆)的其他人选择并使用 new-and-improved 提交而不是旧的。 那个有多难显然取决于其他用户的顽固程度,以及提交的位置。

位于其分支顶端且不在任何其他分支上的提交很容易换出,使用 git commit --amend。历史上更早的提交更加困难:您可以使用交互式变基,或 git replace,或者,在特别难看的情况下,git filter-branch 来交换它们(有时结合这些技术)。任何对旧提交的 "change" 都必然会在设计上影响其所有后代,1 因此这种更改可能具有相当大的破坏性。然而,如果它是 "changing"——真正地取代——没有人见过的历史,它就足够安全了。


1"bad" 提交的直接 children 包含错误提交的 parent 哈希 ID。因此,要让 children 引用替换,我们必须 也替换 children 。这意味着我们必须随后替换 他们的 children,依此类推,一直到每个受影响分支的提示提交。