您现在的位置是:首页 >技术杂谈 >Git常用命令rebase网站首页技术杂谈
Git常用命令rebase
Git常用命令rebase
1、git常用命令rebase
rebase 会把你当前分支的 commit 放到公共分支的最后面,所以叫变基,就好像你从公共分支又重新拉出来这个
分支一样。
例如如果你从 master 拉了个 feature 分支出来,然后你提交了几个 commit,这个时候刚好有人把他开发的东西
合并到 master 了,这个时候 master 就比你拉分支的时候多了几个 commit,如果这个时候你 rebase master 的
话,就会把你当前的几个 commit,放到那个人 commit 的后面。
具体操作:
首先 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。
2、git rebase的两种用法
2.1 合并当前分支的多个commit记录
你可能出现过对同一处代码进行多次处理的场景,这会导致如下提交记录:
# 新建文件
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 就很有用了。
2.1.1 找到想要合并的commit,使用rebase -i
寻找合并 commit 的下一个。
$ git rebase -i 58c8fed
注意:git rebase -i [startPonit] [endPoint]
前开后闭区间,这里的 [startPonit] 是指需要合并的 commit 的前一个 commit (即当前示例中的 58c8fed ),因
为三个 commit 肯定要基于上一个 commit 合并成了新的 commit。
谨慎使用 [endPoint],该参数省略即默认表示从起始 commit 一直到最后一个,但是一旦你填写了,则表示
[endPoint] 后面的commit全部不要了。
2.1.2 进入Interact交互界面
终端会进入选择交互界面,让你进行变基选择操作:
最上面三行,就是刚刚选中的三个 commit,按时间顺序依次往下排序(和 git log 的展示顺序是反的, 大家查看的时
候要注意)。
前面的三个 Pick 其实就是下面 Commands 展示的7种命令中的第一个 p,也就是 use commit。
2.1.3 使用s命令合并到上一个commit
按 i 进入操作,将第二、三个 commit 的 pick 改成 s,按 Esc 退出操作,输入 :wq 保存并退出。
2.1.4 修改commit记录
接下来会弹出第二个页面,分别展示三个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.
2.1.5 查看最新合并情况
会发现原三个一样的提交现在合并成了一个新的commit 。
$ git log --pretty=format:'%h: %s'
e6c4bbd: modify c
792e41c: modify b
58c8fed: modify a
f6f3452: Initial commit
2.1.6 rebase的其他用法
命令 | 缩写 | 含义 |
---|---|---|
pick | p | 保留该commit |
reword | r | 保留该commit,但需要修改该commit的注释 |
edit | e | 保留该commit,但我要停下来修改该提交(不仅仅修改注释) |
squash | s | 将该commit合并到前一个commit |
fixup | f | 将该commit合并到前一个commit,但不要保留该提交的注释信息 |
exec | x | 执行shell命令 |
drop | d | 丢弃该commit |
2.2 避免出现分叉合并
接下来,我将用实际示例和场景,来分析 rebase 是如何解决分叉合并的,在此之前,我先做如下规定:
1、有两个分支:develop
(主分支) 和 rebase_new
(feature分支)
2、新需求按时间顺序叫 a 、b、...
等 ( a 需求最早,b 其次,以此类推)
3、原 commit a 变基之后(hashId改变) 叫 a'
2.2.1 先初始化信息
# 新建文件本地提交
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 rebase.js
git add rebase.js
git commit -m "feat rebase.js"
# 修改当前分支名master为develop
$ git branch -m master develop
$ git branch
* develop
# 新建分支
$ git branch rebase_new
$ git branch
* develop
rebase_new
$ git log --pretty=format:"%h: %cd %s" --graph
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
$ git checkout rebase_new
Switched to branch 'rebase_new'
$ git log --pretty=format:"%h: %cd %s" --graph
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
2.2.2 develop新增文件
# develop分支下操作
$ touch a.txt
$ git add a.txt
$ git commit -m "feat a"
[develop ea2b178] feat a
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 a.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* ea2b178: Tue Mar 14 14:07:18 2023 +0800 feat a
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
2.2.3 feature新增文件
# rebase_new操作
$ touch b.txt
$ git add b.txt
$ git commit -m "feat b"
[rebase_new 9a6ddc7] feat b
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b.txt
$ git log --pretty=format:"%h: %cd %s" --graph
* 9a6ddc7: Tue Mar 14 14:08:30 2023 +0800 feat b
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
2.2.4 develop直接merge feature
$ git checkout develop
Switched to branch 'develop'
$ git merge rebase_new
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
* 185c7fc: Tue Mar 14 14:12:57 2023 +0800 Merge branch 'rebase_new' into develop
|
| * 9a6ddc7: Tue Mar 14 14:08:30 2023 +0800 feat b
* | ea2b178: Tue Mar 14 14:07:18 2023 +0800 feat a
|/
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
会出现以下结果:
1、会保留所有的commit(hashId不变)
2、按提交顺序排序
3、产生新的commit点 (Merge branch ‘rebase_new’ into develop)
2.2.5 develop rebase feature
$ git checkout develop
Switched to branch 'develop'
$ git rebase rebase_new
First, rewinding head to replay your work on top of it...
Applying: feat a
$ git log --pretty=format:"%h: %cd %s" --graph
* d60a365: Tue Mar 14 14:27:06 2023 +0800 feat a
* 9a6ddc7: Tue Mar 14 14:08:30 2023 +0800 feat b
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
会出现以下结果:
develop 分支的 a 会被排在合进来的 feature 分支 b 的上面(尽管a是先完成的)
develop 的原 commit a 被移除产生了新的 commit a’ (hashId已变)
从 feature 合进来的 b 不变 (不会对合进来的 commit 进行变基)
2.2.6 rebase两步走完整版
step1:feature 先 rebase develop
# rebase_new分支
$ git checkout rebase_new
Switched to branch 'rebase_new'
# 从分支上拉取
# 拉取的是两个分支都没有提交过的
$ git fetch origin
$ git rebase develop
First, rewinding head to replay your work on top of it...
Applying: feat b
$ git log --pretty=format:"%h: %cd %s" --graph
* c2b5c6c: Tue Mar 14 15:02:02 2023 +0800 feat b
* ea2b178: Tue Mar 14 14:07:18 2023 +0800 feat a
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
# 上面的命令比较繁琐,也可以直接执行下面的命令
# rebase_new分支
$ git checkout rebase_new
Switched to branch 'rebase_new'
$ 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 rebase_new
Updating ea2b178..c2b5c6c
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
* c2b5c6c: Tue Mar 14 15:02:02 2023 +0800 feat b
* ea2b178: Tue Mar 14 14:07:18 2023 +0800 feat a
* e3e3525: Tue Mar 14 13:58:03 2023 +0800 feat rebase.js
* f1801e7: Tue Mar 14 13:58:02 2023 +0800 first commit
* cf94b2d: Tue Mar 14 13:58:01 2023 +0800 Initial commit
而这,也是 rebase 为什么不会产生多余的 commit 记录的原因了。
不要基于 rebase 的分支切新分支:如果 feature 在写完新需求 b 后,切了新分支 feature_B,然后又 rebase 了
develop,那么新分支 feature_B 无论是合进 develop 还是合进 feature 都会有冲突,出现重复的 b (其实是b和
b’)。
除非你能百分百确保你的分支已经完成新需求,rebase 操作结束之后,再切新分支,这时他们才是同步的。
3、rebase的其它问题
3.1 rebase时如何解决冲突
1、先解决冲突再保存
2、git add .
3、git rebase --continue
注意不是commit。
如果 rebase 过程中,你想中途退出,恢复 rebase 前的代码则可以用命令 git rebase --abort
。
3.2 使用rebase的注意点
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。
因为变基最强大也是最危险之处就在于它能改变原commit的hashId,而一旦你对历史提交做出改变, 那么从变基
那个节点开始往后的所有节点的 commit id 都会发生变化。这对线上环境来说,是不可控的。
3.3 不要对已经合并到主分支的本地修改进行变基
首先,自己的分支,如果想对已经推送的 commit 进行修改,可以在修改完后,使用 git push -f 强行 push 并覆
盖远程对应分支。
但是如果这些修改已经被合到了其他分支(比如主分支),那又会出现冲突,因为其他分支保存的是你 rebase 之前
合进去的 commit。
3.4 不要在预发布/正式分支上使用rebase -i
从变基那个节点开始往后的所有节点的 commit id 都会发生变化。
所以可以想象一下,master 上有100个 commit,你悄悄改了第 50 个 commit,那从 50-100 的所有 commit 全
部改变了,这时别人的分支合进来,就会有51个冲突,解决完冲突之后,就会产生51*2个相同的提交记录。