← 返回文章

五、更改提交的操作

回溯历史版本 git reset 通过前面学习的操作,已经知道了如何在实现功能后进行提交,不断地提交日志作

回溯历史版本 git reset

通过前面学习的操作,已经知道了如何在实现功能后进行提交,不断地提交日志作为历史记录,借此不断培育一款软件。

Git 的另一特征便是可以灵活操作历史版本。借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。

  1. 回溯到创建 feature-A 分支前

要让仓库的 HEAD、暂存区、当前工作树回溯到指定状态,需要用到 git rest --hard 命令。只要提供目标时间点的哈希值,就可以完全恢复至该时间点的状态。

$ git reset --hard fd0cbf0d4a25f747230694d95cac1be72d33441d
HEAD is now at fd0cbf0 Add index

已经成功回溯到特性分支(feature-A)创建之前的状态。由于所有文件都回溯到了指定哈希值对应的时间点上,README.md 文件的内容也恢复到了当时的状态。

  1. 创建 fix-B 分支

创建特性分支(fix-B)

$ git checkout -b fix-B
Switched to a new branch 'fix-B'

在 README.md 文件中添加一行文字。

# Git教程
 - fix-B

然后直接提交 README.md 文件。

$ git add README.md
$ git commit -m "Fix B"
[fix-B 4096d9e] Fix B
 1 file changed, 2 insertions(+)

使用 git log --graph 可以查看当前状态树

  1. 推进至 feature-A 分支合并后的状态

首先恢复到 feature-A 分支合并后的状态。不妨称这一操作为“推进 历史”。 git log 命令只能查看以当前状态为终点的历史日志。所以这里要使用 git reflog命令,查看当前仓库的操作日志。

在日志中找出回溯历史之前的哈希值,通过 git reset --hard命令恢复到回溯历 史前的状态。

首先执行 git reflog 命令,查看当前仓库执行过的操作的日志。

$ git reflog
4096d9e HEAD@{0}: commit: Fix B
fd0cbf0 HEAD@{1}: checkout: moving from master to fix-B
fd0cbf0 HEAD@{2}: reset: moving to fd0cbf0d4a25f747230694d95cac1be72d33441d
83b0b94 HEAD@{3}: merge feature-A: Merge made by the 'recursive' strategy.
fd0cbf0 HEAD@{4}: checkout: moving from feature-A to master
8a6c8b9 HEAD@{5}: checkout: moving from master to feature-A
fd0cbf0 HEAD@{6}: checkout: moving from feature-A to master
8a6c8b9 HEAD@{7}: commit: Add feature-A
fd0cbf0 HEAD@{8}: checkout: moving from master to feature-A
fd0cbf0 HEAD@{9}: commit: Add index
9f129ba HEAD@{10}: commit (initial): First commit

在日志中,可以看到 commit、checkout、reset、merge 等 Git 命令的执行记录。

只要不进行 Git 的 GC(Garbage Collection,垃圾回收),就可以通过日志随意调取近期的历史状态,就像给时间机器指定一个时间点,在过去未来中自由穿梭一般。

即便开发者错误执行了 Git 操作,基本也都可以利用 git reflog 命令恢复到原先的状态,所以请各位读者务必牢记本部分。

从上面数第四行表示 feature-A 特性分支合并后的状态,对应哈希值为 83b0b94A。

现在将 HEAD、暂存区、工作树恢复到这个时间点的状态。

$ git checkout master
$ git reset --hard 83b0b94
HEAD is now at 83b0b94 Merge branch 'feature-A'

之前使用 git reset --hard命令回溯了历史,这里又再次通过它恢复到了回溯前的历史状态。

消除冲突

$ git merge --no-ff fix-B
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Recorded preimage for 'README.md'
Automatic merge failed; fix conflicts and then commit the result.

这时,系统告诉我们 README.md 文件发生了冲突(Conflict)。系统在合并 README.md 文件时,feature-A 分支更改的部分与本次想要合并的 fix-B 分支更改的部分发生了冲突。

不解决冲突就无法完成合并,所以打开 README.md 文件,解决这个冲突。

  1. 查看冲突部分并将其解决用编辑器打开 README.md 文件,就会发现其内容变成了下面这个样子。
# Git教程
<<<<<<< HEAD
 - feature-A
=======
 - fix-B
>>>>>>> fix-B

======= 以上的部分是当前 HEAD 的内容,以下的部分是要合并 的 fix-B 分支中的内容。我们在编辑器中将其改成想要的样子。

# Git教程
 - feature-A
 - fix-B

如上所示,本次修正让 feature-A 与 fix-B 的内容并存于文件之中。

但是在实际的软件开发中,往往需要删除其中之一,所以各位在处理冲突时,务必要仔细分析冲突部分的内容后再行修改。

  1. 提交解决后的结果

冲突解决后,执行 git add命令与 git commit命令。

$ git add README.md
$ git commit -m "Fix conflict"
Recorded resolution for 'README.md'.
[master 6a97e48] Fix conflict

由于本次更改解决了冲突,所以提交信息记为 "Fix conflict"。

修改提交信息 git commit --amend

要修改上一条提交信息,可以使用 git commit --amend 命令。

之前将上一条提交信息记为了 "Fix conflict",但它其实是 fix-B 分支的合并,解决合并时发生的冲突只是过程之一,这样标记实在不妥。

于是,要修改这条提交信息。

$ git commit --amend

执行上面的命令后,编辑器就会启动。

Fix conflict
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
# (use "git reset HEAD^1 <file>..." to unstage)
#
# modified: README.md
#

编辑器中显示的内容如上所示,其中包含之前的提交信息。请将 提交信息的部分修改为 Merge branch 'fix-B',然后保存文件,关闭编 辑器。

