交互式变基后删除了空提交,即使使用了 --keep-empty

Empty commits removed after interactive rebase, even though --keep-empty is used

我在使用 git rebase--keep-empty 选项时遇到了一些问题,我 不确定我是否误解了这个选项的作用,或者有一个错误。

这是一个最小的例子:

设置

  1. 创建一个新的 Git 存储库和一个初始的、不相关的提交。

    $ git init
    $ echo something >base.txt
    $ git add base.txt
    $ git commit -m 'some base commit to not run into the root corner case'
    
  2. 创建一个添加两个新文件的新提交。

    $ echo A >a.txt; echo B >b.txt
    $ git add a.txt b.txt
    $ git commit -m 'add A and B'
    
  3. 修改其中一个文件。

    $ echo A1 >a.txt
    $ git add a.txt
    $ git commit -m 'change A'
    
  4. 修改其他文件。

    $ echo B1 >b.txt
    $ git add b.txt
    $ git commit -m 'change B'
    

变基

$ git checkout -b rebased master
$ git rebase --keep-empty -i :/base

… 选择 edit 添加了 AB 的提交,并更改它以便只添加 B (在真实场景中,原因可能 A 是保密的):

$ git rm a.txt
$ git commit --amend
$ git rebase --continue

自然地,修改 A 的下一个提交现在会产生冲突:

error: could not apply 182aaa1... change A

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply 182aaa1701ad100fc02a5d5500cacebdd317a24b... change A

…选择不添加a.txt的修改版本:

$ git mergetool
Merging:
a.txt

Deleted merge conflict for 'a.txt':
  {local}: deleted
  {remote}: modified file
Use (m)odified or (d)eleted file, or (a)bort? d

修改 A 的提交现在为空:

$ git diff --cached
# nothing

…并完成变基:

$ git rebase --continue
Successfully rebased and updated refs/heads/rebased.

问题

所以现在我的历史有两个版本,不同的是其中一个版本没有 A 的痕迹。但是,因为我选择了 --keep-empty 选项,所以我仍然希望 rebased 中存在一个空提交,这会告诉我如果 A 在那里,它会被修改。

但显然,事实并非如此:

$ git log --oneline master
f893569 change B
182aaa1 change A
3340b71 add A and B
38cb5da some base commit to not run into the root corner case

$ git log --oneline rebased
73a2c05 change B
55f502b add A and B
38cb5da some base commit to not run into the root corner case

这不是--keep-empty应该做的,还是不起作用 正确吗?


相关:Rebase on the root and keep empty commits 是一个非常相似的问题,但它涉及我在此处明确避免的 --root 极端情况。它没有答案,只有一些评论表明我在这里展示的内容应该有效。另一个区别是在另一个问题中提交首先是空的,而这里只有在解决冲突后才变为空。

这是一个错误,由于某种功能。 :-)

当您 运行 交互式 rebase 和它 "pauses" 时,实际上,它 完成 ,但留下一些文件让 new git rebase 意识到它毕竟更像是一个延续。就目前而言,这很好;您稍后需要 运行 git rebase --continue 来开始一个新的变基并告诉它:您并不是真正的新手,去阅读状态并表现得就像您在继续原来的变基一样.

然后,让我们看一个 "interactive rebase"。实际上,这主要是一系列的挑选操作:pick 命令从字面上指示旧的 rebase shell 脚本(现在正在逐步淘汰)到 运行 git cherry-pick .

好的,到目前为止没什么大不了的。但是让我们考虑一下 为什么 交互式变基停止。有两个原因:

  1. 您标记了一个提交 "edit"。它实际上提交了 cherry-pick,并停止让您修改提交或以其他方式大惊小怪。

  2. 或者,有一个问题——例如合并冲突——迫使停止。

在情况 (1) 中,当您 运行 git rebase --continue 时,Git 应该 自己提交。

在情况 (2) 中,当您 运行 git rebase --continue、Git 应该 进行自己的提交。也就是说,它应该除非——这是特性部分——你先自己提交。在那种情况下,对于情况 (2) Git 应该 而不是 自己提交。

Git 可以,也许应该记录停止原因,以便区分这两种情况……但事实并非如此。相反,它只查看 --continue.

上的状态

对于-interactive rebase,Git知道它只会在冲突时停止,所以它知道尝试提交,如果有冲突则抱怨没有什么可承诺的。这是 --keep-empty-k 标志有用的地方。 (在内部,非交互式案例默认使用 git format-patchgit am,尽管您可以强制它使用带有 --preserve-merges 的交互式机制。我在这里提到这个是因为它是 实施原因 Git 必须知道您是否正在 "interactive":正如经常发生的那样,这里 Git 让实施决定行为。如果Git 不需要这种区别,--continue 可以只使用相同的代码进行交互式和非交互式 rebase,但是 Git 需要区别,因此不使用相同的代码。)

