如何从已经重新定位的分支中删除特定的提交?
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
而不是H
,F
自动成为分支上的最后一次提交。
每当我们进行 new 提交时,我们都会这样做:
git switch <em>branch</em>
或 git checkout <em>branch</em>
;
处理/使用 Git 从所选分支名称的提交中复制的文件;
使用git add
(原因我不会在这里讨论);和
运行git commit
。
最后一步——git commit
步骤——通过以下方式进行新的提交:
user.name
和 user.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 checkout
或 git 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 develop
。 H
代表 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
其余三个提交以此类推。 现在,由于-i
,git rebase
在此说明表上打开您的编辑器。您现在的工作是调整这些说明,然后写出来并退出您的编辑器。1 在您的特定情况下,您的工作是更改或删除提交 H
的 pick
命令——您不想要的提交。如果您将其更改为 drop
或 d
,或者如果您只是删除整行,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 分支(本地和远程)中删除特定的提交? [复制]