如何从已经重新定位的分支中删除特定的提交?

Posted

技术标签:

【中文标题】如何从已经重新定位的分支中删除特定的提交?【英文标题】:How to remove specific commit from already rebased branch? 【发布时间】:2021-12-24 22:06:11 【问题描述】:

我已经为 develop 分支开设了 PR feature/orphans。从那时起,很多重要的分支已经添加到 repo 中,所以我应该重新基于它们。

现在我需要从我的功能分支中删除一个特定的提交,这对我来说有点棘手。我试图从类似问题(4110978 和 51400593)中获得几个答案,但它无法以某种方式工作????‍♂️。

我试过了:

git rebase --onto feature/db-optimization-for-orphans~fcf0c4a feature/db-optimization-for-orphans~2505060 feature/db-optimization-for-orphans

fatal: invalid upstream 'feature/db-optimization-for-orphans~2505060'

我想删除这个提交 #fcf0c4a,这是日志:

* cf83304 - (HEAD -> feature/orphans, origin/feature/orphans) Added orphans collection
* 844eb17 - fix: bugs
* 4f0111f - fix message
* 9093c8d - fix(cat-172): Change data format from object to array
* fcf0c4a - feat(CAT-172): Add new publisher // <---- ???? I WANT TO REMOVE THIS COMMIT
*   2505060 - (origin/develop, develop) Merge branch 'main' into develop
|\
| * 9600c29 - feat(CAT-111) description
* | 878ee2f - feat(cat-196) description
* | 6b53545 - feat(CAT-18) description
* | 1d2ef2e - saving model name into db + test + version
* | 24eea8e - saving ean values into the db + tests
|/
* 10c205c - Add preprod and prod deployers
* 8a8d0f2 - squashed commits
* 15328b6 - initial commit

有人能告诉我删除 fcf0c4a 提交的最佳解决方案是什么吗?

【问题讨论】:

【参考方案1】:

TL;DR

我想你想要git rebase -i origin/develop

我有坏消息和好消息要告诉你。

这是坏消息

您只能从分支的 end 移除提交:

* cf83304 - (HEAD -> feature/orphans, origin/feature/orphans) Added orphans collection
* 844eb17 - fix: bugs
* 4f0111f - fix message

例如,最后有三个提交,我将其名称缩短为第一个“数字”(c 最后一个):

... <-4 <-8 <-c

提交cf83304 向后指向提交844eb17。这就是git log 发现 844eb17 的方式。

Git finds cf83304 首先是因为 branch name 找到了它:

cf83304 ... feature/orphans

即名称feature/orphans指向cf83304

要从分支中删除提交,我们将分支名称指向某个较早的提交:

            c   <-- origin/feature/orphans
           /
... <-4 <-8   <-- feature/orphans

因此,commit cf83304 现在被推到一边。 (Git 仍然可以找到 cf83304,因为 name origin/feature/orphans 仍然指向 cf83304。)

一旦我们从分支中删除了提交 c...,我们也可以删除提交 8...

        8 <-c   <-- origin/feature/orphans
       /
... <-4   <-- feature/orphans

等等。

所以坏消息是:要删除提交fcf0c4a——你可以这样做——你必须删除所有后续提交 从那个分支。

好消息来了

在我们“删除”提交之前——它们并没有真正消失;如果您知道它们的编号,或者它们有其他名称,例如origin/feature/orphans,我们仍然可以找到它们——我们可以复制选定的提交新的和改进的提交 .

每次提交,在 Git 中:

有编号。那些大而难看的随机hexadecimal 数字、fcf0c4a 等等,唯一地定位那个特定的提交。 Git 需要这个数字才能找到提交。

包含两件事:

每个提交都有一个每个文件的完整快照,因为它在您(或任何人)进行提交时出现。这些文件以一种特殊的、压缩的和去重的、只读和仅 Git 的形式保存:您无法读取它们,并且 没有任何东西——甚至 Git 本身——也无法覆盖 它们,所以这些不是您使用的文件。它们只是永久存储,以便您以后可以取回它们。