但是,对于交互式变基,Git 允许 您在情况 (2) 之前进行自己的提交,就在 运行ning git rebase --continue(这是特征部分)。如果是这样,--continue 步骤应该继续进行下一次提交。所以 --continue 只是检查现在是否有东西要提交,而不是早期的交互式 rebase 是否退出案例 (1) 与案例 (2)。这个简单的实现技巧启用了该功能,但也意味着 --keep-empty 不能 在这里工作:Git 只是不知道区别。

解决方法是在解决合并问题后自行 git commit --allow-empty。换句话说,使用 "you may make your own commit" 功能将案例 (2) 转换为模拟案例 (1)。

However, because I chose the --keep-empty option, I still expect an empty commit to exist in rebased, which would show me that A would have been modified, had it been there.

But apparently, this is not the case:

使用 Git 2.18(2018 年第 2 季度)仔细检查,考虑到如果另一方包含空提交(由于“[=13]”,“git rebase --keep-empty”仍然删除空提交=]" 检查),已更正

参见 commit 3d94616, commit 76ea235, commit bb2ac4f (20 Mar 2018) by Phillip Wood (phillipwood)
(由 Junio C Hamano -- gitster -- in commit d892bee 合并,2018 年 4 月 25 日)

rebase -i --keep-empty: don't prune empty commits

If there are empty commits on the left hand side of $upstream...HEAD then the empty commits on the right hand side that we want to keep are pruned by --cherry-pick.
Fix this by using --cherry-mark instead of --cherry-pick and keeping the commits that are empty or are not marked as cherry-picks.

并且:

rebase --keep-empty: always use interactive rebase

rebase --merge 接受 --keep-empty 但忽略它,方法是使用 隐式交互变基,用户仍然得到重命名检测 基于合并的 rebase,但具有 --keep-empty 支持。

If rebase --keep-empty without --interactive or --merge stops for the user to resolve merge conflicts then 'git rebase --continue' will fail. This is because it uses a different code path that does not create $git_dir/rebase-apply.
As rebase --keep-empty was implemented using cherry-pick it has never supported the am options and now that interactive rebases support --signoff there is no loss of functionality by using an implicit interactive rebase.


注意:这是 Git 2.18 中添加到 git rebase 的更大功能的一部分:
参见“What exactly does Git's “rebase --preserve-merges” do (and why?)”。
使用 git --rebase-merges(最终将取代旧的 git --preserve-merges),您现在可以在其他地方重新设置提交图的整个拓扑结构。


在 Git 2.27(2020 年第二季度)中,“git rebase”(再次)学会尊重“--no-keep-empty”,这让用户可以丢弃空的提交开始(而不是那些因为变基而变空的)。

The interactive rebase also marks commits that are empty in the todo.

参见 commit 50ed761, commit b9cbd29, commit 1b5735f (11 Apr 2020) by Elijah Newren (newren)
(由 Junio C Hamano -- gitster -- in commit c7d8f69 合并,2020 年 4 月 22 日)

rebase: reinstate --no-keep-empty

Reported-by: Bryan Turner
Reported-by: Sami Boukortt
Signed-off-by: Elijah Newren

Commit d48e5e21da ("rebase (interactive-backend): make --keep-empty the default", 2020-02-15, Git v2.26.0-rc0 -- merge listed in batch #8) turned --keep-empty (for keeping commits which start empty) into the default.

The logic underpinning that commit was:

  1. 'git commit' errors out on the creation of empty commits without an override flag
  2. Once someone determines that the override is worthwhile, it's annoying and/or harmful to required them to take extra steps in order to keep such commits around (and to repeat such steps with every rebase).

While the logic on which the decision was made is sound, the result was a bit of an overcorrection.

Instead of jumping to having --keep-empty being the default, it jumped to making --keep-empty the only available behavior.

There was a simple workaround, though, which was thought to be good enough at the time.

People could still drop commits which started empty the same way the could drop any commits: by firing up an interactive rebase and picking out the commits they didn't want from the list.

However, there are cases where external tools might create enough empty commits that picking all of them out is painful.

As such, having a flag to automatically remove start-empty commits may be beneficial.

Provide users a way to drop commits which start empty using a flag that existed for years: --no-keep-empty.

Interpret --keep-empty as countermanding any previous --no-keep-empty, but otherwise leaving --keep-empty as the default.

This might lead to some slight weirdness since commands like:

git rebase --empty=drop --keep-empty
git rebase --empty=keep --no-keep-empty

look really weird despite making perfect sense (the first will drop commits which become empty, but keep commits that started empty; the second will keep commits which become empty, but drop commits which started empty).

However, --no-keep-empty was named years ago and we are predominantly keeping it for backward compatibility; also we suspect it will only be used rarely since folks already have a simple way to drop commits they don't want with an interactive rebase.

我也遇到了这个问题

  • 3328dbe - 为 jpa 添加 hello world 测试代码 — Minghui Ma (HEAD) -(14 分钟前)
  • 3fd2d95 - 初始化空提交

当我使用 cmd 时“git rebase -i --root --keep-empty” 没有提交“3fd2d95 - 初始化空提交”

所以我强制在第一行插入一行"pick 3fd2d95 init empty commit"。