当git历史不同但变化相同时如何发送拉取请求?

时间:2018-07-24 18:47:45

标签: git github

这是我的起源,最初是上游的,

upstream: --A--B--C--D--

origin: --A--B--C--D--E--F

我向上游发出拉动请求,并开始进一步工作,

origin: --A--B--C--D--E--F--G--H--

现在,存储库所有者压缩了我的提交,现在我的上游看起来像

upstream: --A--B--C--D--EF--

我想再次发出请求,但是由于git认为提交E,F和EF不同,我该怎么做?这件事发生在我身上很多次了,我总是最终弄乱了我的git历史。有人可以告诉我正确的方法吗?我已经尝试过进行变基,压扁和其他操作,但是没有用,可能是我做这些的方法可能是错误的。我什至想不出适合我问题的标题。

1 个答案:

答案 0 :(得分:3)

TL; DR

您将要使用git rebase --onto。正确使用它有点棘手,特别是因为您将需要用力推动自己的叉子。

  

我想再次发出请求,但是由于git认为提交E,F和EF不同,我该怎么做?

它们不同

请记住,提交的“真实名称”是其原始哈希ID。 1 E的哈希ID与F和EF的哈希ID不同;这三个都是独立的唯一提交。 EF是挤压E和F的结果没关系(嗯,对您来说很重要; 问题在于,它对 Git 没有任何帮助,所以Git无法/不会在这里为您提供帮助。

我们需要了解Git是分布式的,Git实现这种分布的方式是给出提交的副本,这些副本由其哈希ID标识。每个提交本身主要是一个独立的快照,只是每个提交记录其 parent 提交的哈希ID。 名称,无论是分支名称(如masterdevelop还是远程跟踪名称(如origin/masterupstream/master)都是Git设备记住一个特定的提交。一个提交会记住另一个先前的(父)提交,而父级会记住父级,依此类推。因此,当我们查看任何一个特定的存储库时,例如您在笔记本电脑上的存储库,我们都可以绘制出提交图:

A  <--B  <--C ... <--H   <--master

名称 master存储提交H的实际哈希ID。 H存储G的ID,存储F的ID,依此类推,一直返回到A。 (如果A是存储库中的第一个提交,则它没有父提交,这使Git停止向后移动。)

因此,

Git需要一个 name 来找到 last 提交(Git称为分支的 tip ),然后Git使用每次提交回顾历史。历史本身就是 Git可以通过以您拥有的所有名称开头并向后工作达到的所有承诺。

当我们连接任意两个不同的Git存储库时,我们将其中一个发送给其他发送者,而发送者具有接收者没有的任何提交,以及接收者可以用来识别提示提交的名称。因此,如果我们从以下内容开始:

A--B--C--D   <-- master

在一个Git存储库中,让另一个空的Git调用并从中接收,空的Git获得A-B-C-D序列和名称master。如果接收方Git正在执行git fetch,则接收方将其master重命名为origin/masterupstream/master,具体取决于我们是否在{{ 1}}或git fetch origin。如果git fetch upstreamupstream都具有这个origin序列,并且都用名称A-B-C-D来标识它们的D,那么我们从两者,我们最终得到:

master

(我们只提交一次提交,因为从其他两个Git存储库中获取了序列之后,我们就有了所有提交的问题)。

然后我们可以使自己的分支名称A--B--C--D <-- origin/master, upstream/master 指向master

D

然后创建我们的A--B--C--D <-- master (HEAD), origin/master, upstream/master

E

,然后像以前一样创建我们的A--B--C--D <-- origin/master, upstream/master \ E <-- master (HEAD) 。现在我们可以运行F来让我们的Git在git push origin master上调用Git并将其提交originE发送给它,以便 that Git —记住,它有自己的分支名称;其F当前指向master-具有以下提交:

D

然后我们的Git建议原点的Git应该对其自己的A--B--C--D <-- master [on origin] \ E--F 进行更改{em}以指向提交master。 Origin的Git易于遵循,因此现在具有:

F

您自己的Git更新了A--B--C--D--E--F <-- master [on origin] ,以使您的笔记本电脑上的存储库如下所示:

origin/master

现在,您在GitHub上单击clicky按钮,并使用“ make pull request”选项。这样做是将A-B--C--D <-- upstream/master \ E--F <-- master (HEAD), origin/master E提交到Git,您在笔记本电脑上调用F,设置一个稍微隐蔽的名称,以便上游存储库具有以下内容:

upstream

此时,您取决于控制此GitHub存储库的人员。

如果他们只是合并了您的提交,则这两个哈希ID将以它们自己的A--B--C--D <-- master [on upstream at GitHub] \ E--F <-- refs/pull/123/head [on upstream] 结尾。但是,他们使用“挤压并合并” clicky按钮。这告诉GitHub将master链的效果复制到新提交中,并将其添加到E-F中。我们将此提交称为master

EF

(一旦拉取请求被关闭并且停滞了足够长的时间,特殊的A--B--C--D--EF <-- master [on upstream at GitHub] \ E--F <-- refs/pull/123/head [on upstream] 名称可能会消失,并且两个提交refs/pull/123/head会被垃圾回收。这些详细信息取决于GitHub。)< / p>

这时,如果将便携式计算机存储库连接到GitHub存储库(称为E-F),您将获得他们没有的所有提交,即upstream。所以现在您的存储库中有:

EF

如果现在添加新的提交A--B--C--D--EF <-- upstream/master \ E--F <-- master (HEAD), origin/master ,或者已经添加了新提交,则将其提交G作为其父提交:

F

,依次类推A--B--C--D--EF <-- upstream/master \ E--F <-- origin/master \ G <-- master (HEAD) (如果已创建或已创建)。这就是您现在遇到的情况。

不想要做的是尝试将所有这些内容传递到您称为H的Git存储库中。您不能更改提交upstreamG,但是可以将它们复制到新的提交中。我们将它们称为HG',因为它们非常像 H'GHG之间的主要区别在于G'将以G'作为其父级,而EF将具有H'作为其父级:

G'

我把名字留在这里作为问号。对您而言,理想的做法是使您的名字 G'-H' <-- ??? / A--B--C--D--EF <-- upstream/master \ E--F <-- origin/master \ G--H <-- ??? 指向此新副本master。如果这样做的话,记住原始的H'的名字是什么? Git的一般回答是,您可能不需要记住原始链:完全放弃它是安全的。

使用H

复制单个提交的命令为git rebase。一次复制整个单个提交的 chain ,然后按照我们想要的方式移动分支名称的命令是git cherry-pick。但是git rebase需要三项信息:

  • 复制完所有需要复制的提交后,它应该移动哪个分支名称?
  • 实际上需要复制哪些提交?
  • 副本去哪里

默认情况下,它会从您提供的一条 信息中隐含这些信息。您运行:

git rebase

,它告诉我们要移动的名称是git checkout master git rebase other-name ,副本的目标是master所标识的提交,而要复制的提交是那些从可以到达的 other-name(以Git的惯用方式从尖端开始并向后工作),但不是master可到达的任何提交(通过尖端开始并向后工作) 。但是,此时您已经:

other-name

因此,如果我们枚举A--B--C--D--EF <-- upstream/master \ E--F <-- origin/master \ G--H <-- master 可以到达的提交,而master不能到达的提交(我们希望复制到的地方),则这就是整个upstream/master链。我们只想 E-F-G-H

解决方案是使用G-H,它使我们可以将 target (在我们的情况下为git rebase --onto)与 commit limiter参数,我们想要upstream/master,或任何标识提交origin/master的内容。因此:

F

告诉我们的Git: select提交G和H;将它们复制到git checkout master git rebase --onto upstream/master origin/master 之后的新提交中;然后使我们的名称upstream/master指向最后一次复制的提交。结果是:

master

现在我们有一个不同的问题。现在,我们必须让我们的Git将 G--H <-- master (HEAD) / A--B--C--D--EF <-- upstream/master \ E--F <-- origin/master \ G--H [abandoned] G'传递到GitHub上的Git,我们称之为H'-这部分很容易,但接着 force 更改其origin(我们称为master)以指向提交origin/master,即使这使它放弃了提交H。为此,我们可以使用E-F

--force

这将传递复制的提交,然后命令使用其Git(而不是礼貌地请求)移动其名称git push --force origin master 来指向提交master,就像我们自己的{ {1}}可以。假设他们遵守命令, 2 他们将把他们的 存储库更改为:

H'

现在,我们可以使用GitHub上的clicky按钮将请求从此GitHub存储库发送到我们在笔记本电脑上称为master的存储库中。此拉取请求将向他们传递A--B--C--D--EF--G'-H' <-- master [on origin at GitHub] 链。他们可能会再次执行“挤压并合并”操作之一,从而最终得到:

upstream

之后,我们必须丢弃G'-H'A--B--C--D--EF--GH <-- master [on upstream at GitHub] ,而将它们合并为G,就像我们必须处理H和{{ 1}}赞成合并使用GH

这里涉及的负责人,或者为什么他们的过程有点不友好

任何rebase或squash操作都涉及复制一些提交到一些新提交。新提交具有不同的新哈希ID。

任何EF操作都涉及将提交从一个存储库复制到另一个存储库。这些复制的提交 share 其哈希ID。

这意味着,如果您重新构建或压缩自己的提交,特别是您从未放弃过的提交,那么一切都非常简单。您是唯一拥有这些哈希ID的人。您现在有了更新的,更新的替换提交,并具有新的和不同的哈希ID,但是您是唯一拥有旧ID的人,并且您是唯一拥有新ID的人,并且自动 使用新的。

但是,如果您重新部署或压缩已发布的提交,则您将为接受了这些提交并开始使用它们的其他任何人工作。它们具有专门通过 其哈希ID提交的那些提交,当您用经过改进的新提交替换它们时,您将强制 them 替换其提交方式相同。

在这种情况下,他们是进行替换的人,从而迫使 you 做一些工作。他们用压缩后的EF提交替换了您的git fetch提交序列,因此您必须复制所有后续工作。

这不一定是 bad ,但对您来说,肯定比他们照原样提交您的工作还要多。如果这样做,您也可以按原样交付git push提交。


1 对于大多数提交,您可以让Git计算一个辅助ID,Git将其称为补丁ID 。修补程序ID仅取决于从提交(单个)父级到提交的 change 。也就是说,实际上,Git在父级和提交上运行E-F,或者运行EF(执行相同的操作),然后剥离一些其他可变信息,例如行号和白字,空间,并计算结果的哈希值。

计算出的补丁ID旨在解决简单的Cherry-pick操作。这对南瓜没有帮助。

2 由于您控制了GitHub存储库(即从笔记本电脑调用G-H的存储库),因此可以确保自己允许这样做。通常,控制每个存储库的人还可以控制是否允许强制推送。