繁体   English   中英

Git rebase 冲突,实际修改了哪些分支?

[英]Git rebase conflict, which branches are actually modified?

所以即使做了很多时间,我仍然非常害怕变基,我认为我遇到的一个问题是我确实深刻理解它的作用。

所以我有分支开发和我的分支,从开发开始。 为了避免/解决冲突,我希望更新我的分支从哪个提交开始。 出于这个原因,在我的分支上,我执行了git rebase develop

我的问题是,假设在变基阶段,我决定删除/修改执行的每一个更改。 一旦我推送,是否只会修改我的分支,或者我的 rebase 是否也修改了来自开发的实际提交?

只有您当前所在的分支会被修改。 您正在变基的分支不会被触及:它仅用作工作的起点。

如果你想更深入地了解 git,我推荐阅读A Hacker's Guide to Git ,这是一篇深入浅出的好文章。 它确实提高了我对 git 的工作原理及其作用的理解。 下面显示的是文章的摘录:

在此处输入图像描述

正如kapsiR 所说,不要害怕。 好吧,也许有一点点,足以采取一些预防措施,比如经常运行git status

git rebase的真正目的是将(一些)提交复制到新的和改进的提交 要了解其工作原理和原因,您首先需要了解提交:

  • 什么是提交?
  • 我们如何找到提交?
  • 我们如何进行新的提交?

这是前几个关键问题。 当您知道答案时,您将了解 Git 本身。 然后git rebase只需要一项:

  • Git 如何复制提交?

(虽然总会有更多的东西要学)。

确切地说,什么是提交(以及我们为什么要关心)

Git 中的提交:

  • 有编号。 每个提交都有一个唯一的hash ID 这是一个非常大的、无法记住的数字,以十六进制表示 每当您进行的提交时,该提交都会获得一个唯一编号。 我所说的独特并不是指“可能独特”或“某种独特”,而是绝对独特。 现在,宇宙中任何地方都不允许其他 Git 拥有或使用该号码,除非您向其他 Git 提供您刚刚提交的提交。 然后他们将使用编号进行提交,并且两个存储库将具有相同的提交。

    这意味着提交的编号——它的 hash ID——在某种意义上提交。 当您将两个 Git 版本相互连接时,将提交从一个存储库交换到另一个存储库时,他们只需查看数字 如果他们有相同的号码,他们有相同的东西。 如果没有,谁丢了号码,可以从另一个Git那里得到东西,现在他们有了同样的东西,同样的号码。

  • 存储两个东西:

    • 每个提交都存储每个文件的完整快照。 提交中的文件特殊的、压缩和去重的、仅 Git 的形式存储:只有 Git 可以实际读取这些内容,没有任何东西——甚至 Git 本身——可以覆盖它们。 它们无法更改的事实允许重复数据删除,并防止存储库快速变得非常胖,即使大多数提交主要重用了以前提交的大部分文件。

    • 每个提交还存储一些元数据,或有关提交的信息。 例如,这包括提交人的姓名和 email 地址。 它包括一些日期和时间戳。 它包含您的日志消息,因此您可以稍后回顾并记住您提交的原因,或者阅读其他人解释他们提交的原因的文本。 好的提交信息很重要。

    Git 将部分元数据用于自己的目的,这在这里至关重要。 当您进行的提交时,Git 会在该提交的元数据中存储一些先前提交的 hash ID。 大多数提交都得到一个存储在这里的 hash ID; 我们稍后会看到更多关于这个的信息。

  • 是只读的:任何Git 提交的任何部分都不能更改。

    事实上,Git 的神奇 hash 技术意味着任何类型的内部 object 都无法改变。 这提供了提交的只读性质、去重文件的只读性质以及重文件的能力。

为什么我们关心这个很简单。 Git 都是关于提交的。 Git 与文件或分支(即分支名称)无关。 这是关于提交的。 提交是 Git 存储库之间的交换货币。 我们将一个 Git 连接到另一个,使用git fetchgit push , 1我们将提交从一个 Z0365Z1 转移到另一个 Z0365Z105ADFE279

