New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何优雅地合并多个 Commit #13

Open
JasinYip opened this Issue Jan 5, 2016 · 15 comments

Comments

Projects
None yet
10 participants
@JasinYip

JasinYip commented Jan 5, 2016

大家都知道 Git 是一种分布式的版本控制工具。正因为它有「分布式」这个特性,所以理论上我们其实可以不需要连接网络,就可以进行版本管理。然而,在实际上这是不可能的,因为你还需要上网查 Git 的 中种命令(逃


哈哈,玩笑开完,进入正题吧!

我为什么要写这篇文章呢?因为实在太多同学跑来问我「到底怎么合并 commit?」了,每次都重复讲一遍这种做法完全不符合程序猿的风格啊!

那么,就先让我们来看这么一个情况,我们执行以下命令获得四个 Commit:

mkdir test
cd test

git init

echo "0" >> a
git add a
git commit -m "Commit-0"

echo "1" >> a
git add a
git commit -m "Commit-1"

echo "2" >> a
git add a
git commit -m "Commit-2"

echo "3" >> a
git add a
git commit -m "Commit-3"

我们可以看到 Git 的历史长成这样:

* b1b8189 - (HEAD -> master) Commit-3
* 5756e15 - Commit-2
* e7ba81d - Commit-1
* 5d39ff2 - Commit-0

那么问题来了,如何把 e7ba81d(Commit-1)5756e15(Commit-2)b1b8189(Commit-3) 合并到一起,并且只保留 e7ba81d(Commit-1) 的 Git message Commit-1 呢?

这个时候我们就要祭出我们这篇文章的主角—— git rebase -i 了!
这里我不想直接搬出写文档的那套,把所有的选项都介绍完,我们就把这次要用到的讲一下。

-i 实际上就是 --interactive 的简写,在使用 git rebase -i 时,我们要在后面再添加一个参数,这个参数应该是 最新的一个想保留的 Commit。这句话读起来有点坳口,所以这个情况下通常需要举个例子。就我们前面提到的那个例子中,这个「最新的一个想保留的 Commit」就是 5d39ff2(Commit-0),于是我们的命令看起来就长这样:

git rebase -i 5d39ff2

当然,我们也可以通过 HEAD~3 来指定该 Commit:

git rebase -i HEAD~3

按下回车后,我们会进入到这么一个界面:

pick e7ba81d Commit-1
pick 5756e15 Commit-2
pick b1b8189 Commit-3

# Rebase 5d39ff2..b1b8189 onto 5d39ff2 (3 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
#
# 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

前面三行是我们需要操作的三个 Commit,每行最前面的是对该 Commit 操作的 Command。关于每个 Command 具体做什么,下面的注释写得非常清楚。为了完成我们的需求,我们可以关注到这两个命令:

s, squash = use commit, but meld into previous commit
f, fixup = like "squash", but discard this commit's log message

为了让大家看得更明白,我不厌其烦地翻译一下:

  • squash:使用该 Commit,但会被合并到前一个 Commit 当中
  • fixup:就像 squash 那样,但会抛弃这个 Commit 的 Commit message

看样子两个命令都可以完成我们的需求,那么让我们先试一下 squash!由于我们是想把三个 Commit 都合并在一起,并且使 Commit Message 写成 Commit-1,所以我们需要把 5756e15(Commit-2)b1b8189(Commit-3) 前面的 pick 都改为squash,于是它看起来像这样:

pick e7ba81d Commit-1
squash 5756e15 Commit-2
squash b1b8189 Commit-3

当然,因为我很懒,所以通常我会使用它的缩写:

pick e7ba81d Commit-1
s 5756e15 Commit-2
s b1b8189 Commit-3

完成后,使用 :wq 保存并退出。这个时候,我们进入到了下一个界面:

# This is a combination of 3 commits.
# The first commit's message is:
Commit-1

# This is the 2nd commit message:

Commit-2

# This is the 3rd commit message:

Commit-3

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Jan 5 23:27:22 2016 +0800
#
# rebase in progress; onto 5d39ff2
# You are currently editing a commit while rebasing branch 'master' on '5d39ff2'.
#
# Changes to be committed:
#   modified:   a

通过下面的注释,我们可以知道,这里其实就是一个编写 Commit Message 的界面,带 # 的行会被忽略掉,其余的行就会作为我们的新 Commit Message。为了完成我们的需求,我们修改成这样:

Commit-1

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Jan 5 23:27:22 2016 +0800
#
# rebase in progress; onto 5d39ff2
# You are currently editing a commit while rebasing branch 'master' on '5d39ff2'.
#
# Changes to be committed:
#   modified:   a

使用 :wq 后,再看一下我们的 log:

* 2d7b687 - (HEAD -> master) Commit-1
* 5d39ff2 - Commit-0

任务完成!


至于 fixup 怎么用,我觉得大家现在应该已经知道了,因为我已经展示过 squash 的用法了,相信你再去看一下上面的 fixup 的解释就能明白了。

那么这篇文章就这样粗略地讲了一下 git rebase -i 的最基础的用法,希望能帮助到大家,有任何问题可以直接在评论里问我吧~

@jaysimon

This comment has been minimized.

jaysimon commented Jun 15, 2017

Q:最好能说明一下最后的a文件时什么样的状态,处在commit 几的状态

@kevin1202

This comment has been minimized.

kevin1202 commented Aug 8, 2017

很清楚,感谢

@yanhaijing

This comment has been minimized.

yanhaijing commented Aug 8, 2017

差评,一个图都没有

@gzhjs

This comment has been minimized.

gzhjs commented Aug 8, 2017

赞!!但是感觉会导致大家滥用rebase,毕竟它会导致一些历史被丢掉

@yanhaijing

This comment has been minimized.

yanhaijing commented Aug 8, 2017

@gzhjs push的就不能rebase了

@GeeLaw

This comment has been minimized.

GeeLaw commented Aug 8, 2017

@gzhjs 我经常把弱智的错误通过 fixup 弄掉。

@yanhaijing 可以 --force,虽然会让别人很麻烦(如果别人 pull 了你的 branch)。

@yanhaijing

This comment has been minimized.

yanhaijing commented Aug 9, 2017

@GeeLaw 这样不好,最好的就是 禁止push -f

@countzj

This comment has been minimized.

countzj commented Sep 5, 2017

这个太麻烦,用tortoiseGit可以轻松搞定,调出提交日志,选择需要合并的日志,就会出来合并按钮,修改一个注释就搞定了!

@countzj

This comment has been minimized.

countzj commented Sep 5, 2017

还没发现sourcetree怎么合并commit,知道的可以分享一下

@yanhaijing

This comment has been minimized.

yanhaijing commented Sep 5, 2017

命令行多好用,一次学会,受益终身

@sunshumin

This comment has been minimized.

sunshumin commented Sep 11, 2017

如果想把

* 5756e15 - Commit-2
* e7ba81d - Commit-1

这两个commit合并有什么办法?

@countzj

This comment has been minimized.

countzj commented Sep 11, 2017

如果合并commit-2和commit-1,就在git rebase -i 后面跟上commit1和2的序列号就可以了吧

@GeeLaw

This comment has been minimized.

GeeLaw commented Sep 11, 2017

@sunshumin

p 575...
f e7b...
p b1b...

之后 commit-3 的 hash 自然会变化。

@ChopinChao

This comment has been minimized.

ChopinChao commented Oct 17, 2018

文件少的话可以先: git reset --soft 然后重新 git commit ... 这样又简单又好理解。
rebase -i 合并比较麻烦,利用 rebase 做分支之间的变基会比较好;

@totoleo

This comment has been minimized.

totoleo commented Oct 23, 2018

@gzhjs push的就不能rebase了

可以强制 push

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment