Rebase(变基)
Rebase
是将一个分支的所有更改重新应用到另一个分支的最新状态上。使你的分支看起来像是在目标分支的最新提交之后创建的。
[应用举例] (heads/dev) git rebase master 详细工作原理
1、Git 会检查是否有未提交的更改,如果你有未提交的修改,Git 会尝试保留它们,如果这些修改与即将 rebase 的内容冲突,则会提示错误并中止。
2、Git 首先会找到dev
和master
分支的最近共同祖先提交。把共同祖先提交到dev
最新提交的每个提交都换为一个补丁,并将这些补丁依次应用到master
的最新提交上。如果某个补丁应用失败(如冲突),Git 会暂停 rebase 并提示你解决冲突。解决冲突后,使用 git add .
标记解决,然后运行 git rebase --continue
继续;也可以选择跳过某次提交(–skip
)或中止整个 rebase(–abort
)。
3、所有补丁成功应用后,Git 会更新dev
的指针(HEAD
),使其指向新的提交。
4、由于提交历史已改变,远程仓库上的 dev 分支与本地不一致,必须使用强制推送。推荐使用 git push --force-with-lease
,而不是 git push --force
。
● (heads/dev) git rebase origin/release 将某分支的提交移到新基础上
● (heads/dev) git rebase -i origin/release 交互式地rebase
● (heads/dev) git rebase -i HEAD~3 交互式整理最近3个提交
git rebase -i 详解
git rebase发生冲突时 ☞ 撤销rebase,git rebase --abort
git rebase发生冲突时 ☞ 解决冲突,git add . && git rebase --continue
github上基于rebase的一次PR提交
rebase导致源分支修改丢失
参数 | 说明 | Git 引入年份 / 最低版本 |
---|---|---|
--exec "echo hahah" |
在交互式变基过程中,对每个提交应用后执行指定命令(如测试、lint),确保提交质量 | 2005 / Git v1.0 |
--ignore-date |
用当前时间覆盖原提交时间 | 2005 / Git v1.0 |
--onto <newbase> |
自定义变基的目标基准(指定新基底) | 2005 / Git v1.0 |
--abort |
中断变基,回退到变基前状态 | 2005 / Git v1.0 |
--continue |
解决冲突后继续变基流程 | 2005 / Git v1.0 |
--skip |
跳过当前冲突提交,继续后续变基 | 2005 / Git v1.0 |
--keep-empty |
保留空提交(默认跳过空提交) | 2007 / Git v1.5.0 |
--signoff |
给每个提交添加 Signed-off-by: 签名行 |
2007 / Git v1.5.0 |
--interactive / -i |
进入交互模式,手动编排提交 | 2007 / Git v1.5.4 |
--committer-date-is-author-date |
让提交者时间与作者时间一致,统一时间戳 | 2008 / Git v1.6.0 |
--no-ff |
变基时禁用快进合并(保留合并提交) | 无独立版本,继承merge --no-ff 逻辑,Git v1.0 + 支持 |
--autosquash / -a |
自动识别、合并标记过的 fixup 提交 |
2010 / Git v1.7.0 |
--rebase-merges / -r |
保留原分支合并结构,重放合并提交 | 2012 / Git v1.8.5 |
--preserve-merges |
在变基时尝试保留分支合并结构(通过重新创建合并提交),但不保留原合并提交的元数据(如提交信息、时间戳) ⚠️ 注意:此参数在 Git v2.0 后已被标记为 deprecated(不推荐使用),推荐使用 --rebase-merges 替代,后者能更完整地保留合并历史 |
2010 / Git v1.7.0(引入) 2012 / Git v1.8.5(被 --rebase-merges 替代) 2014 / Git v2.0(标记为 deprecated) |
--force-rebase |
强制对已有推送的分支执行变基(需谨慎) | 无独立版本,依赖push --force-with-lease 逻辑,Git v1.8.5+ 后更安全 |
–exec
git rebase -i HEAD~3 --exec "echo hahah"
pick be04f1b update
exec echo hahah
pick ddd538d update
exec echo hahah
pick a1c019c update
exec echo hahah
# $ git rebase -i HEAD~3 --exec "echo hahah"
# hint: Waiting for your editor to close the file... Can't find filter element
# Can't find filter element
# Executing: echo hahah
hahah
# Executing: echo hahah
hahah
# Executing: echo hahah
hahah
# Successfully rebased and updated refs/heads/dev.
–ignore-date
git rebase 会更新 committer date,不会更新 author date(作者时间)。 而 --ignore-date 不仅更新 committer date,还会覆盖 author date,让整个提交的时间看起来就像“现在写的”。
# $ git log --pretty=format:"%<(10)%h | %<(20)%D | %<(20)%ai | %<(20)%ci | %s"
# 6ed3bac | HEAD -> dev | 2025-06-28 14:43:54 +0800 | 2025-06-28 14:43:54 +0800 | update 5
# zhang@zhangziwa MINGW64 /d/gitPrac/ckck (dev)
$ git rebase release --ignore-date
# Current branch dev is up to date, rebase forced.
# Successfully rebased and updated refs/heads/dev.
# $ git log --pretty=format:"%<(10)%h | %<(20)%D | %<(20)%ai | %<(20)%ci | %s"
# e3f5262 | HEAD -> dev | 2025-06-28 14:53:19 +0800 | 2025-06-28 14:53:19 +0800 | update 5
–onto
git rebase --onto main develop feature
,将 feature 分支中从 develop 到 feature 的所有提交,重新应用到 main 上
–skip,–continue,–abort
git rebase 过程中用于处理冲突或中断操作时,有如下3个操作
git rebase --skip 跳过当前正在处理的提交,不将其应用到新的基础上
git rebase --continue 解决冲突并继续
git rebase --abort 中止整个变基
–keep-empty
在进行以下操作时,Git 默认会忽略所有空提交,加上 --keep-empty 参数后,Git 在重写历史时会 保留这些空提交,不会自动跳过它们。
git rebase
git rebase -i
–signoff
给每个提交添加 Signed-off-by:
签名行
# $ git log --pretty=format:"%<(10)%h | %<(20)%D | %<(20)%ai | %<(20)%ci | %s"
# 6ed3bac | HEAD -> dev | 2025-06-28 14:43:54 +0800 | 2025-06-28 14:43:54 +0800 | update 5
$ git rebase release --signoff
# Current branch dev is up to date, rebase forced.
# Successfully rebased and updated refs/heads/dev.
# $ git log --pretty=format:"%<(10)%h | %<(20)%D | %<(20)%ai | %<(20)%ci | %s - %b"
# 405bb6c | HEAD -> dev | 2025-06-28 14:43:54 +0800 | 2025-06-28 15:03:48 +0800 | update 5 - Signed-off-by: zhangziwa <zhangziwa@qq.com>
–committer-date-is-author-date
# $ git log --pretty=format:"%<(10)%h | %<(20)%D | %<(20)%ai | %<(20)%ci | %s"
# 6ed3bac | HEAD -> dev | 2025-06-28 14:43:54 +0800 | 2025-06-28 14:43:54 +0800 | update 5
$ git rebase release --signoff # git rebase 会更新 committer date,不会更新 author date(作者时间)
# $ git log --pretty=format:"%<(10)%h | %<(20)%D | %<(20)%ai | %<(20)%ci | %s"
# 815ac13 | HEAD -> dev | 2025-06-28 14:43:54 +0800 | 2025-06-28 15:15:58 +0800 | update 5
$ git rebase release --committer-date-is-author-date # 让提交者时间与作者时间一致,统一时间戳
# $ git log --pretty=format:"%<(10)%h | %<(20)%D | %<(20)%ai | %<(20)%ci | %s"
# 6ed3bac | HEAD -> dev | 2025-06-28 14:43:54 +0800 | 2025-06-28 14:43:54 +0800 | update 5
–no-ff
变基时禁用快进合并(保留合并提交)
–autosquash
当你使用 git commit --fixup
或 git commit --squash <commit>
创建了“修复提交”后,使用 git rebase -i --autosquash
自动识别并合并 fixup/squash 提交。
–preserve-merges(已废弃), --rebase-merges
–rebase-merges 的作用是:让 Git 在变基时记住你以前是怎么合并分支的,避免历史变得混乱或丢失重要信息。–preserve-merges 也是相同思路,但是已弃用。
原始结构如下:
A---B---C---D <-- main
\
E---F---M <-- feature
git rebase main
A---B---C---D <-- main
\
E'--F'--M' <-- feature E'相对于E表示sha变了
git rebase --preserve-merges main(已废弃)
A---B---C---D <-- main
\ \
E'---F'---M' <-- feature M'是对原始M的“复制”,可能无法准确反映合并逻辑,已废弃
git rebase --rebase-merges main
A---B---C---D <-- main
\ \
E'---F'---M' <-- feature M'是通过重新执行合并生成的新提交
git merge main
A---B-----C----D <-- main
\ \
D---E---M---N <-- feature
git merge feature
A---B---C---D---N <-- main
\ /
E---F---M <-- feature
–force-rebase
–force-rebase 的作用是:即使提交已经存在于目标分支中,也强制重新 apply 所有提交。
原始结构:
A---B---C <-- main
\
D---E <-- feature
git rebase main
A---B---C <-- main
\
D---E <-- feature (无变化)
git rebase --force-rebase main
A---B---C <-- main
\
D'---E' <-- feature