Git 存储库首先是一个大型的提交数据库。 提交保存文件和元数据。 我们自己的个人目标很可能与文件有关,但 Git 在这个级别不处理文件 它只处理提交。 所以我们必须使用提交来处理文件。


1 git pull命令是一种方便的包装器,它首先运行git fetch ,从某个地方获取新的提交,然后运行第二个 Git 命令来执行一些操作 这有点陷阱:它使人们相信git pull是正确的命令,而实际上这两个单独的命令通常是正确的,特别是因为您可以在它们之间挤压一个命令。 也就是说,您可以在决定如何甚至是否要合并它们之前获得新的提交并查看它们。 当您使用git pull时,您不会得到这个选择。


我们如何找到提交

我在上面提到过,每个提交都存储了之前提交的 hash ID。 或者,更准确地说,每个提交都有一个先前提交的列表:列表可以是空的(“我没有父母,我是孤儿”),或者只有一个条目(“我的爸爸/妈妈是 ________”——填写具有 hash ID 的空白),或两个或多个长条目(“我是一个合并。我的父母是 ________ 和 ________”——再次填写空白)。

请注意,没有父母知道它的孩子。 当提交“出生”时,它知道它的父母是谁,但从那时起它就一直被冻结。 它无法学习其子代的“名称”(哈希 ID)。 因此,存储库中的历史记录是反向的。

我们从稍后的提交开始找到提交。 后面的每一个都向后指向其父级。 只要我们没有合并提交,我们就有一个简单的线性链,如下所示:

... <-F <-G <-H

这里H代表链中最后一个提交的真实(又大又丑)hash ID。 我们必须以某种方式知道它的 hash ID - 我们稍后会回到这个问题 - 但假设我们确实知道它的 hash ID,这足以让 Z0BCC70105AD279503E51FE7B3F47B6 的所有提交从其大数据库中检索提交。

检索到H后,Git 现在有了一个快照——所有 go 和H的文件——以及一些元数据。 元数据包括早期提交G的 hash ID。 所以 Git 现在可以回到它的数据库并拉出提交G ,现在它有另一个快照和更多元数据。

通过比较两个快照中的文件GH , Git 可以告诉我们哪些文件发生变化,哪些没有。 (这也很快,因为重复数据删除。更改的文件是共享的,即两个提交引用相同的底层文件。)Git 可以更仔细地查看确实更改的文件,并且向我们展示这些文件中发生了什么变化 这就是我们通常如何看待提交:作为自上次提交以来的更改。 但是 Git 不存储更改; 它将整个文件存储为快照。

向我们展示了H的元数据和更改后,Git 现在可以后退一跳以提交G (它已经检索到其 hash ID)。 这当然是一个提交,带有快照和元数据。 它的元数据指的是较早的提交F 所以 Git 现在可以重复它刚刚对H所做的事情,向我们展示提交G

显示提交G后,Git 现在可以后退一跳提交F 它可以向我们显示F ,然后再次向后移动一跳。 这一直持续到我们到达第一个提交。 第一次提交在一个方面很特别:它没有父级。 它的“先前提交”列表是空的。 这就是 Git 知道何时停止倒退的方式。 (在大型存储库中,您可能会在 Git 回到开始之前很久就退出git log ,但这也很好。)

不过这里有一个大问题。 我们如何找到提交H 我们在上面说过,我们只是假设我们在某处保存了 hash ID。 也许我们把它写在一张纸上,或者写在办公室的白板上,或者其他什么地方。 但这里有一个更好的主意:我们有一台计算机,运行带有提交数据库的软件。 让我们也有一个最新的 hash ID的数据库。 我们可以称这些分支名称

分支名称查找提交并由新提交更新

mastermaindevelopfeature等分支名称只包含一个提交 hash ID 存储在分支名称中的一个 hash ID是链中最后一个提交的 hash ID。 所以如果我们有:

...--F--G--H   <-- main

那么根据定义,提交H是分支main上的最新提交。

我们可以制作更多的分支名称。 让我们添加名称develop ,也指向提交H

...--F--G--H   <-- develop, main

现在,我们需要某种方法来知道我们正在使用哪个名称——尽管无论我们使用哪个名称,我们都将使用提交H所以让我们添加特殊的全大写名称HEAD

