Git使用教程六

本文主要介绍两个git命令,它们分别是git rebase和git cherry-pick。git rebase可以改变分支的根基,假设有两个分支沿着不同的方向操作,使用rebase命令,可以将两条沿着不同方向的分支合并为一个分支,这两个分支路径会被更改为一条路径。不仅如此,git rebase还可以更改合并服务器上的提交记录,可以将已经提交到服务器的多条历史提交合并为一个提交记录。git merge命令可以将整个分支的更改合并到另一个分支,但是如果想将某一次提交的更改,作为一次新的提交合并其它分支,这时候可以使用git cherry-pick命令。

git rebase

rebase翻译为变基或者衍合,使用rebase意味着会改变分支的根基。

git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
	[<upstream> [<branch>]]
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
	--root [<branch>]
git rebase --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch

使用rebase变基

在本地使用git rebase操作,网上有一个很详细的例子,例子如下:

假设你现在基于远程分支"origin",创建一个叫"mywork"的分支。

$ git checkout -b mywork origin

现在我们在这个分支做一些修改,然后生成两个提交(commit)。

$ vi file.txt
$ git commit
$ vi otherfile.txt
$ git commit
...

但是与此同时,有些人也在"origin"分支上做了一些修改并且做了提交了。这就意味着"origin"和"mywork"这两个分支各自"前进"了,它们之间"分叉"了。

在这里,你可以用"pull"命令把"origin"分支上的修改拉下来并且和你的修改合并; 结果看起来就像一个新的"合并的提交"(merge commit):

但是,如果你想让"mywork"分支历史看起来像没有经过任何合并一样,你也许可以用 git rebase:

$ git checkout mywork
$ git rebase origin

这些命令会把你的"mywork"分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch)(这些补丁放到".git/rebase"目录中),然后把"mywork"分支更新 到最新的"origin"分支,最后把保存的这些补丁应用到"mywork"分支上。

当'mywork'分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection), 这些被丢弃的提交就会删除。

现在我们可以看一下用合并(merge)和用rebase所产生的历史的区别:

在rebase的过程中,也许会出现冲突(conflict). 在这种情况,Git会停止rebase并会让你去解决冲突;在解决完冲突后,用"git add"命令去更新这些内容的索引(index), 然后,你无需执行 git-commit,只要执行:

$ git rebase --continue

这样git会继续应用(apply)余下的补丁。

在任何时候,你可以用--abort参数来终止rebase的行动,并且"mywork" 分支会回到rebase开始前的状态。

$ git rebase --abort

一般来说,不要对master分支执行rebase,否则会引起许多问题。

执行rebase的分支基本上是开发自己的本地分支,没有推送到远程分支库。

使用rebase -i合并提交

如果想要对上一次的commit做更改,或者想将上一次commit和本次的commit合并为一个,那么可以使用git commit --amend命令。虽然可以使用--amend合并提交,有一点需要注意,提交的commit id还是会更改,只是将提交记录合并为了一条,commit id是最新一次提交的id。

但是如果想要合并多次提交呢,这时候可以使用类似git rebase -i HEAD~3命令,该命令表示合并前三次提交为一次提交。 在开发中有可能某次提交后,发现还有些逻辑没有更改完成,结果是对同一个场景进行了多次提交,如下是提交记录:

为了更清晰简洁的显示提交记录,现在将如上3次提交合并为一次。

$ git rebase -i HEAD~3
[detached HEAD 3fcba56] add 002 line
 Date: Wed Oct 2 10:29:37 2019 +0800
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/develop.

命令执行成功后,会弹出如下rebase message信息。

pick 63f42fa add 002
pick e6bbd74 add 002 行
pick e181252 add 002 line

# Rebase 0b15e07..e181252 onto 0b15e07 (3 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

想要输出的结果是最后两次的提交跟第一次的提交记录合并为一个记录,那么现在更改rebase的message文件如下:

pick 63f42fa add 002
s e6bbd74 add 002 行
s e181252 add 002 line

