git rebase --onto 失败

git rebase --onto fails

我创建了一个用于学习的本地存储库git,因此丢失数据不是问题。

当前树:

*   006f7ab - (2 days ago) Merge branch 'hotfix' idc what will heppen :( - stav alfi (master
|\
| * 0f028e8 - (2 days ago) good - stav alfi
* | fc040d3 - (2 days ago) good - stav alfi
* | ed29b30 - (2 days ago) good - stav alfi
|/
* a7c5bb3 - (2 days ago) good branch - stav alfi
* 9d804c2 - (2 days ago) new.txt changed in 16:35 - stav alfi
* 6ada3b7 - (2 days ago) new.txt changed in 16:32 - stav alfi (oldDad)
* f6497fc - (2 days ago) this is the nest commit! - stav alfi (oldDad1)
* b1b3e25 - (2 days ago) omg - stav alfi
* 74656b3 - (2 days ago) new1234 - stav alfi
* e8977d3 - (2 days ago) fast commit - stav alfi
* 114b46c - (3 days ago) good - Stav Alfi
* 8212c78 - (3 days ago) good - Stav Alfi
* 23dfc61 - (3 days ago) removed-something - Stav Alfi
* 184178d - (3 days ago) shortcut - Stav Alfi
* f1e606f - (3 days ago) good-commit - Stav Alfi
* 5ae787b - (3 days ago) initial-project-version1 - stav alfi
* 1321cba - (3 days ago) initial-project-version1 - stav alfi
* eae3e1c - (3 days ago) initial-project-version - stav alfi
* d3c3e93 - (3 days ago) initial-project-version - Stav Alfi
* db309e9 - (3 days ago) initial-project-version - Stav Alfi (HEAD -> newDad)

想要的树:(我想做的)

*   006f7ab - (2 days ago) Merge branch 'hotfix' idc what will heppen :( - stav alfi (HEAD -> master)
|\
| * 0f028e8 - (2 days ago) good - stav alfi
* | fc040d3 - (2 days ago) good - stav alfi
* | ed29b30 - (2 days ago) good - stav alfi
|/
* a7c5bb3 - (2 days ago) good branch - stav alfi
* 9d804c2 - (2 days ago) new.txt changed in 16:35 - stav alfi
* 6ada3b7 - (2 days ago) new.txt changed in 16:32 - stav alfi (oldDad)
(....I want to remove all of this....)
* db309e9 - (3 days ago) initial-project-version - Stav Alfi (newDad)

我使用的命令+错误:

$ git rebase --onto newDad oldDad1
First, rewinding head to replay your work on top of it...
Applying: new.txt changed in 16:32
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
A       new.txt
Falling back to patching base and 3-way merge...
CONFLICT (modify/delete): new.txt deleted in HEAD and modified in new.txt changed in 16:32. Version new.txt changed in 16:32 of new.txt left in tree.
Patch failed at 0001 new.txt changed in 16:32
The copy of the patch that failed is found in: .git/rebase-apply/patch

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".

git状态(在运行git rebase --onto newDad oldDad1之后)

$ git status
rebase in progress; onto db309e9
You are currently rebasing branch 'master' on 'db309e9'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add/rm <file>..." as appropriate to mark resolution)

        deleted by us:   new.txt

no changes added to commit (use "git add" and/or "git commit -a")

我的问题:

我无法理解我遇到的错误以及以下单词的含义: 倒带、重放、应用。 我也不知道如何解决冲突。

我很想知道我做错了什么以及如何解决它。

非常感谢!

好的,深呼吸:-)

Git的rebase副本提交

git rebase 使用的基本技巧是 cherry-pick 操作,它 复制 一个提交。我们现在将了解复制提交的机制,但考虑一个简单、普通的 git cherry-pick,其中我们 git checkout 一些 b运行ch 名称——我将创建一个新的在这里指向一个特定的提交——然后告诉 Git 复制一些其他提交,通常是(还)不在我们的新 b运行ch.

上的提交
git checkout -b newbranch 23dfc61

这使得 23dfc61 成为当前提交,但给它一个新的 b运行ch name, newbranch。现在我们可以进行新的提交,添加到新的 b运行ch,所以现在我们 运行,例如:

git cherry-pick 9d804c2

复制提交 9d8042c.

结果,如果这有效——如果没有合并冲突,或者如果有的话,在你清理任何合并冲突之后——是一个新的提交,其 parent 是 23dfc61,并且其 源代码树 23dfc61 类似,但与 6ada3b7 相比,您在 9d804c2 中所做的更改添加到其中:

...
* 9d804c2 - (2 days ago) new.txt changed in 16:35 - stav alfi
* 6ada3b7 - (2 days ago) new.txt changed in 16:32 - stav alfi (oldDad)
* f6497fc - (2 days ago) this is the nest commit! - stav alfi (oldDad1)
* b1b3e25 - (2 days ago) omg - stav alfi
* 74656b3 - (2 days ago) new1234 - stav alfi
* e8977d3 - (2 days ago) fast commit - stav alfi
* 114b46c - (3 days ago) good - Stav Alfi
* 8212c78 - (3 days ago) good - Stav Alfi
| * NNNNNNN - (now) new.txt changed in 16:35 - stav alfi (HEAD -> newbranch)
|/
* 23dfc61 - (3 days ago) removed-something - Stav Alfi
* 184178d - (3 days ago) shortcut - Stav Alfi
...

我们不知道新的哈希值是多少,所以我输入了NNNNNNN。但是新提交与旧提交具有相同的日志消息,并且做出与旧提交相同的更改

提交包含快照,而不是更改

每个提交都附有截至该提交时的完整来源。这与许多其他版本控制系统不同:大多数倾向于将每个提交存储为来自它们之前的提交或之后的提交的 change。这意味着,为了复制一个提交,Git首先必须找出发生了什么变化。

找出答案的方法是将提交与其 parent 提交进行比较。 9d804c2 的 parent 提交是 6ada3b7,所以 Git 是:

git diff 6ada3b7 9d804c2

查看发生了什么变化。假设日志消息是准确的,您在 new.txt 中更改了一些内容,所以这就是 Git 会找到的内容。那么,这就是 当 Git 试图修改为 23dfc61 保存的快照以便为 NNNNNNN.

如果成功,Git 将提交结果,并且会成功 cherry-pick。

没有提交可以永远被改变

无法发音的哈希 ID 23dfc616ada3b7 以及 badf00dbedface 等等是通过获取每个提交的确切内容构造的。如果您尝试 更改 关于任何提交的任何内容,Git 会构建一个新的提交;如果任何地方甚至有一点不同,你都会得到一个新的、不同的散列,所以你会得到一个新的、不同的提交。

其中的部分包括所有源代码,加上 parent ID,因为每个提交 "points to"(包含其 parent 的 ID)。 (还有一些时间戳,所以除非你在同一秒内进行两次相同的提交,否则你仍然会得到两个不同的 ID,即使它们的其余部分相同。)因此,要更改任何内容——无论是源代码, 或者只是一个 parent ID—Git 必须 复制提交。

这就是变基复制提交的原因:它必须。您正在进行一些提交,将每一个都变成一个更改,然后应用这些更改从一些 不同的 提交开始,它具有不同的 parent ID,即使它具有相同的源代码树。所以你给 git rebase 的基本上是两块信息:

  • 它应该复制哪些提交?
  • 它应该把这些副本放在哪里?

如果使用 --onto,复制的地方很容易,因为就是那个地方!然而,提交到 copy 的集合比较棘手。

选择提交

Git 提供了一个 运行ge 符号,X..Y,看起来它的意思是 "commits between X and Y"——而且确实如此, 有点。但不完全是!事实上,Git 使用我们称为 reachability 的东西,在提交中遵循 parent 链接。我们已经注意到每个提交都有一个 parent ID 存储在其中。这就是 Git 找到您的提交的方式:您告诉它从 b运行ch tip 开始,使用 b运行ch name 类似于 master,它通过其哈希 ID 找到特定的提交,Git 会在名称 master.

中为您记住

该提交中有另一个哈希 ID:这是提交的 parent。 Git 使用该哈希 ID 查找 that 提交。 parent 有另一个哈希 ID,Git 不断发现越来越多的 parent。这种情况会尽可能持续下去,一直追溯到您所做的第一次提交。

太多的提交,所以我们告诉 Git 停止 在某个时候返回。那是 X..Y 的 "Y" 部分:这告诉 Git 从 Y 开始并向后工作,标记暂时 "green" 接受他们。但是,与此同时,从 X 开始并向后工作,标记提交 "red" 临时 避免 接受它们 .

我喜欢用提交的 one-letter 名称来绘制所有这些,而不是丑陋的大哈希 ID,以及左边有旧提交而右边有新提交的连接线:

...--D--E--F--G--H   <-- branch

这里commit H是b运行ch的tipGH的parent、FG 的 parent,等等。如果我们写 E..H,那会绘制 E(以及 D 和背面)"red":停止,不要拿走这些!然后它绘制 H 绿色,然后 GF,然后我们点击 red-painted E 并停止。因此 selects 提交 F-G-HE这里自然排除

但是当我们有 b运行ches 和合并时,事情变得更加棘手:

          F--G--H
         /       \
...--D--E         K--L
         \       /
          I-----J

提交 K 是一个 merge 提交。合并提交是一个有两个(或更多,但我们不去那里)parents 的提交。如果我们坚持红色和绿色油漆的类比,E..L 意味着 "paint E and on back red and paint L on back green":当我们点击 K 时,我们同时绘制 H J 绿色,并在 两侧 返回 branch/merge.

如果我们说 G..L,看看它是如何工作的:我们将 G 涂成红色,然后 F,然后 ED 等等.我们根本不绘制 I,因为那不是从 F 向后 :我们只能向后移动 ,不能向前移动,在这个过程中。然后我们画 L 绿色,K,然后 HJG 已经是红色,所以我们停止 一侧,但继续在另一侧继续,将I 涂成绿色。然后我们回到 E,但它是红色的,所以我们停下来。所以 this selects I and J, and also H, and K and L (按某种顺序)。

什么 git rebase 副本:合并是个问题

当 Git 转到 select 提交到 copy 时,它使用您的另一个(非 --onto)参数作为 "red paint" 停止项的一部分,并且您的 current 提交为 "green paint" 部分。如果您不使用 --onto,onto 目标与 red-paint select 或 相同。这就是 --onto 的全部功能:它可以让您选择与目标不同的 "stop" red-paint select。

但是如果这里有合并——在你的情况下,有——我们有问题,或者实际上,两个问题。一个是变基 无法复制 合并,所以它甚至不尝试。它只是从要复制的提交集中完全删除合并。另一个是我们跟随 both legs of a branch-and-merge,但我们无法控制 order 除非我们使用交互式(-i) 变基。

你在 master 和 运行:

git rebase --onto newDad oldDad1

所以这个 selects:

oldDad1..master

作为要复制的提交,但抛出所有合并,并将其余提交线性化。这意味着您开始于:

*   006f7ab - (2 days ago) Merge branch 'hotfix' idc what will heppen :( - stav alfi (master
|\
| * 0f028e8 - (2 days ago) good - stav alfi
* | fc040d3 - (2 days ago) good - stav alfi
* | ed29b30 - (2 days ago) good - stav alfi
|/
* a7c5bb3 - (2 days ago) good branch - stav alfi
* 9d804c2 - (2 days ago) new.txt changed in 16:35 - stav alfi
* 6ada3b7 - (2 days ago) new.txt changed in 16:32 - stav alfi (oldDad)

但最后是:

* 0f028e8 - (2 days ago) good - stav alfi
* fc040d3 - (2 days ago) good - stav alfi
* ed29b30 - (2 days ago) good - stav alfi
* a7c5bb3 - (2 days ago) good branch - stav alfi
* 9d804c2 - (2 days ago) new.txt changed in 16:35 - stav alfi
* 6ada3b7 - (2 days ago) new.txt changed in 16:32 - stav alfi (oldDad)

或者——因为我们不控制顺序:

* fc040d3 - (2 days ago) good - stav alfi
* ed29b30 - (2 days ago) good - stav alfi
* 0f028e8 - (2 days ago) good - stav alfi
* a7c5bb3 - (2 days ago) good branch - stav alfi
* 9d804c2 - (2 days ago) new.txt changed in 16:35 - stav alfi
* 6ada3b7 - (2 days ago) new.txt changed in 16:32 - stav alfi (oldDad)

(我在这里所做的只是交换两条腿)。 Git 将检查提交 db309e9(新爸爸,你的 --onto)作为临时 b运行ch,然后开始 cherry-picking 每个提交,转 6ada3b7 通过与 f6497fc 进行比较来改变。但这立即失败:

error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
A       new.txt
Falling back to patching base and 3-way merge...
CONFLICT (modify/delete): new.txt deleted in HEAD and modified 
 in new.txt changed in 16:32. Version new.txt changed in 16:32
 of new.txt left in tree.

这里的问题是 new.txt 在提交 db309e9 中不存在 。 Git 不知道如何结合 "make a slight change to new.txt" 和 "don't have a new.txt at all"。

现在您的工作是解决此冲突,方法是决定如何让 new.txt 出现在最终快照中。编辑或删除 work-tree 中的文件,完成后,git add 结果和 运行 git rebase --continue 和 Git 将继续尝试 cherry-pick 下一次提交。

重复此过程,直到 git rebase 复制了所有 to-be-copied 提交。完成后,git rebase 告诉 Git 到 "peel off" 原始 b运行ch 标签(master)并将其粘贴到它刚刚进行的最后一次提交。所以现在 master b运行ch 将命名最新的提交,它将指向它的 parent,依此类推。原始提交——你复制的那些——仍然在存储库中,有一段时间了,但它们现在是来自这个 b运行ch 的 "abandoned":它们没有名称 master 可用找到他们。

但是现有的 b运行ch 名称仍然可以找到现有的提交

名称oldDadoldDad1 仍然指向此处的一些原始(not-copied) 提交。这些名称仍会找到那些原始提交。如果有更多的名字记住了一些 copied 的提交,这些名字也会记住原来的名字。所以复制的提交不仅没有消失,有时它们仍然可见,这取决于 b运行ch names.

请注意,您的最终合并刚刚完成

因为git rebase甚至tr 复制合并,您的合并提交将被完全省略。但是,由于合并的 "legs" 都得到了应用(以某种顺序),如果您适当地解决任何冲突,最终的 源代码树 将匹配。难易程度取决于先完成哪条腿以及两条腿是否相互影响。

那里一个--preserve-merges标志

一种让git rebase尝试保留合并的方法。但实际上 无法保存它们。相反,它所做的是像以前一样复制叉子的每条腿,但这次是通过分叉两条腿;然后当它到达合并提交时,它 运行s a new git merge 进行 new 合并——Git希望——是"just as good"原来的样子。

在这种特殊情况下,--preserve-merges 不会帮助解决眼前的问题,因为它发生在 branch-and-re-merge 序列之前。这个 new.txt 文件在您 cherry-picking 的第一次提交中被修改,但在您的 starting-point 中不存在,发生在 branch-and-merge 序列之前。 --preserve-merges对你有没有用,我不知道。