更改提交的操作
git reset —— 回溯历史版本
通过前面学习的操作,我们已经学会如何在实现功能后进行提交,累积提交日志作为历史记录,借此不断培育一款软件。
Git的另一特征便是可以灵活操作历史版本。借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。
在这里,为了让各位熟悉对历史版本的操作,我们先回溯历史版本,创建一个名为fix-B的特性分支

回溯到创建feature-A分支前
让我们先回溯到feature-A分支创建之前,创建一个名为fix-B的特性分支。
要让仓库的HEAD、暂存区、当前工作树回溯到指定状态,需要用到git reset --hard
。只要提供目标时间点的哈希值,就可以完全恢复至该时间点的状态。
哈希值在每个环境中各不相同,可以使用git log
查看。
➜ git-test git:(master) git log
commit c00815d1247d0ef656b3ea31c8cd2079e04bf52b
Merge: 7c1e8df ad5a4ba
Author: Vergil <vergil@vip.163.com>
Date: Mon Sep 21 00:21:55 2015 +0800
Merge branch 'feature-A'
commit ad5a4baec2ac4125c489b2461cb203ec14cd2658
Author: Vergil <vergil@vip.163.com>
Date: Sun Sep 20 23:48:30 2015 +0800
Add feature-A
commit 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
Author: Vergil <vergil@vip.163.com>
Date: Sun Sep 20 21:20:27 2015 +0800
Add index
commit 69ca7dddd02bca848adccd5d00a62903081de979
Author: Vergil <vergil@vip.163.com>
Date: Sun Sep 20 20:34:43 2015 +0800
First commit
执行以下命令:
➜ git-test git:(master) git reset --hard 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
HEAD is now at 7c1e8df Add index
现在已经成功回溯到特定分支(feature-A)创建之前的状态。由于所有文件都回溯到了指定哈希值对应的时间点上。README.md文件的内容也恢复到了当时的状态。
创建fix-B分支
现在来创建特性分支(fix-B)。
➜ git-test git:(master) git checkout -b fix-B
Switched to a new branch 'fix-B'
作为这个主题的作业内容,我们在README.md文件中添加一行文字:
➜ git-test git:(fix-B) echo "fix-B" >> README.md
然后直接提交README.md文件。
➜ git-test git:(fix-B) ✗ git add README.md
➜ git-test git:(fix-B) ✗ git commit -m "Fix B"
[fix-B 47aa230] Fix B
1 file changed, 1 insertion(+)
现在的状态如图所示:

当前fix-B的状态
接下来的目标是下图所示的状态,即主干分支合并feature-A分支的修改后,又合并了fix-B的修改

