1. 概述

在这篇文章中,我们将探讨修改Git(/git-guide)提交的不同方法。

2. 使用amend

我们可以通过简单的amend选项来修改最新的Git提交。它会替换最近的提交。我们可以修改提交消息并更新包含在提交中的文件。Git将考虑修改后的提交作为一个新的提交。

让我们通过一个例子尝试amend选项。为了简单起见,我们先更新一个文件,然后使用消息“Commit 1”进行提交。现在,让我们尝试使用amend选项更新提交:

git commit --amend

执行上述命令后,会打开一个编辑器以包含更改。我们更新提交消息并保存更改。关闭编辑器后,可以看到更新后的提交如下:

[master c0bc5d3] Amended Commit 1
 Date: Wed Jun 29 22:41:08 2022 +0530
 1 file changed, 1 insertion(+), 1 deletion(-)

我们也可以在提交时包含暂存区的更改。让我们添加额外的更改,并使用amend选项将它们包含在最新提交中,同时更改提交消息:

[master 0a1d571] Amended Commit 1 - Added new file
 Date: Wed Jun 29 22:41:08 2022 +0530
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 README2

如果我们只想添加暂存区的更改而不更新提交消息,可以使用no-edit选项:

git commit --amend --no-edit

因此,我们可以看到amend选项是向最近的提交添加更改的便捷方式。接下来,我们将探讨更新Git历史中较旧提交的不同方法。

