关于 git rebase 到选项的一些问题

Posted

技术标签:

【中文标题】关于 git rebase 到选项的一些问题【英文标题】:Some question about git rebase onto option 【发布时间】:2022-01-19 06:42:57 【问题描述】:

主分支:

百灵枝:

变基后 奇怪!,lark_file 在哪里?嗯,应该是三个文件(init、main_file、lark_file)吧?

创建环境的脚本。

#!/bin/env bash
git init
touch init
git add -- .
git commit -am init
echo a >> main_file
git add --all
git commit -am 'main a'
echo b >> main_file
git commit -am 'main b'
echo c >> main_file
git commit -am 'main c'
echo d >> main_file
git commit -am 'main d'
echo e >> main_file
git commit -am 'main e'

git checkout HEAD~5

git checkout -B lark
echo a >> lark_file
git add --all
git commit -am 'lark a'
echo b >> lark_file
git commit -am 'lark b'
echo c >> lark_file
git commit -am 'lark c'
echo d >> lark_file
git commit -am 'lark d'
echo e >> lark_file
git commit -am 'lark e'
echo f >> lark_file
git commit -am 'lark f'

【问题讨论】:

【参考方案1】:

TL;DR:由于 rebase 的重命名检测,Git 将更改应用到错误的文件。 (--onto 标志不是获得此效果所必需的。您只需要有一个差异,在其中 Git 会产生错误/不正确的重命名。)

首先,让我稍微修改一下您的复制器,以便每个人都可以使用它:

$ cat repro.sh
#! /bin/sh -e
mkdir t
cd t
git init
touch init
git add -- .
git commit -am init
echo a >> main_file
git add --all
git commit -am 'main a'
echo b >> main_file
git commit -am 'main b'
git tag tag-onto
echo c >> main_file
git commit -am 'main c'
echo d >> main_file
git commit -am 'main d'
echo e >> main_file
git commit -am 'main e'

git checkout HEAD~5

git checkout -B lark
echo a >> lark_file
git add --all
git commit -am 'lark a'
echo b >> lark_file
git commit -am 'lark b'
echo c >> lark_file
git commit -am 'lark c'
echo d >> lark_file
git commit -am 'lark d'
echo e >> lark_file
git commit -am 'lark e'
echo f >> lark_file
git commit -am 'lark f'

git rebase --onto tag-onto HEAD~3

这有一些简单的变化:它在其中创建了一个临时目录 tcd-s,这样我们就可以在完成后删除临时目录,并且复制器脚本本身不会不要卡在存储库中。更有用的是,它将最后一个 git rebase 更改为:

git rebase --onto tag-onto HEAD~3

也就是说,我们删除了最终的HEAD——它毫无意义地产生了一个分离的 HEAD——我们使用标签 tag-onto 作为--onto 目标,我们将在该目标上复制lark d 提交.

运行此脚本会重现该问题:

$ ./repro.sh
[much output snipped]
CONFLICT (content): Merge conflict in main_file
error: could not apply 1a3193f... lark d
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 1a3193f... lark d

这里的设置是我们尝试将提交 lark~2 挑选到提交 tag-ontomaster~3main~3 取决于您的初始分支)。

我们需要意识到git cherry-pickgit merge的一种。它进行三向合并,合并基础是提交的父级。这意味着它运行两个git diffs,从父lark~3 到当前提交HEAD,从父lark~3 到提交lark~2。让我们看一下这两个差异中的第一个:

$ git diff lark~3 HEAD
diff --git a/lark_file b/main_file
similarity index 66%
rename from lark_file
rename to main_file
index de98044..422c2b7 100644
--- a/lark_file
+++ b/main_file
@@ -1,3 +1,2 @@
 a
 b
-c

这表示我们应该重命名文件:现在应该将其命名为 main_file,而不是 lark_file

第二个差异当然显示了您在提交 lark d 中添加的内容:

$ git diff lark~3 lark~2
diff --git a/lark_file b/lark_file
index de98044..d68dd40 100644
--- a/lark_file
+++ b/lark_file
@@ -1,3 +1,4 @@
 a
 b
 c
+d

所以 Git 决定我们需要将 lark_file 重命名为 main_file 并在末尾添加 d,同时从 main_file 的末尾删除 c

这确实是 Git 所做的:我们现在有一个文件 main_file,而不是两个单独的文件 lark_filemain_file,我们看到了冲突。我的是diff3风格而不是merge风格,所以它有更多信息:

$ cat main_file 
a
b
<<<<<<< HEAD:main_file
||||||| parent of 1a3193f... lark d:lark_file
c
=======
c
d
>>>>>>> 1a3193f... lark d:lark_file

Git 的行为符合 Git 规则。

Git 合并规则时不时会产生意外,这就是为什么您必须始终检查 任何 合并结果的原因。这包括挑选结果,即合并结果。

【讨论】:

好吧,樱桃采摘部分真的让我很困惑,我不得不说我没有遵循。 樱桃挑选是一种合并形式。通过查找 三个 提交而不是两个提交来进行合并。这对于传统的合并更有意义,在这种合并中,您实际上是从一个共同的起点开始合并工作,但它也用于挑选樱桃。 Rebase 是重复挑选的,因此每个“复制提交”步骤都使用git cherry-pick。这就是所有这些的来源。要正确理解merging,你应该先进入merging这个话题。

以上是关于关于 git rebase 到选项的一些问题的主要内容,如果未能解决你的问题,请参考以下文章

git svn rebase:数据不完整:增量源意外结束

Git (master|REBASE 1/1) 是啥意思?我该如何摆脱它?

闲谈 git merge 与 git rebase 的区别

git rebase简介 基本篇

Git分支merge和rebase的区别

git 交互式 rebase squash 进入下一个提交