每个提交都包含一些元数据,或者关于提交本身的信息。例如,这包括提交人的姓名和电子邮件地址。它包括一些日期和时间戳:当您运行 git log 并查看带有作者和日期的提交时,这些都来自元数据。它还包括您在此处看到的日志消息。

对于 Git 本身来说至关重要的是,每个提交中的元数据都存储了 先前 提交的一些列表的原始哈希 ID(提交编号)。大多数提交只存储一个哈希 ID,这就是您在这里得到的;一些,如2505060,是merge 提交,它存储两个 哈希ID;并且每个非空存储库中至少有一个提交是第一次提交,例如15328b6。这个第一次提交不存储任何个以前的提交 ID,因为没有任何以前的提交。

除了那些古怪的特殊情况(合并和第一次提交),我们可以画出这样的提交序列:

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

每个像H 这样的大写字母代表一些实际的哈希ID。 (这就是我在上面所做的,除了我能够使用 real 散列 ID 的第一个字符。)字母代表保存的文件和元数据,箭头从每个字母代表存储的先前提交哈希 ID:commit H 存储先前提交的哈希 ID G。我们说H 指向 G

现在,再次谈到坏消息 - 好消息的主题,坏消息是,任何提交都无法更改。 (这是必要的,原因有很多,包括 Git 相当神奇的哈希 ID 方案——这类似于加密货币的技巧——还包括提交 share 相同文件的事实。如果我们可以以某种方式更改其中一个文件,这将更改所有共享副本。)好消息是我们和 Git 的方式查找这些提交是通过分支和其他名称,我们可以并且做东西不同将哈希ID提交到这些分支和其他名称中。

每个名称仅包含 一个 哈希 ID。对于分支名称,根据定义,该哈希 ID 是分支上的最后一次提交。所以当我们有:

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

这意味着提交H 根据定义是分支上的last 提交。这就是我们可以移动名称以删除一个或多个提交的方式:

       G--H
      /
...--F   <-- somebranch

现在somebranch 指向F 而不是HF 自动成为分支上的最后一次提交

每当我们进行 new 提交时,我们都会这样做:

    git switch <em>branch</em>git checkout <em>branch</em>; 处理/使用 Git 从所选分支名称的提交中复制的文件; 使用git add(原因我不会在这里讨论);和 运行git commit

最后一步——git commit 步骤——通过以下方式进行新的提交:

收集适当的元数据:例如,它会从 user.nameuser.email 获取您的姓名和电子邮件地址; 找出当前提交的哈希ID,使用步骤1中的当前分支名称:如果它指向F,那就是当前提交; 写出新的快照和元数据,新提交的箭头指向当前提交;和 最后一招...

但让我们先画出写出新提交的效果:

       G--H   <-- origin/feature/orphans
      /
...--F   <-- current-branch (HEAD), some-other-branch
      \
       I

我们现在有了这个新的提交I,它有一个新的、唯一的、又大又丑的哈希 ID。现在最后一个技巧开始了:Git 将新提交的哈希 ID 写入当前分支名称:

       G--H   <-- origin/feature/orphans
      /
...--F   <-- some-other-branch
      \
       I   <-- current-branch (HEAD)

这就是分支在 Git 中的增长方式。我们检查一个,git checkoutgit switch——在我的图中,这意味着我们将特殊名称 HEAD 附加到分支名称;你可以在你自己的git log 输出中看到这个特殊的名字——并且检查提交可以让我们得到所有保存的文件来自提交。然后我们像往常一样做我们的工作并进行 new 提交。 new 提交获得一个新的唯一哈希 ID,Git 将新提交的哈希 ID 填充到当前 name 中,现在该名称指向新的最后一次提交。

这如何帮助你做你想做的事?

让我们画一些你有的东西,用我喜欢的单字母大写字母名称替换丑陋的散列 ID,以我喜欢的形式:

