开个玩笑,我把笔记本电脑的用户帐户命名为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:
当我的存储库设置清楚地说明我的意图时,为什么它一直这样做?我不希望乔恩因为我的代码而获得荣誉!从命令行提交具有预期的结果。
有多种可能发生的方式。 在我们了解所有这些信息之前,我们需要涵盖适当的背景信息。 还值得强调一个关键项:任何现有提交中的任何数据都不能更改,甚至不能更改一位。只要您的仓库中存在提交39c31f5aebe43cdddbe00432207e4bb2cc6a777e
,它就会继续具有相同的信息。 (请注意,这是您显示的提交的父级。 您没有显示提交本身的实际哈希 ID,所以我无法使用那个。
在非常特殊的情况下 Git-GUI(git-gui.sh
,我从不使用),它从源代码看起来好像有一个功能,使用"amend"读取HEAD
提交的作者信息并复制它。 当你选择"修改"时,它通常应该这样做(如上所述,这是一个善意的谎言),而不修改时不应该这样做。 与命令行git commit
不同,似乎没有Git-GUI旋钮来做修改,同时不保留作者。 如果它不小心将作者保留应用于所有新提交,那只是一个错误。
欲了解更多信息,请继续阅读。
背景
每个提交都有一些与之关联的元数据。 原始提交对象中有两个相关的元数据行,称为作者和提交者。 这两者通常(但不一定)相同,正如人们可以从 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>
(我已经用可能减少垃圾邮件收集替换了
@
)。 但:
$ 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]
请注意,这两个字段中的每一个实际上都有三个部分:全名、<尖括号>中的电子邮件地址和带区域偏移量的时间戳。尖括号>
当你使用git commit
进行新提交时,Git通常会将作者和提交者设置为相同的三个字符串。 但是许多 Git 命令将一些现有的提交复制到新的和改进的替换中。 根据定义,新提交具有新的和不同的哈希 ID,但旨在代替旧哈希 ID。 对于这些情况,Git 通常会保留原始作者信息,并将您(现在)设置为提交者。
作为参考,保留作者的提交复制命令与-c
或-C
/git cherry-pick
选项--amend
;git rebase
;和git am
.git am
命令旨在将通过电子邮件发送的补丁转换为提交:它接受提交以外的其他内容作为其输入,因此我们可以说它是作者保存的,但是我们必须定义作者的含义。 在这种情况下,git commit-tree
通过分析邮箱格式的邮件来猜测作者身份信息。
每个领域的机制
有一个底层 Git 命令,git commit-tree
,其他命令要么使用,要么内置到其中。 这实际上构建了提交对象,其中包含上述元数据。 它可以采用各种指令来单独设置每个字段。 如果未设置某些字段,git commit-tree
可以从某处获取默认值。
由于作者和提交者都有六个部分(姓名、电子邮件地址和时间戳),因此有六个地方可以获取特定的指令,而且很多地方(这次不是六个!)可以获取默认值。 不过,首先,让我们列举一下主要的六个。
/etc/mailname
不是采用命令行选项,而是从环境变量中获取以下六项,如文档中所述:
GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE
如果您设置了这些变量中的任何一个或全部,则会设置将进入所有后续新提交的值(直到您取消设置变量或与此环境的会话过期,取消设置变量)。
如果没有,那么文档继续说:
如果未设置(其中一些)这些环境变量,则 信息取自配置项 user.name 和 user.email,或者,如果不存在,环境变量 EMAIL,或者,如果 未设置,系统用户名和用于传出的主机名 邮件(取自
user.name
并回退到完全合格的邮件 主机名(当该文件不存在时)。
这有点善意的谎言,因为实际采用的代码路径取决于编译时选项,因此不同的 Git 安装可以有不同的自定义默认值。 但总体思路是正确的:如果你没有用各种环境变量覆盖一个或两个,Git 将首先使用你的user.email
和user.useConfigOnly
设置,对于作者和提交者。
当然,默认时间戳只是您自己的计算机对当前时间的想法。 相对较新的user.name
设置告诉现代 Git 不要猜测user.email
和/或git commit-tree
。 在旧版本的 Git 中,Git 没有猜测:如果没有设置这些,git commit
和git commit
只会失败并显示错误消息,说它不知道你是谁。
--author
前端命令也将--date
和git commit
作为参数。 这些参数可以指定要在新提交中使用的用户名、电子邮件地址和/或时间戳;GIT_AUTHOR_*
通过在提交操作期间设置git commit
变量来有效地实现这些变量。
当将--amend
前端与--reset-author
标志一起使用时(尽管它的名字,它实际上并没有改变提交;它只是创建一个新的来使用而不是当前的,这意味着这一切——--author
标志告诉前端不要保留原始提交的作者信息。
结论
如果新提交在获得正确的提交者的同时获得了错误的作者,则必须出现以下两种情况之一:
您正在使用
GIT_AUTHOR_NAME
. 停!您的环境中设置了
GIT_AUTHOR_EMAIL
和git commit --amend
。 别再设置它们了!
如果您有一些现有的提交,并且您正在尝试通过--reset-author
替换为新的和改进的提交,但它保留了其作者设置,只需添加git commit --amend
即可。 当然,这仅适用于命令行。 如果您使用的是其他内容,请了解它是否有类似的选项。
如果某些现有提交的作者错误,那么您就会陷入困境。 您可以将现有的不太好的提交复制到新的和改进的提交中,并尝试说服拥有同一存储库副本(克隆)的其他人选择并使用新的和改进的提交而不是旧的提交。这有多难显然取决于其他用户的固执程度,以及提交的位置。
位于其分支顶端且不在任何其他分支上的提交很容易换出,使用git replace
。 历史更久远的提交更加困难:您可以使用交互式变基或git filter-branch
,或者在特别丑陋的情况下,CC_43 来交换它们(有时结合这些技术)。 对旧提交的任何"更改"都必然会通过设计波及其所有后代,1因此这种更改可能会造成很大的破坏性。 然而,如果它正在"改变"——真正取代——其他人从未见过的历史,那就足够安全了。
1"错误"提交的直接子级包含错误提交的父哈希 ID。 因此,为了让孩子们参考替代品,我们也必须更换孩子。 这意味着我们必须替换他们的子级,依此类推,一直到每个受影响分支的提示提交。