fix-B分支的下一步目标
推送至feature-A分支合并后的状态
首先恢复到feature-A分支合并后的状态。不妨称这一操作为“推进历史”。
git log
命令只能查看以当前状态为终点的历史日志。所以这里要使用git reflog
命令,查看当前仓库的操作日志。在日志中找出回溯历史之前的哈希值,通过 git reset --hard
命令恢复到回溯历史前的状态。
首先执行git reflog
命令,查看当前仓库执行过的操作日志。
➜ git-test git:(fix-B) git reflog
47aa230 HEAD@{0}: commit: Fix B
7c1e8df HEAD@{1}: checkout: moving from master to fix-B
7c1e8df HEAD@{2}: reset: moving to 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
c00815d HEAD@{3}: reset: moving to c00815d1247d0ef656b3ea31c8cd2079e04bf52b
7c1e8df HEAD@{4}: reset: moving to 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
c00815d HEAD@{5}: merge feature-A: Merge made by the 'recursive' strategy.
7c1e8df HEAD@{6}: checkout: moving from feature-A to master
ad5a4ba HEAD@{7}: checkout: moving from master to feature-A
7c1e8df HEAD@{8}: checkout: moving from feature-A to master
ad5a4ba HEAD@{9}: commit: Add feature-A
7c1e8df HEAD@{10}: checkout: moving from feature-B to feature-A
7c1e8df HEAD@{11}: checkout: moving from feature-A to feature-B
7c1e8df HEAD@{12}: checkout: moving from master to feature-A
7c1e8df HEAD@{13}: commit: Add index
69ca7dd HEAD@{14}: commit (initial): First commit
在日志仲,可以看到commit、checkout、reset、merge等Git命令的执行记录。只要不进行Git的GC(Garbage Collection,垃圾回收),就可以通过日志随意调取近期的历史状态,就像给时间机器指定一个时间点,在过去未来中自由穿梭一般。即便开发者错误指定了Git操作,基本也都可以利用git reflog
命令恢复到最原先的状态(如果人生可以像Git一样操作那该多好-_#)。
c00815d HEAD@{5}: merge feature-A: Merge made by the 'recursive' strategy.
这一行表示feature-A特性分支合并后的状态,对应哈希值为c00815d(哈希值只输入4位以上就可以执行)。将HEAD、暂存区、工作树恢复到这个时间点的状态。
➜ git-test git:(fix-B) git checkout master
Switched to branch 'master'
➜ git-test git:(master) git reset --hard c00815d
HEAD is now at c00815d Merge branch 'feature-A'
之前我们使用git reset --hard
命令回溯了历史,这里又再次通过它恢复到了回溯前的历史状态。当前的状态如下图所示:

解决冲突
现在只要合并fix-B分支,就可以得到我们想要的状态。现在来进行合并操作。
➜ git-test git:(master) 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-test git:(master) ✗ git add README.md
➜ git-test git:(master) ✗ git commit -m "Fix conflict"
[master 0d9652a] Fix conflict
由于本次更改解决了冲突,所以提交信息记录为“Fix conflict”
git commit --amend —— 修改提交信息
要修改上一条提交信息,可以使用git commit --amend
命令。
我们将上一条提交信息记录为“Fix conflict”,但它实际是fix-B分支的合并,解决合并时发生的冲突只是过程之一,这样标记实在不妥。于是,我们要修改这条提交信息。
➜ git-test git:(master) 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 Sep 21 02:08:05 2015 +0800
#
# On branch master
# Changes to be committed:
# modified: README.md
#
编辑器中显示的内容如上所示,其中包含之前的提交信息。将提交信息的部分修改为Merge branch 'fix-B'
,然后保存文件,关闭编辑器。
[master 957d25d] Merge branch 'fix-B'
随后会显示上面这条结果。现在执行git log --graph
命令,可以看到提交日志中的响应内容也已经被修改。
* commit 957d25dd33395a5cec6a8a2d9c95c1e0256894b4
|\ Merge: c00815d 47aa230
| | Author: Vergil <vergil@vip.163.com>
| | Date: Mon Sep 21 02:08:05 2015 +0800
| |
| | Merge branch 'fix-B'
| |
| * commit 47aa2309167faed0c7a7da6b7c8bc162adafb4b6
| | Author: Vergil <vergil@vip.163.com>
| | Date: Mon Sep 21 01:08:51 2015 +0800
| |
| | Fix B
| |
* | commit c00815d1247d0ef656b3ea31c8cd2079e04bf52b
|\ \ Merge: 7c1e8df ad5a4ba
| |/ Author: Vergil <vergil@vip.163.com>
|/| Date: Mon Sep 21 00:21:55 2015 +0800
| |
| | Merge branch 'feature-A'
| |
| * commit ad5a4baec2ac4125c489b2461cb203ec14cd2658
|/ Author: Vergil <vergil@vip.163.com>
| Date: Sun Sep 20 23:48:30 2015 +0800
|
| Add feature-A
|
* commit 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
| Author: Vergil <vergil@vip.163.com>
| Date: Sun Sep 20 21:20:27 2015 +0800
|
| Add index
|
* commit 69ca7dddd02bca848adccd5d00a62903081de979
Author: Vergil <vergil@vip.163.com>
Date: Sun Sep 20 20:34:43 2015 +0800
First commit
git rebase -i —— 压缩历史
在合并特性分支之前,如果发现已提交的内容中有些许拼写错误等,不妨提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录。这是个经常会用到的技巧,我们来实际操作体会一下。
创建feature-C分支
首先,新建一个fearure-C特性分支。
➜ git-test git:(master) git checkout -b feature-C
Switched to a new branch 'feature-C'
作为fearure-C的功能实现,在README.md文件中添加一行文字,并且故意留下拼写错误,以便之后修正。
➜ git-test git:(feature-C) echo "faeture-C" >> README.md
提交这部分内容。这个小小的变更就没必要先执行git add
命令再执行git commit
命令了,直接使用git commit -am
来一次完成这两部操作。
➜ git-test git:(feature-C) ✗ git commit -am "Add feature-C"
[feature-C 3b3482c] Add feature-C
1 file changed, 1 insertion(+)
修正拼写错误
看在来修正刚才预留的拼写错误。修正后的差别如下所示:
➜ git-test git:(feature-C) ✗ git diff
diff --git a/README.md b/README.md
index 9d181c5..847fb0f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
# Git基本操作
feature-A
fix-B
-faeture-C
+feature-C
然后进行提交。
➜ git-test git:(feature-C) ✗ git commit -am "Fix typo"
[feature-C e1eb7f3] Fix typo
1 file changed, 1 insertion(+)
错字漏字等失误称为typo,所以我们将提交信息记为“Fix typo”。
实际上,我们不希望在历史记录上看到这类提交,因为健全的历史记录不需要它们。如果能在最初提交之前就发现并修正这些错误,也就不会出现这类提交了。
更改历史
因此,我们来更改历史。将“Fix repo”修正的内容与之前一次的提交合并,在历史记录中合并为一次完美的提交。为此,要用到git rebase
命令。
➜ git-test git:(feature-C) git rebase -i HEAD~2
pick 3b3482c Add feature-C
pick e1eb7f3 Fix typo
# Rebase 957d25d..e1eb7f3 onto 957d25d ( 2 TODO item(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
#
# 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
我们将e1eb7f3的Fix typo的历史记录压缩到3b3482c的Add feature-C里。按以下所示,将e1eb7f3左侧的pick
部分删除,改写为fixup
。
pick 3b3482c Add feature-C
fixup e1eb7f3 Fix typo
保存编辑器里的内容,关闭编辑器。
➜ git-test git:(feature-C) git rebase -i HEAD~2
[detached HEAD 26c1654] Add feature-C
Date: Mon Sep 21 02:28:08 2015 +0800
1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/feature-C.
系统显示rebase成功。也就是以下面两个提交作为对象,将“Fix typo”的内容合并到了上一个提交“Add feature-C”中,改写成一个新的提交。
- 3b3482c Add feature-C
- e1eb7f3 Fix typo
现在再查看提交日志时发现Add feature-C的哈希值已经不是3b3482c,这证明提交已经被更改。
➜ git-test git:(feature-C) git log --graph
* commit 26c16540cf42661c84da8e46b8cd8377783b5e5d
| Author: Vergil <vergil@vip.163.com>
| Date: Mon Sep 21 02:28:08 2015 +0800
|
| Add feature-C
|
* commit 957d25dd33395a5cec6a8a2d9c95c1e0256894b4
|\ Merge: c00815d 47aa230
| | Author: Vergil <vergil@vip.163.com>
| | Date: Mon Sep 21 02:08:05 2015 +0800
| |
| | Merge branch 'fix-B'
| |
| * commit 47aa2309167faed0c7a7da6b7c8bc162adafb4b6
| | Author: Vergil <vergil@vip.163.com>
| | Date: Mon Sep 21 01:08:51 2015 +0800
| |
| | Fix B
| |
* | commit c00815d1247d0ef656b3ea31c8cd2079e04bf52b
|\ \ Merge: 7c1e8df ad5a4ba
| |/ Author: Vergil <vergil@vip.163.com>
|/| Date: Mon Sep 21 00:21:55 2015 +0800
| |
| | Merge branch 'feature-A'
| |
| * commit ad5a4baec2ac4125c489b2461cb203ec14cd2658
|/ Author: Vergil <vergil@vip.163.com>
| Date: Sun Sep 20 23:48:30 2015 +0800
|
| Add feature-A
|
* commit 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
| Author: Vergil <vergil@vip.163.com>
| Date: Sun Sep 20 21:20:27 2015 +0800
|
| Add index
|
* commit 69ca7dddd02bca848adccd5d00a62903081de979
Author: Vergil <vergil@vip.163.com>
Date: Sun Sep 20 20:34:43 2015 +0800
First commit
合并至master分支
feature-C分支的使命告一段落,将它与master合并。
➜ git-test git:(feature-C) git checkout master
Switched to branch 'master'
➜ git-test git:(master) git merge --no-ff feature-C
Merge made by the 'recursive' strategy.
README.md | 2 ++
1 file changed, 2 insertions(+)
master分支整合了feature-C分支。