rebase 会把你当前分支的 commit 放到公共分支的最后面,所以叫变基,就好像你从公共分支又重新拉出来这个
分支一样。
例如如果你从 master 拉了个 feature 分支出来,然后你提交了几个 commit,这个时候刚好有人把他开发的东西
合并到 master 了,这个时候 master 就比你拉分支的时候多了几个 commit,如果这个时候你 rebase master 的
话,就会把你当前的几个 commit,放到那个人 commit 的后面。
rebase 具体操作:
首先 master 也需要拉取到最新版本,然后是切换到 branch 分支,在branch分支执行 git rebase master
,表
示 branch 上新提交的 commit 节点会在 master 上的最新提交点后重新设立起点重新执行,若是有冲突则解决冲
突,没有冲突执行 git add
,然后执行 git rebase --continue
,最后切换到 master 分支,执行 git merge
master
。
git merge 会将两个分支的最新提交点进行一次合并,形成一个新的提交点,最终形成树状的提交记录,但是有些
人并不是喜欢 merge,觉得 merge 之后出现的分叉会难以管理,那么可以选择 rebase 操作来替代 merge。
git rebase 操作是将要 rebase 的分支最新提交点作为新的基础点,将当前执行 git rebase master 的分支的新
commit 点重新生成 commit hash 值,rebase 完后再次切换到另一条分支进行合并,就可以保证线性的 commit
的记录。
最后只选择 merge 还是 rebase 取决于个人和时机情况,假如你想提交记录呈现线性整洁那么选择 rebase,否则
选择 merge,实际情况也有可能是这样的,每个人本地开发,可能会提交非常的多次,有些提交可能是修一些简
单的 bug,那么最后的提交只想做一次完整、正确的提交,那么也可以使用 rebase。
你可能出现过对同一处代码进行多次处理的场景,会进行多次提交,这会导致如下提交记录:
# 新建文件
touch README.md
touch a.txt
touch b.txt
touch c.txt
# 新建文件和本地提交
git add .
git commit -m "Initial commit"
# 修改和提交
echo a > a.txt
git add a.txt
git commit -m "modify a"
# 修改和提交
echo 1 > b.txt
git add b.txt
git commit -m "modify b"
# 修改和提交
echo 2 > b.txt
git add b.txt
git commit -m "modify b"
# 修改和提交
echo 3 > b.txt
git add b.txt
git commit -m "modify b"
# 修改和提交
echo c > c.txt
git add c.txt
git commit -m "modify c"
$ git log --pretty=format:'%h: %s'
2eeb74a: modify c
5d340c4: modify b
e51aaca: modify b
16aee3f: modify b
58c8fed: modify a
f6f3452: Initial commit
其实,中间的对 b 的 3 次提交完全可以合并成一次 commit,这个时候 rebase 就很有用了。
这里的合并的 commit 是待合并的多个 commit 之前的那个 commit,这里也就是最下面那个 modify a 的
commit。
$ git rebase -i 58c8fed
注意:git rebase -i startPonit endPoint
前开后闭区间,这里的 startPonit 是指需要合并的 commit 的前一个 commit (即当前示例中的 58c8fed ),因
为三个 commit 肯定要基于上一个 commit 合并成了新的 commit。
谨慎使用 endPoint,该参数省略即默认表示从起始 commit 一直到最后一个,但是一旦你填写了,则表示
endPoint 后面的commit全部不要了。
终端会进入选择交互界面,让你进行变基选择操作。
最上面三行,就是刚刚选中的三个 commit,按时间顺序依次往下排序。
前面的三个 Pick 其实就是下面 Commands 展示的 7 种命令中的第一个 p,也就是 use commit。
按 i 进入操作,将第 2 和 第 3 个 commit 的 pick 改成 s,按 Esc 退出操作,输入 :wq 保存并退出。
接下来会弹出第二个页面,分别展示 3 个commit的提交信息。
这里三个信息都是一样的,我们选用第一个的提交信息,将其余的全部注释掉,重复上述步骤,保存退出即可。
# 执行结果
$ git rebase -i 58c8fed
[detached HEAD 792e41c] modify b
Date: Tue Mar 14 12:28:51 2023 +0800
1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.
会发现原三个一样的提交现在合并成了一个新的 commit 。
$ git log --pretty=format:'%h: %s'
e6c4bbd: modify c
792e41c: modify b
58c8fed: modify a
f6f3452: Initial commit
命令 | 缩写 | 含义 |
---|---|---|
pick | p | 保留该commit |
reword | r | 保留该commit,但需要修改该commit的注释 |
edit | e | 保留该commit,但我要停下来修改该提交(不仅仅修改注释) |
squash | s | 将该commit合并到前一个commit |
fixup | f | 将该commit合并到前一个commit,但不要保留该提交的注释信息 |
exec | x | 执行shell命令 |
drop | d | 丢弃该commit |
接下来,我将用实际示例和场景,来分析 rebase 是如何解决分叉合并的。
# 新建文件本地提交
touch README.md
git add README.md
git commit -m "Initial commit"
# 新建文件本地提交
touch first.txt
git add first.txt
git commit -m "first commit"
# 新建文件本地提交
touch second.txt
git add second.txt
git commit -m "second commit"
# 修改当前分支名master为develop
$ git branch -m master develop
$ git branch
* develop
# 新建分支
$ git branch feature
$ git branch
* develop
feature
# 查看log信息
$ git log --pretty=format:"%h: %cd %s" --graph
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
$ git checkout feature
Switched to branch 'feature'
$ git log --pretty=format:"%h: %cd %s" --graph
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
# develop分支下操作
$ touch a.txt
$ git add a.txt
$ git commit -m "add a.txt"
[develop 0434671] add a.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 a.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* 0434671: Thu May 18 13:49:41 2023 +0800 add a.txt
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
# feature操作
$ touch b.txt
$ git add b.txt
$ git commit -m "add b.txt"
[feature 21d641d] add b.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* 21d641d: Thu May 18 13:51:04 2023 +0800 add b.txt
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
$ git checkout develop
Switched to branch 'develop'
$ git merge feature
Merge made by the 'recursive' strategy.
b.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* 4e78e41: Thu May 18 13:52:04 2023 +0800 Merge branch 'feature' into develop
|\
| * 21d641d: Thu May 18 13:51:04 2023 +0800 add b.txt
* | 0434671: Thu May 18 13:49:41 2023 +0800 add a.txt
|/
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
会出现以下结果:
1、会保留所有的commit
2、按提交顺序排序
3、产生新的commit点 (Merge branch ‘feature’ into develop)
$ git checkout develop
Switched to branch 'develop'
$ git rebase feature
First, rewinding head to replay your work on top of it...
Applying: add a.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* 77cda09: Thu May 18 13:54:43 2023 +0800 add a.txt
* 21d641d: Thu May 18 13:51:04 2023 +0800 add b.txt
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
会出现以下结果:
develop 分支的 a 会被排在合进来的 feature 分支 b 的上面(尽管 a 是先完成的)
develop 的原 commit a 被移除产生了新的 commit a’
从 feature 合进来的 b 不变 (不会对合进来的 commit 进行变基)
step1:feature 先 rebase develop
# feature分支
$ git checkout feature
Already on 'feature'
# 从分支上拉取
# 拉取的是两个分支都没有提交过的
$ git fetch origin
$ git rebase develop
First, rewinding head to replay your work on top of it...
Applying: add b.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* 73699c0: Thu May 18 20:04:41 2023 +0800 add b.txt
* 0434671: Thu May 18 13:49:41 2023 +0800 add a.txt
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
# 上面的命令比较繁琐,也可以直接执行下面的命令
# feature分支
$ git checkout feature
Switched to branch 'feature'
$ git pull develop --rebase
会出现以下结果:
feature 的 commit b 会在重新排在第一个变成 b’
这一步可以理解为,当前的 feature 相当于先把需求 b 拎出来,同步完最新的 develop,再把需求 b 放在了最后
面。
所以,接下来 merge 的时候就很舒服了。
step2:develop 再 merge develop
# develop分支
$ git checkout develop
Already on 'develop'
$ git merge feature
Updating 0434671..73699c0
Fast-forward
b.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* 73699c0: Thu May 18 20:04:41 2023 +0800 add b.txt
* 0434671: Thu May 18 13:49:41 2023 +0800 add a.txt
* b825e57: Thu May 18 12:59:45 2023 +0800 second commit
* 40cf065: Thu May 18 12:59:09 2023 +0800 first commit
* 1e86259: Thu May 18 12:59:03 2023 +0800 Initial commit
而这,也是 rebase 为什么不会产生多余的 commit 记录的原因了。
不要基于 rebase 的分支切新分支:如果 feature 在写完新需求 b 后,切了新分支 feature_1,然后又 rebase 了
develop,那么新分支 feature_1 无论是合进 develop 还是合进 feature 都会有冲突,出现重复的 b (其实是b和
b’)。
除非你能百分百确保你的分支已经完成新需求,rebase 操作结束之后,再切新分支,这时他们才是同步的。
1、先解决冲突再保存
2、git add .
3、git rebase --continue
注意不是commit。
如果 rebase 过程中,你想中途退出,恢复 rebase 前的代码则可以用命令 git rebase --abort
。
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。
因为变基最强大也是最危险之处就在于它能改变原commit的hashId,而一旦你对历史提交做出改变, 那么从变基
那个节点开始往后的所有节点的 commit id 都会发生变化。这对线上环境来说,是不可控的。
首先,自己的分支,如果想对已经推送的 commit 进行修改,可以在修改完后,使用 git push -f 强行 push 并覆
盖远程对应分支。
但是如果这些修改已经被合到了其他分支(比如主分支),那又会出现冲突,因为其他分支保存的是你 rebase 之前
合进去的 commit。
从变基那个节点开始往后的所有节点的 commit id 都会发生变化。
它的另一个用法是删除任意提交,但是注意不到万不得已的时候不要这么做,因为会使后面的 commit id都会变
化。
$ git log --oneline
d735ee3 (HEAD -> master, origin/master, origin/branch_d, origin/HEAD) branch_d | update a.txt | update b.txt | update e.txt
8cb57f6 (origin/branch_c) branch_c | update a.txt | delete e.txt
5b05cb6 (origin/branch_b) branch_b | update a.txt | add new.txt
ddbfc0b (origin/branch_a) branch_a | update a.txt | add new.txt
87d5c63 add f.txt
47e8b59 add e.txt
c0547da add d.txt
9c173bb add c.txt
8c4a625 add b.txt
8e58180 add a.txt
# git rebase --onto commit^ commit
# 例子,假设不要47e8b59这个提交了
$ git rebase --onto 47e8b59^ 47e8b59
First, rewinding head to replay your work on top of it...
Applying: add f.txt
Applying: branch_a | update a.txt | add new.txt
Applying: branch_b | update a.txt | add new.txt
Applying: branch_c | update a.txt | delete e.txt
Using index info to reconstruct a base tree...
A e.txt
Falling back to patching base and 3-way merge...
Applying: branch_d | update a.txt | update b.txt | update e.txt
$ git log --oneline
2e1e177 (HEAD -> master) branch_d | update a.txt | update b.txt | update e.txt
b6f1c57 branch_c | update a.txt | delete e.txt
1868ec3 branch_b | update a.txt | add new.txt
1eccbb4 branch_a | update a.txt | add new.txt
4e89b48 add f.txt
c0547da add d.txt
9c173bb add c.txt
8c4a625 add b.txt
8e58180 add a.txt