...--G--H--I--J--K--L   <-- feature/orphans (HEAD), origin/feature/orphans

这里G 代表2505060 - (origin/develop, develop) Merge branch 'main' into developH 代表 fcf0c4a - feat(CAT-172): Add new publisher:您要“删除”的提交。 I 代表 9093c8d - fix(cat-172): Change data format from object to array,这是您想要保留的提交。 J-K-L 也是你想要保留的提交。

坏消息是您将不得不退出您想要保留的提交。好消息是您可以先将它们复制到新的和改进的提交中。我们最终会得到:

       H--I--J--K--L   <-- origin/feature/orphans
      /
...--G
      \
       I'-J'-K'-L'  <-- feature/orphans (HEAD)

提交I'-J'-K'-L' 将被仔细安排 提交的副本。我们将对每个副本进行两项更改:

    每个副本的parent都会指向右边的parent:即I'会直接指向G,而不是H。 每个副本的快照文件删除您在提交H中所做的更改。

现在,清晰但手动且有点慢的方法是手动复制您想要复制的每个提交,一次一个。我们将通过创建一个指向提交G新的临时分支名称来做到这一点:

       H--I--J--K--L   <-- feature/orphans, origin/feature/orphans
      /
...--G   <-- temp-branch (HEAD)

我们做的:

git switch -c temp-branch 2505060

我们现在在这个新的临时分支上,我们可以看到和使用的文件是来自提交 G(或者确切地说是 2505060)的文件。

我们现在想让 Git 弄清楚 我们在提交 I 中所做的更改,然后在此时此地进行相同的更改并提交它们。 Git 也会从提交 I 复制提交 消息

执行这个简单的“复制一个提交的更改和提交消息”的 Git 命令是 git cherry-pick,所以我们会运行:

git cherry-pick <hash-of-I>

I 的(缩写)哈希是 9093c8d,所以我们可以输入它并按 ENTER 并得到:

       H--I--J--K--L   <-- feature/orphans, origin/feature/orphans
      /
...--G
      \
       I'  <-- temp-branch (HEAD)

然后我们必须重复三个具有正确哈希 ID 的 git cherry-pick 命令。这会将J 复制到J',然后将K 复制到K',然后将L 复制到L'

       H--I--J--K--L   <-- feature/orphans, origin/feature/orphans
      /
...--G
      \
       I'-J'-K'-L'  <-- temp-branch (HEAD)

一旦我们完成了所有 git cherry-pick 步骤,我们只需要告诉 Git:嘿 Git,强制名称 feature/orphans 指向当前提交,这需要使用 @ 987654412@。然后我们会git switch feature/orphans 回复它:

       H--I--J--K--L   <-- origin/feature/orphans
      /
...--G
      \
       I'-J'-K'-L'  <-- feature/orphans (HEAD), temp-branch

然后我们可以完全删除名称temp-branch,因为我们已经完成了它。

快捷方式

执行所有这些单独的步骤——创建一个新的但临时的分支名称,一个接一个地挑选提交,强制旧的分支名称就位,切换回 旧的分支,并删除临时分支——是 的一大痛点。 我们不必这样做。我们使用git rebase 命令代替。

git rebase 命令主要只是完成上述操作的一种奇特方式,一个命令。因为这个命令做了很多事情,它有很多部分,我认为这就是你遇到 rebase 问题的地方。

你有很多选择——有很多方法可以运行git rebase——但我通常自己使用的这种情况称为interactive rebase。你像这样运行它:

git switch feature/orphans     # if you're not already there
git rebase -i origin/develop

这里的名称origin/develop 是任何名称(分支或其他名称),用于选择您希望提交的位置。如果您愿意,可以使用原始哈希 ID (git rebase -i 2505060),但我们想挑选出我一直称为“commit G”的提交。 这是副本应该去的地方。

