问题描述
我在使用git rebase
的--keep-empty
选项时遇到了一些麻烦,不知道我是否误解了该选项的作用,还是有错误.
I have some trouble using the --keep-empty
option of git rebase
, and I'mnot sure whether I'm misunderstanding what this option does, or there's a bug.
这是一个最小的示例:
-
创建一个新的Git存储库和一个初始的,不相关的提交.
Create a new Git repository and an initial, unrelated commit.
$ git init
$ echo something >base.txt
$ git add base.txt
$ git commit -m 'some base commit to not run into the root corner case'
创建一个新的提交,添加两个新文件.
Create a new commit which adds two new files.
$ echo A >a.txt; echo B >b.txt
$ git add a.txt b.txt
$ git commit -m 'add A and B'
修改其中一个文件.
Modify one of the files.
$ echo A1 >a.txt
$ git add a.txt
$ git commit -m 'change A'
修改其他文件.
Modify the other file.
$ echo B1 >b.txt
$ git add b.txt
$ git commit -m 'change B'
变基
$ git checkout -b rebased master
$ git rebase --keep-empty -i :/base
…选择添加到A
和B
的提交到edit
,并进行更改以仅添加B
(在实际情况下,原因可能是A
是机密的):
… choosing to edit
the commit where A
and B
are added, and changing it so that only B
is added (in a real scenario the reason might be that A
is confidential):
$ git rm a.txt
$ git commit --amend
$ git rebase --continue
自然,现在修改了A
的下一个提交现在会产生冲突:
Naturally, the next commit where A
is modified now gives a conflict:
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
的修改版本:
… choosing to not add the modified version of 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
的提交现在为空:
The commit where A
was modified is now empty:
$ git diff --cached
# nothing
…并完成变基:
$ git rebase --continue
Successfully rebased and updated refs/heads/rebased.
问题
所以现在我有两个版本的历史记录,不同之处在于其中一个没有A
痕迹.但是,因为我选择了--keep-empty
选项,所以我仍然希望rebased
中存在一个空提交,这表明A
如果已经存在,将被修改.
Question
So now I have two versions of my history, with the difference that there is no trace of A
in one of them. 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.
但显然并非如此:
$ 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
应该执行的操作还是不起作用正确吗?
Is this not what --keep-empty
is supposed to do, or does it not workcorrectly?
相关:基于根目录并保留空提交是一个非常相似的问题,但是它涉及到--root
极端情况,在此我明确避免了这种情况.它没有答案,只有一些意见表明我在这里显示的内容应该起作用.另一个区别是,在另一个问题中,提交首先是空的,而在这里,只有在解决冲突后,提交才变为空.
Related: Rebase on the root and keep empty commits is a very similar question, but it involves the --root
corner case which I explicitly avoided here. And it has no answer, only some comments which suggest that what I'm showing here should work. Another difference is that in the other question the commit is empty in the first place, while here it only becomes empty after resolving a conflict.
推荐答案
由于某种特性,它有点像个bug. :-)
It's sort of a bug, due to something that is sort of a feature. :-)
当您运行交互式rebase并暂停"时,实际上它会完成 ,但是会留下一些文件让 new git rebase
意识到它的作用更大毕竟是延续.就目前而言,这很好.您将需要稍后运行git rebase --continue
来开始新的重新设置并告诉它:您不是真正的新手,请阅读状态并像继续进行原来的重新设置一样进行操作.
When you run interactive rebase and it "pauses", in reality, it finishes, but leaves some files around to let a new git rebase
realize that it's more of a continuation after all. This is fine as far as it goes; you will need to run git rebase --continue
later to start a new rebase and tell it: You're not really new, go read the state and act like you're continuing the original rebase.
然后,让我们看一下交互式变基".实际上,这主要是一系列的选择操作:pick
命令从字面上指示旧的rebase shell脚本(现在正在逐步淘汰)运行git cherry-pick
.
And, let's look at an "interactive rebase". In reality this is mostly a series of cherry-pick operations: the pick
command literally instructs the old rebase shell script—which is being phased out now—to run git cherry-pick
.
好的,到目前为止没什么大不了的.但是,让我们考虑为什么停止交互式交互.有两个原因:
OK, no big deal so far. But let's consider why an interactive rebase stops. There are two reasons:
-
您已将提交标记为编辑".实际上,它会提交樱桃拾取",并停止让您修改提交或以其他方式大惊小怪.
You marked a commit "edit". It actually commits the cherry-pick, and stops to let you amend the commit or otherwise fuss with it.
或者,有一个问题(例如合并冲突)迫使停止.
Or, there was a problem—such as a merge conflict—that forced the stop.
在情况(1)中,当您运行git rebase --continue
时,Git应该不进行自己的提交.
In case (1), when you run git rebase --continue
, Git should not make its own commit.
在情况(2)中,当您运行git rebase --continue
时,Git 应该进行自己的提交.也就是说,除非(这是功能部分),否则您应该先进行自己的提交.在这种情况下,对于情况(2),Git应该不进行自己的提交.
In case (2), when you run git rebase --continue
, Git should make its own commit. That is, it should unless—this is the feature part—you make your own commit first. In that case, for case (2) Git should not make its own commit.
Git可能并且应该记录停止的原因,以便将这两种情况区分开来……但事实并非如此.相反,它只是查看--continue
上的状态.
Git could, and perhaps should, record the reason-for-stoppage so as to tell these two cases apart ... but it doesn't. Instead, it just looks at the state on --continue
.
对于 non 交互式的rebase,Git知道它只会在冲突时停止,因此它知道尝试进行提交,并抱怨没有要提交的内容.这是--keep-empty
或-k
标志有用的地方. (在内部,非交互式案例默认情况下使用git format-patch
和git am
,尽管您可以强制它使用带有--preserve-merges
的交互式机器.在这里我提到这一点是因为这是实施原因 Git必须知道您是否处于交互"状态:经常发生的情况是,Git让实现来指示行为.如果Git不需要这种区别,则--continue
可以使用相同的代码交互式和非交互式rebase,但是Git 不需要,因此不需要使用相同的代码.)
For a non-interactive rebase, Git knows that it only stops on conflicts, so it knows to try to make a commit, and complain if there is nothing to commit. This is where the --keep-empty
or -k
flag is useful. (Internally, the non-interactive case uses git format-patch
and git am
by default, although you can force it to use the interactive machinery with --preserve-merges
for instance. I mention this here as it's an implementational reason that Git has to know whether you're being "interactive": as so often happens, here Git lets the implementation dictate the behavior. If Git didn't need this distinction, a --continue
could just use the same code for interactive and non-interactive rebase, but Git does need the distinction, and hence doesn't use the same code.)
但是,对于交互式变基,Git 允许在情况下(2),即在运行git rebase --continue
之前(这是功能部件),可以进行自己的提交.如果是这样,--continue
步骤应仅继续进行下一个提交.因此,--continue
仅检查现在是否有要提交的内容,而不是检查情况(1)与情况(2)是否退出了较早的交互式rebase.这个简单的实现技巧可以启用该功能,但也意味着--keep-empty
在这里无法工作:Git只是不知道区别.
For an interactive rebase, though, Git allows you to make your own commit in case (2), just before running git rebase --continue
(this is the Feature part). If so, the --continue
step should just move on to the next commit. So --continue
just checks whether there's something to commit now, rather than whether the earlier interactive rebase exited for case (1) vs case (2). This simple implementation trick enables the feature, but also means that --keep-empty
cannot work here: Git just doesn't know the difference.
解决方法是在解决合并后执行自己的git commit --allow-empty
.换句话说,使用您可以自己提交"功能将案例(2)转换为模拟案例(1).
The workaround is to do your own git commit --allow-empty
after resolving your merge. In other words, convert case (2) into a simulated case (1), using the "you may make your own commit" feature.
这篇关于交互式重新设置后,即使使用了--keep-empty,也删除了空提交的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!