五、更改提交的操作
回溯历史版本 git reset 通过前面学习的操作,已经知道了如何在实现功能后进行提交,不断地提交日志作
回溯历史版本 git reset
通过前面学习的操作,已经知道了如何在实现功能后进行提交,不断地提交日志作为历史记录,借此不断培育一款软件。
Git 的另一特征便是可以灵活操作历史版本。借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。
- 回溯到创建 feature-A 分支前
要让仓库的 HEAD、暂存区、当前工作树回溯到指定状态,需要用到 git rest --hard 命令。只要提供目标时间点的哈希值,就可以完全恢复至该时间点的状态。
$ git reset --hard fd0cbf0d4a25f747230694d95cac1be72d33441d
HEAD is now at fd0cbf0 Add index
已经成功回溯到特性分支(feature-A)创建之前的状态。由于所有文件都回溯到了指定哈希值对应的时间点上,README.md 文件的内容也恢复到了当时的状态。
- 创建 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 可以查看当前状态树
- 推进至 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 文件,解决这个冲突。
- 查看冲突部分并将其解决用编辑器打开 README.md 文件,就会发现其内容变成了下面这个样子。
# Git教程
<<<<<<< HEAD
- feature-A
=======
- fix-B
>>>>>>> fix-B
======= 以上的部分是当前 HEAD 的内容,以下的部分是要合并 的 fix-B 分支中的内容。我们在编辑器中将其改成想要的样子。
# Git教程
- feature-A
- fix-B
如上所示,本次修正让 feature-A 与 fix-B 的内容并存于文件之中。
但是在实际的软件开发中,往往需要删除其中之一,所以各位在处理冲突时,务必要仔细分析冲突部分的内容后再行修改。
- 提交解决后的结果
冲突解决后,执行 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
压缩历史
在合并特性分支之前,如果发现已提交的内容中有些许拼写错误等,不妨提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录。这是个会经常用到的技巧。
- 创建 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"。
实际上,我们不希望在历史记录中看到这类提交,因为健全的历史记录并不需要它们。如果能在最初提交之前就发现并修正这些错误,也就不会出现这类提交了。
- 更改历史
因此,需要更改历史。将 "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 分支。开发进展顺利。