[master 2e7db6f] Merge branch 'fix-B'

随后会显示上面这条结果。现在执行 git log --graph 命令, 可以看到提交日志中的相应内容也已经被修改。

$ git log --graph
* commit 2e7db6fb0b576e9946965ea680e4834ee889c9d8
| Merge: 83b0b94 4096d9e
| | Author: hirocaster <hohtsuka@gmail.com>
| | Date: Sun May 5 16:58:27 2013 +0900
| |
| | Merge branch 'fix-B'
| |
| * commit 4096d9e856995a1aafa982aabb52bfc0da656b74
| | Author: hirocaster <hohtsuka@gmail.com>
| | Date: Sun May 5 16:50:31 2013 +0900
| |
| | Fix B
| |
* | commit 83b0b94268675cb715ac6c8a5bc1965938c15f62
|  Merge: fd0cbf0 8a6c8b9
| |/ Author: hirocaster <hohtsuka@gmail.com>
|/| Date: Sun May 5 16:37:57 2013 +0900
| |
| | Merge branch 'feature-A'
| |
| * commit 8a6c8b97c8962cd44afb69c65f26d6e1a6c088d8
|/ Author: hirocaster <hohtsuka@gmail.com>
| Date: Sun May 5 16:22:02 2013 +0900
|
| Add feature-A
|
* commit fd0cbf0d4a25f747230694d95cac1be72d33441d
| Author: hirocaster <hohtsuka@gmail.com>
| Date: Sun May 5 16:10:15 2013 +0900
|
| Add index
|
* commit 9f129bae19b2c82fb4e98cde5890e52a6c546922
 Author: hirocaster <hohtsuka@gmail.com>
 Date: Sun May 5 16:06:49 2013 +0900
 First commit

压缩历史

在合并特性分支之前,如果发现已提交的内容中有些许拼写错误等,不妨提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录。这是个会经常用到的技巧。

  1. 创建 feature-C 分支

首先,新建一个 feature-C 特性分支。

$ git checkout -b feature-C
Switched to a new branch 'feature-C'

作为 feature-C 的功能实现,我们在 README.md 文件中添加一行 文字,并且故意留下拼写错误,以便之后修正。

# Git教程
 - feature-A
 - fix-B
 - faeture-C

提交这部分内容。这个小小的变更就没必要先执行 git add 命令 再执行 git commit 命令了,我们用 git commit -am命令来一次 完成这两步操作。

$ git commit -am "Add feature-C"
[feature-C 7a34294] Add feature-C
 1 file changed, 1 insertion(+)

修正拼写错误 现在来修正刚才预留的拼写错误。请各位自行修正 README.md 文 件的内容,修正后的差别如下所示。

$ git diff
diff --git a/README.md b/README.md
index ad19aba..af647fd 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,4 @@
 - feature-A
 - fix-B
- - faeture-C
+ - feature-C

然后进行提交。

$ git commit -am "Fix typo"
[feature-C 6fba227] Fix typo
 1 file changed, 1 insertion(+), 1 deletion(-)

错字漏字等失误称作 typo,所以我们将提交信息记为 "Fix typo"。

实际上,我们不希望在历史记录中看到这类提交,因为健全的历史记录并不需要它们。如果能在最初提交之前就发现并修正这些错误,也就不会出现这类提交了。

  1. 更改历史

因此,需要更改历史。将 "Fix typo" 修正的内容与之前一次的提交合并,在历史记录中合并为一次完美的提交。为此,我们要用到git rebase 命令。

$ git rebase -i HEAD~2

用上述方式执行 git rebase 命令,可以选定当前分支中包含 HEAD(最新提交)在内的两个最新历史记录为对象,并在编辑器中打开。

pick 7a34294 Add feature-C
pick 6fba227 Fix typo
# Rebase 2e7db6f..6fba227 onto 2e7db6f
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# 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

我们将 6fba227 的 Fix typo 的历史记录压缩到 7a34294 的 Add feature-C里。

按照下图所示,将 6fba227 左侧的 pick 部分删除,改写为 fixup。

pick 7a34294 Add feature-C
fixup 6fba227 Fix typo

保存编辑器里的内容,关闭编辑器。

[detached HEAD 51440c5] Add feature-C
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/feature-C.

系统显示 rebase 成功。也就是以下面这两个提交作为对象,将 "Fix typo"的内容合并到了上一个提交 "Add feature-C"中,改写成了一个新的提交。

● 7a34294 Add feature-C ● 6fba227 Fix typo

现在再查看提交日志时会发现 Add feature-C 的哈希值已经不是 7a34294 了,这证明提交已经被更改。

$ git log --graph
* commit 51440c55b23fa7fa50aedf20aa43c54138171137
| Author: hirocaster <hohtsuka@gmail.com>
| Date: Sun May 5 17:07:36 2013 +0900
|
| Add feature-C
|
* commit 2e7db6fb0b576e9946965ea680e4834ee889c9d8
| Merge: 83b0b94 4096d9e
| | Author: hirocaster <hohtsuka@gmail.com>
| | Date: Sun May 5 16:58:27 2013 +0900
| |
| | Merge branch 'fix-B'
| |
| * commit 4096d9e856995a1aafa982aabb52bfc0da656b74
| | Author: hirocaster <hohtsuka@gmail.com>
| | Date: Sun May 5 16:50:31 2013 +0900
| |
| | Fix B
| |
省略

这样一来,Fix typo 就从历史中被抹去,也就相当于 Add feature-C 中从来没有出现过拼写错误。这算是一种良性的历史改写。

合并至 master 分支feature-C 分支的使命告一段落,将它与 master 分支合并。

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff feature-C
Merge made by the 'recursive' strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)

master 分支整合了 feature-C 分支。开发进展顺利。

END