不可能更改 任何 提交。这包括在它被推送之前。了解这一点很重要——你需要知道 git commit --amend 是一个谎言的原因——是 git commit --amend 在本地所做的,可以在将提交推送到另一个 Git 存储库时在此处完成.
git commit --amend 的(小)谎言是它根本没有更改提交。它只是似乎这样做。它真正做的是进行新的和改进的替换提交。
您可以随时执行此操作。你只需要知道每个提交都有编号——每个提交都有一个唯一的编号,这是它的hash ID——并且Git finds 以一种向后(甚至可能是 backassward)的方式提交,通过 分支名称 或其他名称为您定位 最近的 提交,然后从那里向后工作。
想象一个简单的提交链,以您最近的提交结束,其哈希是一些看起来很丑的随机哈希 ID,我们将称之为 H。以下是 Git 如何查看和发现这些提交:
... <-F <-G <-H <--your-branch
您的分支名称“指向”(包含哈希 ID)提交 H。提交H 本身包含先前提交G 的哈希ID。 (提交H 还包含每个文件的完整快照,尽管我们不会在这里查看。)
提交G,作为一个提交,由一些看起来很丑的随机哈希ID编号,并具有快照和元数据,因此向后指向更早的提交F。那是一个提交,所以它被编号并且具有相同类型的东西并且再次向后指向,等等。
现在,如果您在提交 H 中有一个小错字并运行 git commit --amend,Git 所做的就是创建一个新的提交。新的提交与H 完全一样,除了两个方面:
- 它有不同的哈希ID,所以我们称它为
I;和
- 已纠正错字。
Commit H 仍然存在于您的存储库中。它只是不再使用了。 H 仍然指向 G,但新提交 I 也是如此:
H ???
/
... <-F <-G <-I <--your-branch
Git 更新您的 分支名称 以指向新的提交 I。这有效地“将H 踢出分支”,因为 Git finds 通过从末尾开始并向后工作来提交。
现在,如果您运行 git push,您所做的是将提交 H 发送到另一个 Git 存储库,然后让他们更新一个他们的分支名称— 可能与您在存储库中使用的名称相同 — 指向现在共享的提交:
...--F--G--H <--their-branch
如果你做出一个新的提交 I 这是一个新的和改进的替换 H 并且只需将它与常规的git push 一起发送给他们,他们就会接受这个提交并将其暂时放入他们的存储库中:
H <--their-branch
/
...--F--G
\
I
然后您的 Git 要求他们的 Git 将 他们的 分支名称也设置为指向 I。
问题就在这里,因为当你这样做时,他们会检查:如果我将分支名称更新为指向 I,我是否会丢失任何提交? 当然他们这样做: 他们在分支末尾丢失了提交H。这就是你希望他们做的事——毕竟这就是 你的Git 对 你的git commit --amend 所做的事——但 他们没有不知道。他们现在只知道他们确实有H,而你现在要求他们放弃它。
你可以使用:
git push --force
或:
git push --force-with-lease
向他们发送命令,而不是礼貌的请求,告诉他们绝对应该将他们的名字指向I(常规--force) ,或者你认为他们的名字现在指向H,如果是这样,他们应该让它指向I而不是(--force-with-lease)。如果他们会从你那里获取这样的命令——大多数托管网站,比如 GitHub、Bitbucket 和 GitLab 都有他们在 Git 上添加的精美配置来控制这些东西——那么这让你毕竟可以用 I 替换 H。
为什么你可能会使用--force-with-lease
请注意,如果托管网站正在接受来自其他人的git push请求,他们现在可能已经:
H--J--K <--their-branch
/
...--F--G
\
I
因此,您将分支设置为指向 I 的命令不仅会删除您的提交 H,还会删除其他两个额外的提交 J-K,从其分支的末尾。
使用git push --force-with-lease 将您的新提交I 发送给他们,但也会将您认为他们在其分支上的最终提交的哈希ID H 发送给他们。如果您的信念是错误的,那么您的条件命令(即他们必须更改名称以指向 I)会收到拒绝,说您的 Git 现在已过时,您需要先运行 git fetch。
如果这种情况不能发生,您就不需要--force-with-lease。非常旧的 Git 版本也没有 --force-with-lease,但所有现代版本都有,即使没有必要,保持安全检查通常是最明智的,因为“习惯”可能会变成“坏习惯”习惯”随着时间的推移。