为什么 Git Rebase 会丢弃我的提交?

9

我正在尝试将一个分支基于主分支进行变基,这是我以前做过无数次的事情。但是今天它不起作用了:

> git status
On branch mystuff
Your branch and 'master' have diverged,
and have 6 and 2 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

nothing to commit, working directory clean

> git rebase
First, rewinding head to replay your work on top of it...

> git status
On branch mystuff
Your branch is up-to-date with 'master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    [a directory from the project]

nothing added to commit but untracked files present (use "git add" to track)

>

一切开始正常,但是Git完成rebase后,没有把我的提交放到那里;我的分支mystuff与master相同的提交。显然的结论是,我的提交已经在master的某个地方了。但是我发誓它们不在,我已经回溯了历史记录。这些提交在其他一些功能分支中,但它们不在master的历史记录中。(并且当我检出master时,可以根据文件状态知道它们不在master中)。所以,如果提交还没有在上游历史记录中,为什么git rebase会拒绝将我的提交放在顶部呢?奇怪的是,如果我一个一个地将提交拾取到主分支上,那么就能解决这个问题。然后我可以将我的mystuff分支移动到最后,然后将master恢复到原来的位置。(但是我为什么要这样做呢?)编辑:git rebase的文档如下:
“当前分支被重置为或(如果提供了--onto选项)。这与git reset --hard (或)具有完全相同的效果。ORIG_HEAD被设置为指向重置之前分支的末端。 接下来,之前保存在临时区域中的提交将按顺序逐个重新应用于当前分支。请注意,任何在HEAD中引入与HEAD.. 中的提交相同的文本更改的提交都将被省略(即,已经使用不同的提交消息或时间戳在上游接受的补丁将被跳过)。”
这将符合我目前看到的行为,如果提交实际上存在于上游...但它们不存在。正如评论中提到的那样,git rebase master可以正确工作并应用所有提交。但是没有master的git rebase却不能,尽管master被设置为上游分支。我的分支配置如下:
[branch "master"]
    remote = origin
    merge = refs/heads/master
[branch "mystuff"]
    remote = .
    merge = refs/heads/master

共享整个目录是可能的吗?如果你非常确定你正在做正确的事情,这可能有助于找到错误或其他问题。 - mgarciaisaia
@Irineau,请看编辑。未跟踪的目录是因为没有被变基的提交之一是更改.gitignore以忽略该目录。 - Ryan Lundy
@Cupcake,好问题。git rebasegit rebase --onto master都像上面一样失败了...但是git rebase master可以工作!但是为什么呢?如果master是上游分支,那么git rebase不应该需要分支参数吗? - Ryan Lundy
@joshtkling,好的。请看上面。 - Ryan Lundy
毫无头绪,基于那个。我看不出为什么 git rebase 会有任何不同于 git rebase master 的表现。 - joshtkling
显示剩余13条评论
3个回答

5
这个问题在某次 Git 升级后至少困扰了我十几次。现在,git rebasegit rebase master 之间有了一个区别:前者改用了同样花哨的“fork-point”机制。这个问题的答案中有详细的解释: 今天,我第一次找到了具体复现这个问题的步骤。
我的情况是:我在 master 分支上有 4 个提交,我决定将它们移动到一个 topic 分支上,并且还要对它们进行重新排序。如果我这样做...
  1. Create a new topic branch, tracking the current branch (master)

    git checkout -b topic -t
    
  2. Rewind master back :

    git checkout master
    git reset --hard origin/master
    
  3. Reorder the commits on topic

    git checkout topic
    git status  # "Your branch is ahead of 'master' by 4 commits" Good!
    git rebase --interactive
    

...然后交互式的变基屏幕会显示出这个充满威胁性的提交列表:

    # no-op

哎呀,我保存了文件并继续操作。是的,看起来git又把我的工作丢掉了。topic现在指向与masterorigin/master相同的提交。

因此,我猜你遇到这个问题的原因可能是:

  1. 升级了git

  2. 你在master分支上执行了类似于我的第2步的操作。

在我这个小白的理解中,fork-point机制会通过引用日志向后搜索,并注意到这些提交已经从上游分支中删除,得出结论,为了使你的主题分支“保持最新状态”,它们也应该在那里删除。

解决方案是,不要:

git rebase

使用以下其中一种方法:

git rebase --no-fork-point
git rebase master

但我猜想和我一样,你不会每次都这么做。(我的意思是,我们设置上游分支是有原因的,对吧?)所以你只需要学会识别这种情况何时发生,使用git refloggit reset --hard进行恢复,然后再使用上述命令。

然而,你现在需要小心了——我认为这非常危险。我曾经有过重建一个大分支并且几个提交从开头悄悄消失的时候,我几天都没有注意到!我相当熟悉使用git reflog进行灾难恢复,但我不确定每个人都能做到。我想知道git是否开始变得聪明了。


下一个问题:有没有git配置选项可以将行为改回来?(奖励分:也许只针对本地分支?)当我搜索“git help”时,我找不到它的提及。 - Luke Usherwood
3
啊哈!没错,那就是问题所在。可以将其重新描述为“注意到提交记录已经从那里被删除”变成“注意到提交记录曾经与上游分支共享,然后假设它们被替换为“改进的拷贝”。没有自动的--no-fork-point选项,但你可以添加一个:请参见rebase脚本,大约在第454行,使用test "$fork_point" = auto && fork_point=t进行替换,如果设置为auto,则检查配置文件以获取最终值。 - torek
“--no-fork-point” 对我来说并没有改变结果,但这个解释仍然非常有帮助。在使用 “reflog” 恢复了我的提交之后,最终我选择将其 cherry-pick 到一个新的基于 master 的分支上。 - allieferr
@x-yuri 因为最终该主题将合并到主分支中。我不确定这让你困惑了什么。 - Ryan Lundy
@RyanLundy 嗯,通常上游分支是远程分支。你的设置提供了哪些好处? - x-yuri
显示剩余3条评论

1

运行git branch -vv命令以查看上游分支。听起来你的mystuff上游分支不是你想象中的那样。也许你遇到了编辑错误,在配置文件中有多个[branch "mystuff"]条目?


1
我本来想问这个问题,但当我试图提问时,我找到了答案。
当需要执行变基操作时,您可能会遇到以下两种情况。请考虑以下历史记录:
A---B---C master

协作者1更改提交C,添加提交D并执行git push -f

A---B---C'---D master

合作者 #2 添加提交 E 并执行 git fetch

A---B---C'---D origin/master
     \
      C---E master

你可能会说,“B是最后一个共同祖先,C和C'不同,我想保留所有更改。” 那么你期望的历史记录是:
A---B---C'---D---C"---E" master

另一种思考方式是:“其他人更改了 C 提交,肯定有原因,我希望使用这个新版本的 C 替换掉我的版本。” 那么您心中所想的就是:
A---B---C'---D---E" master

这意味着当上游删除提交时,当您执行变基操作时,本地也会发生这种情况。
当您明确指定上游时,将选择前一种模式:
$ git rebase origin/master

后者(“--fork-point”模式):
$ git -c pull.rebase=true pull
$ git pull --rebase
$ git rebase
$ git rebase --fork-point origin/master

这里提供了更详细的描述链接。一个脚本可以重现该问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接