这样后面的两次提交记录就会和第一次的记录合并,统一显示为第一次的提交记录,保存后会弹出另外一个message页面。其实是提示,有多条带提交的message信息,视情况更改,可以保留某个想要的提交信息。

# This is a combination of 3 commits.
# This is the 1st commit message:

add 002

# This is the commit message #2:

add 002 行

# This is the commit message #3:

add 002 line

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Wed Oct 2 10:29:37 2019 +0800
#
# interactive rebase in progress; onto 0b15e07
# Last commands done (3 commands done):
#    squash e6bbd74 add 002 行
#    squash e181252 add 002 line
# No commands remaining.
# You are currently rebasing branch 'develop' on '0b15e07'.
#
# Changes to be committed:
#	modified:   README
#

当更改完成后,需要使用如下命令,强制push到远程分支上去,因为这时候的提交记录已经落后于服务器上的提交记录了,本次使用的是倒数第三次的提交。

$ git push -f
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 254 bytes | 127.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Powered By Gitee.com
To https://gitee.com/xxxx/gitest.git
 + e181252...3fcba56 develop -> develop (forced update)

这时候再次刷新服务器的提交记录,已经只保留一次的提交记录了。

在介绍pull命令时,git pull命令相当于git fetch和git merge命令的组合。当然了,也可以将pull命令和rebase命令结合使用,方式如下:

$ git pull --rebase

上述命令相当于git fetch和git rebase命令的组合。

git cherry-pick

在不同的分支之间合并更新,可以使用git merge或者git rebase命令。

如果想获取某一个分支的单笔提交,并作为一个新的提交引入到当前分支上,这时候可以使用git cherry-pick。

git cherry-pick [--edit] [-n] [-m parent-number] [-s] [-x] [--ff]
		  [-S[<keyid>]] <commit>…?
git cherry-pick --continue
git cherry-pick --quit
git cherry-pick --abort

在不同的分支或者不同的版本之间开发,有时候cherry-pick命令就显得非常有用了。假设在开发分支上面有某些关键提交,而这些提交需要同步到某个历史版本,或者其它分支上,那么在开发分支提交后,就可以直接使用cherry-pick命令,通过开发分支提交的id cherry-pick到其它历史版本或者分支上。这种操作非常方便,简化了许多同步代码的步骤。

$ git log --graph --pretty=oneline --abbrev-commit
* 8e14913 (HEAD -> develop, origin/develop) add 003 line
* c823ff1 add important line
* 3fcba56 (master) add 002 line
...

当前在develop分支上,假设有如上提交信息,有一个重要提交需要同步到master分支上面、去,但是当前分支一直处于开发中,不能将所有的更改都merge或者rebase到master,这时候就可以使用cherry-pick命令。

$ git cherry-pick c823ff1
[master c71aeb5] add important line
 Date: Wed Oct 2 17:22:01 2019 +0800
 1 file changed, 1 insertion(+)

$ git log --graph --pretty=oneline --abbrev-commit
* c71aeb5 (HEAD -> master) add important line
* 3fcba56 add 002 line
* 0b15e07 add 001 line

从运行结果可以知道,add important line的本次提交已经更新到了master分支。

执行git cherry-pick命令,类似merge或者rebase命令,也有可能发生冲突,解决冲突的方式跟其它两个命令类似,有一点需要注意,就是在解决冲突后,需要执行一下git cherry-pick --continue命令,这样才算完成了整个cherry-pick流程。

小结

本文内容不多,主要是git rebase和git cherry-pick两个命令的简单使用。rebase命令不仅可以更改本地分支的根基,还可以合并更改已经提交至服务器的多条记录的历史,改变根基可以根据项目或者实际场景的需要来使用,但是更改提交历史在某些场景下则显得尤为实用。cherry-pick也很容易使用,跟merge对比理解,知道git不仅可以合并整个分支的更改,还可以合并某个分支某一次的提交。

参考资料

Git教程

Git Community Book 中文版

git rebase -i

评论

您确定要删除吗?删除之后不可恢复