git reset -- 恢复历史版本
通过前面学习的操作,我们已经学会如何在实现功能后进行提交,积累提交日志作为历史记录,借此不断培育一款软件。
Git 的另一个特征便是可以灵活操作历史版本。借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。
在这里,为了让各位熟悉对历史版本的操作,我们先恢复历史版本,创建一个名为 fix-B 的特性分支(图1)。
图1: 恢复历史,创建 fix-B 分支
恢复到创建 feature-A 分支前
让我们先恢复到上一篇文章(Git分支操作总结)feature-A 分支创建之前,创建一个名为 fix-B 的特性分支。
要让仓库的 HEAD、暂存区、当前工作树回复道之顶状态,需要用到 git reset --hard
命令。只要提供目标时间点的hash值,就可以完全恢复至该时间点的状态。让我们执行下面的命令。
注:hash值可以通过 git log 命令查看。
$ git reset --hard 7b42faa9bc69c6c025c42273fa331963f85b4625
HEAD is now at 7b42faa Add index
我们已经成功回复道特性分支(feature-A)创建之前的状态,由于所有文件都恢复到了指定hash值对应的时间点上,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 2a31d63] Fix B
1 file changed, 2 insertions(+)
现在的状态如图2所示。接下来我们的目标是图3中所示的状态,即主干分支合并 feature-A 分支的修改后,又合并了 fix-B 的修改。
图2:当前 fix-B 分支的状态
图3:fix-B 分支的下一个目标
推进至 feature-A 分支合并后的状态
首先恢复到 feature-A 分支合并后的状态。不妨称这一操作叫做“推进历史”。
git log
命令只能查看以当前状态为终点的历史日志。所以这里要是使用 git reflog
命令,查看当前仓库的操作日志。在日志中找出恢复历史之前的hash值,通过 git reset --hard
命令恢复到恢复历史前的状态。
首先执行 git reflog
命令,查看当前仓库执行过的操日志。
$ git reflog
2a31d63 HEAD@{0}: commit: Fix B
7b42faa HEAD@{1}: checkout: moving from master to fix-B
7b42faa HEAD@{2}: reset: moving to 7b42faa9bc69c6c025c42273fa331963f85b4625
fb565d4 HEAD@{3}: merge feature-A: Merge made by the 'recursive' strategy.
7b42faa HEAD@{4}: checkout: moving from feature-A to master
40a049f HEAD@{5}: commit: Add feature-A
7b42faa HEAD@{6}: checkout: moving from master to feature-A
7b42faa HEAD@{7}: commit: Add index
37f0170 HEAD@{8}: commit (initial): First commit
在日志中,我们可以看到 commit
、checkout
、reset
、merge
等 Git 命令的执行记录。只要不进行 Git 的 GC(Garbage Collection,垃圾回收),就可以通过日志随意调取近期的历史状态,就像给时间机器指定一个时间点,在过去未来中自由穿梭一般。即便开发者错误执行了 Git 操作,基本也都可以利用 git reflog 命令恢复到原先状态。
从上面数第四行表示 feature-A 特性分支合并后的状态,对应 hash 值为 fb565d4
。我们将 HEAD、暂存区、工作树恢复到这个时间点的状态。
$ git checkout master
$ git reset --hard fb565d4
HEAD is now at fb565d4 Merge branch 'feature-A'
之前我们使用 git reset --hard 命令恢复了历史,这里有再次通过他恢复到了恢复前的历史状态。当前的状态如图4所示。
图4:恢复历史后的状态
消除冲突
现在只要合并 fix-B 分支,就可以得到我们想要的状态。让我们赶快进行合并操作。
$ git merge --no-ff fix-B
Auto-merging README.md
CONFLICT (content): Merge conflict in 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"
[master 5e3a33b] 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.
#
# Date: Mon Apr 18 17:29:46 2016 +0800
#
# On branch master
# Changes to be committed:
# modified: README.md
#
编辑器中显示的内容如上所示,其中包含之前的提交信息。请将提交信息的部分修改为 Merge branch 'fix-B',然后保存文件,关闭编辑器。
[master 906c818] Merge branch 'fix-B'
Date: Mon Apr 18 17:29:46 2016 +0800
随后会显示上面这条结果。现在执行 git log --graph
命令,可以看到提交到日志的响应内容已被修改。
$ git log --graph
* commit 906c818ab0e4e6b234f5c9def09a0a9bafcae55e
|\ Merge: fb565d4 2a31d63
| | Author: Frank <zhwuzhuo@gmail.com>
| | Date: Mon Apr 18 17:29:46 2016 +0800
| |
| | Merge branch 'fix-B'
| |
| * commit 2a31d639d78d3f5123a63fe73ae25d22cbc6321f
| | Author: Frank <zhwuzhuo@gmail.com>
| | Date: Mon Apr 18 16:00:10 2016 +0800
| |
| | Fix B
| |
* | commit fb565d4ef3d8752989703c6429765b93999fef6c
|\ \ Merge: 7b42faa 40a049f
| |/ Author: Frank <zhwuzhuo@gmail.com>
|/| Date: Mon Apr 18 15:18:55 2016 +0800
| |
| | Merge branch 'feature-A'
| |
| * commit 40a049f709b8974fb25bc3f5396546c8f70b8180
|/ Author: Frank <zhwuzhuo@gmail.com>
| Date: Mon Apr 18 15:17:49 2016 +0800
|
| Add feature-A
|
* commit 7b42faa9bc69c6c025c42273fa331963f85b4625
| Author: Frank <zhwuzhuo@gmail.com>
| Date: Mon Apr 18 15:16:03 2016 +0800
|
| Add index
|
* commit 37f01708491d8cb2c410d2b9edc8db1bbc39bb8e
Author: Frank <zhwuzhuo@gmail.com>
Date: Mon Apr 18 15:14:42 2016 +0800
First commit
git rebase -i ---- 压缩历史
在合并特征分支之前,如果发现已经提交的内容中有些许拼写错误等,不妨提交一个修改,然后将这个修改包含到前一个提交中,压缩成一个历史记录。这是个会经常用到的技巧,让我门来实际操作体会一下。
创建 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 fc16a63] Add feature-C
1 file changed, 1 insertion(+)
修正拼写错误
现在来修正刚才预留的拼写错误。请各位自行修正 README.md 文件的内容,修正后的差别如下所示。
$ git diff
diff --git a/README.md b/README.md
index 11516d7..b95b1e4 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 6fbaf88] 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 fc16a63 Add feature-C
pick 6fbaf88 Fix typo
# Rebase 906c818..6fbaf88 onto 906c818 (2 command(s))
#
# 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
# d, drop = remove commit
#
# 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
我门将 6fbaf88 的 Fix typo 的历史记录压缩到 fc16a63 的 Add feature-C 里。按照下图所示,将 6fbaf88 左侧的 pick
部分删除,改写为 fixup
。
pick fc16a63 Add feature-C
fixup 6fbaf88 Fix typo
保存编辑器里的内容,关闭编辑器。
[detached HEAD 1b18032] Add feature-C
Date: Mon Apr 18 17:45:12 2016 +0800
1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/feature-C.
系统显示 rebase 成功。也就是以下面这两个提交作业对象,将“Fix typo”的内容合并到了上一个提交“Add feature-C”中,改写成了一个新的提交。
- fc16a63 Add feature-C
- 6fbaf88 Fix typo
现在再查看提交日志时会发现 Add feature-C 的hash值已经不是 fc16a63
了,这证明提交已经被更改。
$ git log --graph
* commit 1b18032f91a7e6c551473ef35ed7d8b145b505f7
| Author: Frank <zhwuzhuo@gmail.com>
| Date: Mon Apr 18 17:45:12 2016 +0800
|
| Add feature-C
|
* commit 906c818ab0e4e6b234f5c9def09a0a9bafcae55e
|\ Merge: fb565d4 2a31d63
| | Author: Frank <zhwuzhuo@gmail.com>
| | Date: Mon Apr 18 17:29:46 2016 +0800
| |
| | Merge branch 'fix-B'
| |
| * commit 2a31d639d78d3f5123a63fe73ae25d22cbc6321f
| | Author: Frank <zhwuzhuo@gmail.com>
| | Date: Mon Apr 18 16:00:10 2016 +0800
| |
| | 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 分支。开发进展顺利。
文章内容参考:《GitHub入门与实践》
[…] Git基本操作总结 Git分支操作总结 Git更改提交的操作 […]