git rebase 命令现在将通过列出您现在拥有的提交来计算出要复制的提交,不包括那些可从提交G。无需深入了解这一切意味着什么,简短的版本是该列表提交H-I-J-K-L这是一个太多的提交,但没关系!列出了这些提交哈希 ID,git rebase -i 中的 -i 表示 现在你已经列出了要复制的提交,用pick在每个哈希ID前面组成一个指令表。

因此,本说明书将显示为:

pick fcf0c4a feat(CAT-172): Add new publisher
pick 9093c8d fix(cat-172): Change data format from object to array

其余三个提交以此类推。 现在,由于-igit rebase 在此说明表上打开您的编辑器。您现在的工作是调整这些说明,然后写出来并退出您的编辑器。1 在您的特定情况下,您的工作是更改或删除提交 Hpick 命令——您想要的提交。如果您将其更改为 dropd,或者如果您只是删除整行,git rebase不会复制提交 H

一旦你写出指令表,git rebase 将继续执行剩余的pick 指令,为每个需要复制的提交运行git cherry-pick。这会让你得到I'-J'-K'-L' 提交。然后 git rebase 通过移动 name feature/orphans 指向最终复制的提交 L' 来结束:

       H--I--J--K--L   <-- origin/feature/orphans
      /
...--G
      \
       I'-J'-K'-L'  <-- feature/orphans (HEAD)

现在您的存储库中已经有了您想要的一组提交,但还有一件事要做。


1有些编辑器并没有真正“退出”:他们需要与 Git 沟通他们已经完成了文件的编写。这是另一个你可能会遇到绊脚石的地方。但是 Git 已经有了git commit 的这个问题,如果你不使用-m 标志,通常你不应该使用-m 标志。所以你应该已经解决了这个问题,如果你有这些棘手的编辑器之一。


你现在需要使用git push --force-with-lease

您已发送H-I-J-K-L 提交到其他 Git 存储库,您使用名称 origin 调用该存储库。您让其他 Git 存储库创建或更新 他们的 分支名称 feature/orphans。您自己的 Git 通过将 他们的 feature/orphans 记住为 您的 origin/feature/orphans 来反映这一点。

您现在需要将 I'-J'-K'-L' 提交发送给这个其他 Git 存储库——这部分很简单——然后说服他们他们应该放弃他们的 H-I-J-K-L 链支持您新的和改进的I'-J'-K'-L' 提交链。这部分需要使用强制推送

一般来说,Git 非常喜欢向分支添加新的提交。它不喜欢 在结尾处放弃提交: 这通常被认为是坏的或错误的。所以你必须强迫他们的 Git 这样做。

使用git push --force-with-lease origin feature/orphans,您的Git 调用他们的Git,给他们提交I'-J'-K'-L',然后发送以下形式的命令:

我认为你的 feature/orphans 持有 cf83304。如果是这样,我命令您将提交 L' 的哈希 ID 填入其中。如果我是对的,请告诉我,而你是这样做的。

他们要么找到正确的事情并服从,要么告诉你为什么他们没有。

您可以使用更简单的git push --force。通过向他们发送命令,这省略了一些安全检查:

将提交 L' 的哈希 ID 填充到您的 feature/orphans 中!现在做!我命令你!

如果出于某种原因他们获得了一个更新的提交,L 之后,这将放弃该提交。如果你不知道它的哈希 ID,你永远不能要求它。通过使用“我认为......所以这样做”构造,如果您错了,您可以在让他们放弃以后再也找不到的提交之前看到到底发生了什么。 p>

【讨论】:

以上是关于如何从已经重新定位的分支中删除特定的提交?的主要内容,如果未能解决你的问题,请参考以下文章

Git 如何从特定的提交中创建一个新的分支

是否可以从 Git 分支(本地和远程)中删除特定的提交? [复制]

如何重新添加 Bamboo 已自动删除的分支构建?

如何从PR的另一个分支中删除不需要的提交

如果合并或重新定位,则主删除分支文件中的 Git `revert`

如何从 Git 主分支中间删除一些提交? [复制]