...--F--G--H   <-- develop, main (HEAD)

我们目前on branch main ,因为HEAD附加到(或旁边) main 如果我们git checkout developgit switch develop ,我们得到:

...--F--G--H   <-- develop (HEAD), main

我们仍在使用提交H ,所以没有其他任何改变,但我们通过名称develop使用它。

当我们进行新的提交时——我将在这里完全跳过 Git索引(即暂存区)的大部分细节——我们运行git commit和 Git:

  • 收集元数据,例如您的姓名和 email 地址和日志消息;
  • 使用HEAD查找当前提交的 hash ID H
  • 将 go 的所有文件(已删除重复)的快照写入新提交;
  • 写出元数据,创建新的提交本身并获得一个新的唯一 hash ID;
  • 将 hash ID 写入HEAD所附加的名称中。

所以现在我们已经做了一个新的提交I ,新的提交I指向现有的提交H 并且因为HEADdevelop相关联,因此名称develop现在指向新的提交I 名称main仍然指向提交H

...--F--G--H   <-- main
            \
             I   <-- develop (HEAD)

没有其他改变:提交H没有改变(它没有向前指向II向后指向H )。 HEAD仍然依附于develop 唯一的变化是我们有一个新的提交I ,带有新的元数据和快照,并且新提交I的 hash ID 现在存储在develop中。

假设您现在创建自己的新分支名称fix-123 ,并切换到该分支:

...--F--G--H   <-- main
            \
             I   <-- develop, fix-123 (HEAD)

现在你做了两个新的提交JK

...--F--G--H   <-- main
            \
             I   <-- develop
              \
               J--K   <-- fix-123 (HEAD)

现在假设其他人对develop做了一个新的提交。 git checkout developgit switch develop得到:

...--F--G--H   <-- main
            \
             I   <-- develop (HEAD)
              \
               J--K   <-- fix-123

然后你获得他们的新提交( git fetch + git merge ,也许,或者git pull如果你使用速记的一体式命令,那么它适用于实例,现在和干燥器组合)你有:

...--F--G--H   <-- main
            \
             I--L   <-- develop (HEAD)
              \
               J--K   <-- fix-123

现在是 rebase 的时候了。

变基是复制

我们现在将停止在main中绘制,只需使用:

...--I--L   <-- develop (HEAD)
      \
       J--K   <-- fix-123

使事情更紧凑。 我们现在的问题是提交JK 它们并没有什么问题,除了......好吧,问题是提交J作为其父级,提交I 我们想要一个以提交L作为其父级的提交。

任何现有的提交都不会改变。 我们无法修复提交J 但是我们可以做出一个非常J提交,只是不同而已。 我们也可以对提交K做同样的事情。 我们想要得到的是这样的:

          J'-K'  <-- new-and-improved-fix-123 (HEAD)
         /
...--I--L   <-- develop
      \
       J--K   <-- old-and-lousy-fix-123

这里J'是我们的J副本,而K'是我们的K副本。 J vs J'有两点不同。 K vs K'也有两点不同。 特别是,两个副本都有不同的提交,我们可以从图中看到。 两个副本的快照有任何差异,基于L中的快照而不是I中的快照。

为了到达这里,从我们所在的地方,我们需要:

  • 列出我们要复制的提交的 hash ID, JK
  • 在提交L处创建一个新的分支名称;
  • 检查新的分支名称;
  • 复制第一个提交J
  • 复制第二个提交K

当我们完成所有这些后,我们有了新的图表,我们现在要做的就是修复分支名称

有一种手动执行此操作的方法,甚至不是那么难:

git checkout -b new-fix-123 develop
git cherry-pick <hash-of-J>
git cherry-pick <hash-of-K>

我们可以让它更短:

git checkout -b new-fix-123 develop
git cherry-pick <hash-of-J> <hash-of-K>

将其简化为两个命令。 但是我们仍然需要将fix-123名称移动到我们现在所在的位置,并再次检查fix-123

git checkout -B fix-123

会这样做,然后我们可以删除new-fix-123 ,我们将拥有:

          J'-K'  <-- fix-123 (HEAD)
         /
