参数详情
--continue继续变基过程,在解决冲突后继续操作。--abort中止变基操作,将HEAD重置为原始分支。如果在开始变基时提供了分支,则HEAD将被重置到该分支。否则,HEAD将被重置为变基操作开始时的位置。--keep-empty在结果中保留与其父节点没有任何更改的提交。--skip通过跳过当前补丁来继续变基过程。-m, --merge使用合并策略进行变基。当使用默认的递归合并策略时,这允许变基感知上游的重命名操作。请注意,变基合并是通过在上游分支上重新应用工作分支的每个提交来实现的。因此,当发生合并冲突时,报告为“我们的”是到目前为止已重新应用的系列,从上游开始,“他们的”是工作分支。换句话说,两边的角色被交换了。--stat显示自上次变基以来上游更改的差异统计信息。差异统计信息也受rebase.stat配置选项的控制。-x, --exec command执行交互式变基,在每个提交之间停止并执行命令。
12.1 本地分支变基
变基会将一系列提交重新应用到另一个提交之上。
要对分支进行变基,先检出该分支,然后将其变基到另一个分支之上。
git checkout topic
git rebase master # 将当前分支变基到 master 分支
这会导致:
A---B---C topic
\
D---E---F---G master
变为:
A'--B'--C' topic
\
D---E---F---G master
重要:变基后,应用的提交将具有不同的哈希值。你不应该对已经推送到远程主机的提交进行变基。一个可能的后果是你将无法将本地变基后的分支推送到远程主机,此时你唯一的选项是 git push --force。
这些操作可以组合成一个命令,该命令在检出分支的同时立即对其进行变基:
git rebase master topic # 将 topic 分支变基到 master 分支
12.2 变基中的“我们的”和“他们的”,本地和远程
变基会切换“我们的”和“他们的”的含义:
git checkout topic
git rebase master # 将 topic 分支变基到 master 分支
HEAD 指向的内容是“我们的”。
变基的第一步是将 HEAD 重置到 master,然后从旧的 topic 分支中挑拣提交到新分支(旧 topic 分支中的每个提交都将被重写,并将具有不同的哈希值)。
在合并工具中使用的术语(不要与本地引用或远程引用混淆):
- 本地是 master(“我们的”),
- 远程是 topic(“他们的”)。
这意味着合并/差异工具将上游分支视为本地(master:你正在其上变基的分支),将工作分支视为远程(topic:正在变基的分支)。
在合并中:
c--c--x--x--x(*) <- 当前分支 topic ('*' = HEAD)
\
\--y--y--y <- 要合并的另一个分支
我们不更改当前分支 topic,因此我们仍然拥有我们正在工作的东西(并从另一个分支合并)。
c--c--x--x--x---------o(*) MERGE, 仍在 topic 分支上
\ ^ /
\ ours /
\
--y--y--y--/
^
theirs
在变基中:
但变基会切换两边,因为变基的第一步是切换到上游分支以在其上重新应用当前提交!
c--c--x--x--x(*) <- 当前分支 topic ('*' = HEAD)
\
\--y--y--y <- 上游分支
git rebase 上游会首先将 HEAD 设置为上游分支,因此与之前的“当前”工作分支相比,“我们的”和“他们的”发生了交换。
c--c..x..x..x <- 旧“他们的”提交,现在是“幽灵”,可通过“引用日志”访问
\
\--y--y--y--x'--x'--x'(*) <- topic,所有 x' 都重新应用后
^ 将分支 topic 指向此提交
|
上游分支
12.3 交互式变基
此示例旨在说明如何在交互模式下使用 git rebase。假设你对 git rebase 是什么以及它做什么有基本的了解。
使用以下命令启动交互式变基:
git rebase -i
-i 选项表示交互模式。在交互式变基中,用户可以更改提交消息,以及重新排序、拆分和/或合并(合并为一个)提交。
假设你想重新排列你最后三个提交。为此,你可以运行:
git rebase -i HEAD~3
执行上述指令后,将在文本编辑器中打开一个文件,在该文件中你可以选择如何重新应用你的提交。为了本示例的目的,只需更改提交的顺序,保存文件并关闭编辑器。这将启动你指定的顺序的变基。如果你检查 git log,你会看到你的提交按你指定的新顺序排列。
更改提交消息
现在,你决定其中一个提交消息过于模糊,希望它更具描述性。让我们再次使用相同的命令检查最后三个提交。
git rebase -i HEAD~3
这次,不是重新排列提交的顺序,而是将默认的 pick 更改为 reword,以更改你希望更改消息的提交。
关闭编辑器后,变基将启动,并将在你希望更改的特定提交消息处停止。这将让你将提交消息更改为任何你想要的内容。更改消息后,只需关闭编辑器即可继续。
更改提交的内容
除了更改提交消息外,你还可以调整提交所做的更改。为此,只需将 pick 更改为 edit。Git 将在到达该提交时停止,并在暂存区提供该提交的原始更改。你现在可以通过取消暂存它们或添加新更改来调整这些更改。
一旦暂存区包含你希望在该提交中的所有更改,提交更改。旧的提交消息将显示并可以更改以反映新的提交。
将单个提交拆分为多个
假设你已经提交了一些更改,但后来决定这些更改可以拆分为两个或更多个提交。使用与之前相同的命令,将 pick 替换为 edit,然后按回车。
现在,Git 将在你标记为编辑的提交处停止,并将所有内容放入暂存区。从这一点开始,你可以运行 git reset HEAD^ 将提交放入工作目录。然后,你可以按不同顺序添加和提交文件——最终将单个提交拆分为 n 个提交。
将多个提交合并为一个
假设你完成了一些工作并有多个提交,你认为这些提交可以合并为一个提交。为此,你可以执行 git rebase -i HEAD~3,将 3 替换为适当的提交数量。
这次将 pick 替换为 squash。在变基过程中,你指示要合并的提交将被合并到之前的提交之上,从而将它们合并为一个提交。
12.4 变基到初始提交
自 Git 1.7.12 版本以来,可以变基到根提交。根提交是仓库中第一个提交,通常不能编辑。使用以下命令:
git rebase -i --root
12.5 配置自动暂存
在使用变基进行本地更改时,自动暂存是一个非常有用的配置选项。很多时候,你可能需要从上游分支引入提交,但尚未准备好提交。
然而,Git 不允许在工作目录不干净的情况下开始变基。自动暂存来救场:
git config --global rebase.autostash # 一次性配置
git rebase @{u} # 示例:在上游分支上执行变基
自动暂存将在变基完成后应用。无论变基是否成功完成,或者是否被中止,自动暂存都会被应用。如果变基成功,且基础提交因此更改,那么自动暂存和新提交之间可能会有冲突。在这种情况下,你必须在提交之前解决冲突。这与你手动暂存然后应用时没有区别,因此自动执行并没有任何缺点。
12.6 在变基期间测试所有提交
在提交拉取请求之前,确保每个提交都能成功编译并通过测试是有用的。我们可以使用 -x 参数自动完成此操作。
例如:
git rebase -i -x make
这将执行交互式变基,并在每个提交后停止以执行 make。如果 make 失败,Git 将停止并给你一个机会修复问题并修改提交,然后再继续选择下一个提交。
12.7 代码审查前的变基
概述
目标是将你分散的提交重新组织成更有意义的提交,以便于代码审查。如果一次涉及太多文件的更改层太多,代码审查会更困难。如果你能将按时间顺序创建的提交重新组织成主题提交,那么代码审查过程会更轻松(并且可能少一些漏洞)。
这个过于简化的示例不是使用 Git 进行更好代码审查的唯一策略。这是我用来做代码审查的方式,也是为了激励其他人考虑如何使代码审查和 Git 历史更容易/更好。
这也展示了变基的强大功能。
此示例假设你了解交互式变基。
假设:
- 你正在 master 分支上创建一个功能分支。
- 你的功能有三个主要层次:前端、后端、数据库。
- 你在功能分支上做了很多提交。每个提交同时更改多个层次。
- 你希望(最终)在你的分支中只有三个提交。
- 一个包含所有前端更改。
- 一个包含所有后端更改。
- 一个包含所有数据库更改。
策略
我们将按主题将按时间顺序的提交重新组织成“主题”提交。
首先,将所有提交拆分为多个更小的提交——每个提交只包含一个主题(在我们的示例中,主题是前端、后端、数据库更改)。
然后,重新排序我们的主题提交并将它们“合并”为单个主题提交。
示例
$ git log --oneline master..
975430b db adding works: db.sql logic.rb
3702650 trying to allow adding todo items: page.html logic.rb
43b075a first draft: page.html and db.sql
$ git rebase -i master
这将在文本编辑器中显示:
pick 43b075a first draft: page.html and db.sql
pick 3702650 trying to allow adding todo items: page.html logic.rb
pick 975430b db adding works: db.sql logic.rb
将其更改为:
e 43b075a first draft: page.html and db.sql
e 3702650 trying to allow adding todo items: page.html logic.rb
e 975430b db adding works: db.sql logic.rb
然后 Git 将逐个应用提交。每个提交后,它将显示一个提示,然后你可以执行以下操作:
Stopped at 43b075a92a952faf999e76c4e4d7fa0f44576579... first draft: page.html and db.sql
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
$ git status
rebase in progress; onto 4975ae9
You are currently editing a commit while rebasing branch 'feature' on '4975ae9'.
(use "git commit --amend" to amend the current commit)
(use "git rebase --continue" once you are satisfied with your changes)
nothing to commit, working directory clean
$ git reset HEAD^ # 这将“取消提交”此提交中的所有更改。
$ git status -s
M db.sql
M page.html
$ git add db.sql # 现在我们将创建更小的主题提交
$ git commit -m "first draft: db.sql"
$ git add page.html
$ git commit -m "first draft: page.html"
$ git rebase --continue
然后你将对每个提交重复这些步骤。最后,你将得到以下结果:
$ git log --oneline
0309336 db adding works: logic.rb
06f81c9 db adding works: db.sql
3264de2 adding todo items: page.html
675a02b adding todo items: logic.rb
272c674 first draft: page.html
08c275d first draft: db.sql
现在我们再次运行变基以重新排序和合并:
$ git rebase -i master
这将在文本编辑器中显示:
pick 08c275d first draft: db.sql
pick 272c674 first draft: page.html
pick 675a02b adding todo items: logic.rb
pick 3264de2 adding todo items: page.html
pick 06f81c9 db adding works: db.sql
pick 0309336 db adding works: logic.rb
将其更改为:
pick 08c275d first draft: db.sql
s 06f81c9 db adding works: db.sql
pick 675a02b adding todo items: logic.rb
s 0309336 db adding works: logic.rb
pick 272c674 first draft: page.html
s 3264de2 adding todo items: page.html
注意:确保你告诉 Git 变基按它们被按时间顺序提交的顺序应用/合并更小的主题提交。否则,你可能会遇到虚假的、不必要的合并冲突。
当交互式变基完成时,你将得到以下结果:
$ git log --oneline master..
74bdd5f adding todos: GUI layer
e8d8f7e adding todos: business logic layer
121c578 adding todos: DB layer
总结
你现在已将按时间顺序的提交重新组织成了主题提交。在现实生活中,你可能不需要每次都这样做,但当你确实需要这样做时,你现在可以做到。此外,希望你对 Git 变基有了更多的了解。
12.8 中止交互式变基
你已开始了一个交互式变基。在你选择提交的编辑器中,你决定有些事情出错了(例如,缺少一个提交,或者你选择了错误的变基目标),并且你希望中止变基。
为此,只需删除所有提交和操作(即,不以 # 符号开头的所有行),变基将被中止!
编辑器中的帮助文本实际上提供了这个提示:
# Rebase 36d15de..612f2f7 onto 36d15de (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
12.9 设置 git-pull 以自动执行变基而不是合并
如果你的团队遵循基于变基的工作流,那么将 git 设置为在 git pull 期间自动执行变基操作而不是合并操作可能更有优势。
要将每个新分支设置为自动变基,将以下内容添加到你的 .gitconfig 或 .git/config 文件中:
[branch]
autosetuprebase = always
命令行:
git config [--global] branch.autosetuprebase always
或者,你可以将 git pull 命令设置为始终表现得好像传递了 --rebase 选项:
[pull]
rebase = true
命令行:
git config [--global] pull.rebase true
12.10 变基后的推送
有时你需要通过变基重写历史,但 git push 会抱怨这样做是因为你重写了历史。
这可以通过 git push --force 解决,但考虑使用 git push --force-with-lease,表明你希望如果本地远程跟踪分支与远程分支不同,则推送失败,例如,其他人在此前的拉取之后推送到远程。这可以避免无意中覆盖了他人的最近推送。
注意:git push --force 甚至 --force-with-lease 都是一个危险的命令,因为它会重写分支的