3. 使用rebase(/git-merge-vs-rebase#git-rebase)

我们可以使用rebase命令将一系列提交移动到新的基线上。Git在内部为每个旧提交创建一个新的提交,并将其移动到指定的新基线上。

使用rebase命令的-i选项启动交互式会话。在这个会话中,我们可以根据需要对每个提交进行修改,使用以下命令:

  • pick(p) -> 包含特定提交
  • squash(s) -> 将提交与前一个提交合并
  • drop(d) -> 删除特定提交
  • reword(r) -> 包括提交并更新提交消息
  • edit(e) -> 包含提交,同时可以选择更新包含的文件

让我们在我们的示例中使用上述命令来更新提交历史。此时,git log显示以下提交:

commit 5742fcbe1cb14a9c4f1425eea9032ffb4c6191e5 (HEAD -> master)
Author: #####
Date:   Fri Jul 1 08:11:52 2022 +0530
    commit 5
commit e9ed266b84dd29095577ddd8f6dc7fcf5cf9db0d
Author: #####
Date:   Fri Jul 1 08:11:37 2022 +0530
    commit 4
commit 080e3ecc041b7be1757af67bf03db982135b9093
Author: #####
Date:   Fri Jul 1 08:11:18 2022 +0530
    commit 3
commit d5923e0ced1caff5874d8d41f39d197b5e1e2468
Author: #####
Date:   Fri Jul 1 08:10:58 2022 +0530
    commit 2
commit 1376dc1182a798b16dc85239ec7382e8340d5267
Author: #####
Date:   Wed Jun 29 22:41:08 2022 +0530
    Amended Commit 1 - Added new file

假设我们要改变“Amended Commit 1 - Added new file" 之后提交的修改。

让我们将上述提交设置为新基线:

git rebase -i 1376dc1182a798b16dc85239ec7382e8340d5267

这将打开一个编辑器,我们可以根据需要进行更改:

pick d5923e0 commit 2
pick 080e3ec commit 3
pick e9ed266 commit 4
pick 5742fcb commit 5
# Rebase #####..### onto #### (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

现在,让我们尝试一些命令来更新Git历史:

reword d5923e0 commit 2-updated
edit 080e3ec commit 3
squash e9ed266 commit 4
drop 5742fcb commit 5

在第一条命令后,我们会看到以下输出:

[detached HEAD 178e8eb] commit 2-alter
 Date: Fri Jul 1 08:10:58 2022 +0530
 1 file changed, 1 insertion(+)
Stopped at ######...  commit 3
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue

现在,按照Git输出指定的,我们执行git commit –amend命令并进行一些更改:

commit 3          
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Jul 1 08:11:18 2022 +0530
#
# interactive rebase in progress; onto 1376dc1
# Last commands done (2 commands done):
#    reword d5923e0 commit 2-updated
#    edit 080e3ec commit 3
# Next commands to do (2 remaining commands):
#    squash e9ed266 commit 4
#    drop 5742fcb commit 5
# You are currently splitting a commit while rebasing branch 'master' on '1376dc1'.
#
# Changes to be committed:
#       modified:   README
#       modified:   README2

关闭编辑器后,我们得到以下输出:

[detached HEAD 9433120] commit 3 - updated
 Date: Fri Jul 1 08:11:18 2022 +0530
 2 files changed, 3 insertions(+), 1 deletion(-)

在这里,我们不仅更新了提交消息,还把一个额外的文件包含在了提交中。

接下来,我们需要执行git rebase –continue命令继续下一个更新。

我们的下一步是将commit 4commit 3合并,它会打开以下编辑器:

# This is a combination of 2 commits.
# This is the 1st commit message:
commit 3 - updated
# This is the commit message #2:
commit 4
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Date:      Fri Jul 1 08:11:18 2022 +0530
# interactive rebase in progress; onto 1376dc1
# Last commands done (3 commands done):
#    edit 080e3ec commit 3
#    squash e9ed266 commit 4
# Next command to do (1 remaining command):
#    drop 5742fcb commit 5
# You are currently rebasing branch 'master' on '1376dc1'.
#
# Changes to be committed:
#       modified:   README
#       modified:   README2

在上述编辑器中添加修改后,我们得到以下输出:

[detached HEAD 917c583] commit 3 - squashed with commit 4
Date: Fri Jul 1 08:11:18 2022 +0530
2 files changed, 3 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

最后,我们请求删除commit 5,这不需要我们进一步操作。

4. 查看日志分析

完成上述步骤后,我们可以看到git log的输出:

commit 917c583d5bb02803ee43cf87a2143f201c97bbe8 (HEAD -> master)
Author: #######
Date:   Fri Jul 1 08:11:18 2022 +0530
commit 3 - squashed with commit 4
commit 4
commit 178e8ebec178c166d1c9def2d680f41933eba29b
Author: #######
Date:   Fri Jul 1 08:10:58 2022 +0530
commit 2-alter
commit 1376dc1182a798b16dc85239ec7382e8340d5267
Author: #######
Date:   Wed Jun 29 22:41:08 2022 +0530
Amended Commit 1 - Added new file

这里我们可以观察到几点:

  • commit 5已被删除
  • commit 4commit 3合并
  • commit 2的消息已更新

在最终的日志中,我们可以看到提交的ID已经改变。这是因为Git已经替换了之前的提交,并用带有修改的新提交取代了它们。

我们可以使用reflog命令查看与当前HEAD相关的引用日志。它包括所有与Git提交相关的更新历史。

让我们使用reflog命令来观察我们示例的Git历史:

917c583 (HEAD -> master) HEAD@{0}: rebase -i (finish): returning to refs/heads/master
917c583 (HEAD -> master) HEAD@{1}: rebase -i (squash): commit 3 - squashed with commit 4
9433120 HEAD@{2}: commit (amend): commit 3 - updated
f4e8340 HEAD@{3}: commit (amend): commit 3 - updated
fd048e1 HEAD@{4}: commit (amend): commit 3 - updated
39b2f1b HEAD@{5}: commit (amend): commit 3 - updated
f79cbfb HEAD@{6}: rebase -i (edit): commit 3
178e8eb HEAD@{7}: rebase -i (reword): commit 2-alter
d5923e0 HEAD@{8}: rebase -i: fast-forward
1376dc1 HEAD@{9}: rebase -i (start): checkout 1376dc1182a798b16dc85239ec7382e8340d5267
5742fcb HEAD@{10}: commit: commit 5
e9ed266 HEAD@{11}: commit: commit 4
080e3ec HEAD@{12}: commit: commit 3
d5923e0 HEAD@{13}: commit: commit 2
1376dc1 HEAD@{14}: commit (amend): Amended Commit 1 - Added new file

5. 总结

在这篇文章中,我们探讨了改变Git历史的不同方法。然而,在使用这些选项时,我们应该谨慎行事,因为这可能会导致内容丢失。