...--I--L   <-- develop
      \
       J--K   <-- ???

请注意,不再有任何名称可以用来查找提交K 我们强制 Git 将名称fix-123移动到指向K' 旧的提交仍然存在。 我们再也找不到他们了。

git rebase命令一步完成

同样,我们从以下开始:

...--I--L   <-- develop (HEAD)
      \
       J--K   <-- fix-123

跑步:

git checkout fix-123
git rebase develop

有 Git:

  • 列出“on” fix-123但不是“on” develop的提交:那是 hash IDs JK Git 实际上是向后生成此列表 - 因为 Git 总是向后工作 - 但git rebase然后反转向后列表,使其向前。

  • 在提交L处创建一个临时“分支”。 Git 为此使用分离的 HEAD模式,而不是使用分支名称。 我们将在此处跳过详细信息,但如果出现问题,它们很重要。

  • 为列表中的每个提交运行git cherry-pick

  • 将我们所在的分支名称 — fix-123 — 强制到此处并再次检查。

这正是我们手动执行的操作,但 Git 会自动完成所有操作。 只要没有出错,我们最终就会得到我们想要的。 这里真正的诀窍是从失败中恢复。

go 会出现什么问题?

“复制”一个提交——使用cherry-pick,或者使用git rebase在旧版本 Git 中使用的更原始的方法——可能会失败。 特别是每个git cherry-pick操作:

  • 必须弄清楚发生了什么变化,然后
  • 必须弄清楚如何在此处将这些更改应用于另一个提交。

从技术上讲,Git 所做的就是使用它的合并引擎 这通常工作得很好,但它可能会因合并冲突而停止。 当它发生时,您必须解决冲突,然后继续变基。

当 Git 去确定要复制的提交时,有时您没有得到预期的提交集。 您可以在这里使用git rebase --onto来提供帮助,但我不会在这个答案中多说什么。 如果您有一组包含任何合并提交的提交,它们会使事情复杂化。 我根本不打算在这里介绍它们。 Git 现在(从 2.22 开始)有一个--rebase-merges选项可以完成这项工作,但这有点棘手。

最后,如果一个 rebase 错误go并且你希望你没有首先启动它......好吧,如果你被困在失败的 rebase 中间,你可以使用git rebase --abort

          J'  <-- HEAD
         /
...--I--L   <-- develop
      \
       J--K   <-- fix-123

当复制K失败时,可能是因为与L发生冲突,并且您决定更愿意回到这个:

          J'  [abandoned]
         /
...--I--L   <-- develop
      \
       J--K   <-- fix-123 (HEAD)

一个简单的git rebase --abort就足够了。 但是,如果您已经让 rebase 完成,或者解决了冲突并继续 rebase 并且现在处于:

          J'-K'  <-- fix-123 (HEAD)
         /
...--I--L   <-- develop
      \
       J--K   [abandoned]

并确定整个事情是一个错误,您需要找到原始提交K的 hash ID才能恢复。

有一种方法可以做到这一点,使用git reflog 但这是一种痛苦。 幸运的是,有一个更简单的方法 如果您即将开始一个 rebase,并且不确定要使用结果还是坚持原来的,只需在开始之前创建一个新分支

...--I--L   <-- develop
      \
       J--K   <-- fix-123.0, fix-123 (HEAD)

现在,在你的 rebase 之后,你将拥有:

          J'-K'  <-- fix-123 (HEAD)
         /
...--I--L   <-- develop
      \
       J--K   <-- fix-123.0

K结尾的旧系列提交仍然很容易找到,使用分支名称fix-123.0 2如果我发现自己再次变基,我会在开始之前制作一个新的fix-123.1 所以fix-123是当前的,编号的是以前的,如果我想要它们,一旦我确定我完成了它们,就可以删除它们。


2对于像我这样的计算机老手,您通常可以通过我们是否从零开始数数来判断我们是从数学系还是物理/工程系出来的。 我两者都做过,但我的心更多地与数学家在一起。 有时我想我应该把这些名字写得更详细一些,例如,上面有日期,但简单的编号似乎效果很好。 我很少会高于 3 或 4。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM