[英]How to send pull request when git histories are different but changes are same?
这是我的起源和最初的上游,
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历史。 有人可以告诉我正确的方法吗? 我已经尝试过做rebase,壁球和不同的事情但是从来没有用过。可能我做他们的方式可能是错的。 我甚至无法想到我的问题的适当标题。
你会想要使用git rebase --onto
。 正确使用它有点棘手,特别是因为你需要强制推送到你自己的fork。
我想再次提出拉取请求,但是我怎么能这样做因为git认为提交E,F和EF是不同的?
他们是不同的。
请记住,提交的“真实名称”是其原始哈希ID。 1 E的哈希ID与F和EF的哈希ID不同; 这三个都是独立的,独特的提交。 EF是压缩E和F的结果并不重要(好吧,这对你很重要;问题是它对Git没有任何好处,所以Git不能/不会在这里帮助你)。
我们需要了解Git是分布式的,Git实现此分发的方式是提供由其哈希ID标识的提交副本。 每个提交本身主要是一个独立的快照,除了每个提交记录其父提交的哈希ID。 一个名称 ,无论是像master
或develop
这样的分支名称,还是像origin/master
或upstream/master
这样的远程跟踪名称,都是用于记住一个特定提交的Git设备。 一个提交会记住另一个,前一个(父)提交,父级会记住它的父级,依此类推。 因此,当我们查看笔记本电脑上的任何一个特定存储库时,我们可以绘制提交图:
A <--B <--C ... <--H <--master
名称 master
存储commit H
的实际哈希ID。 H
存储G
的ID,其存储F
的ID,等等一直返回到A
(如果A
是存储库中的第一个提交,它没有父级,这可以让Git停止向后移动。)
因此,Git需要一个名称来查找最后一次提交--Git称之为分支的提示 - 然后Git使用每个提交在历史记录中向后工作。 历史本身就是Git可以通过从你拥有的所有名称开始并向后工作所能达到的所有提交 。
当我们连接任何两个不同的Git存储库时,我们有一个发送给另一个发送方具有接收方没有的提交,以及接收方可以识别提示提交的名称。 所以,如果我们开始:
A--B--C--D <-- master
在一个Git存储库中,并有另一个空Git调用它并从中接收,空Git获取ABCD
序列和名称master
。 如果接收Git正在进行git fetch
,则接收器将其master
重命名为origin/master
或upstream/master
,具体取决于我们是在执行git fetch origin
还是git fetch upstream
。 如果upstream
和origin
都有这个ABCD
序列并且两者都通过名称master
识别它们的D
,并且我们从两者中获取,我们最终得到:
A--B--C--D <-- origin/master, upstream/master
(我们只提交一次提交,因为在我们从其他两个Git存储库中获取序列之后,我们有所有提交的提交)。
然后我们可以使我们自己的分支名称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 push origin master
有我们的Git调出的Git在origin
,并将其发送提交E
和F
,让所有 Git -这,记住,有自己的分公司名称; 它的master
目前指向D
-has这些提交:
A--B--C--D <-- master [on origin]
\
E--F
我们的Git然后建议起源的Git应该改变它自己的master
指向提交F
Origin的Git很容易遵守,现在它具有:
A--B--C--D--E--F <-- master [on origin]
您自己的Git会更新您的origin/master
以便您的笔记本电脑上的存储库看起来像这样:
A-B--C--D <-- upstream/master
\
E--F <-- master (HEAD), origin/master
现在,您浏览GitHub上的clicky按钮并使用“make pull request”选项。 这样做的目的是将提交E
和F
传递给Git,您在笔记本电脑上调用upstream
,设置一个隐藏的名称,以便上游存储库具有以下功能:
A--B--C--D <-- master [on upstream at GitHub]
\
E--F <-- refs/pull/123/head [on upstream]
此时,您依赖于控制此GitHub存储库的人员。
如果他们只是合并你的提交,这两个哈希ID将在他们自己的master
身上结束。 但相反,他们使用“壁球和合并”点击按钮。 这告诉GitHub 将 EF
链的效果复制到他们添加到master
的新提交中。 我们称这个提交EF
:
A--B--C--D--EF <-- master [on upstream at GitHub]
\
E--F <-- refs/pull/123/head [on upstream]
(一旦拉取请求关闭并且死了足够长,特殊refs/pull/123/head
名称可能就会消失,两次提交EF
会收集垃圾。这些细节都归GitHub所有。)
此时,如果您将笔记本电脑存储库连接到您调用upstream
的GitHub存储库,您将获得他们没有的任何提交,并且提交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仓库upstream
。 您无法更改提交G
和H
,但可以将它们复制到新提交。 我们称之为G'
和H'
因为它非常像 G
和H
G
和G'
之间的主要区别在于G
G'
将EF
作为其父级, H'
将G'
作为其父级:
G'-H' <-- ???
/
A--B--C--D--EF <-- upstream/master
\
E--F <-- origin/master
\
G--H <-- ???
我把这里的名字留作问号。 对你来说最理想的是让你的名字master
这个新副本H'
。 如果你那样做,那么什么名字会记住原来的H
? Git的一般答案是你可能不需要记住原始链:完全放弃它是安全的。
git rebase
复制一个单独提交的命令是git cherry-pick
。 这个命令一下子复制了整个单独的提交链 ,然后按照我们想要的方式移动分支名称 ,就是git rebase
。 但是git rebase
需要三条信息:
它得到这些,在默认情况下,由一个整块的,你给它的信息,而你暗示之一。 你跑:
git checkout master
git rebase other-name
并且它告诉它要移动的名称是master
,副本的目标是由other-name
标识的提交,并且要复制的提交是从 master
可以访问的 (通过从提示开始并以Git通常的方式向后工作) ),但不能从other-name
到达任何提交(从该提示开始并向后工作)。 但此时你有:
A--B--C--D--EF <-- upstream/master
\
E--F <-- origin/master
\
G--H <-- master
因此,如果我们枚举从master
可以从upstream/master
(我们希望副本去的地方)无法访问的提交,那就是整个EFGH
链。 我们只想要GH
。
解决方案是使用git rebase --onto
,它允许我们分割目标 - 在我们的情况下,即upstream/master
- 来自提交限制器参数 ,我们想要origin/master
,或任何标识提交F
东西。 因此:
git checkout master
git rebase --onto upstream/master origin/master
告诉我们的Git: select提交G和H; 将它们复制到upstream/master
之后的新提交中; 然后让我们的名字master
指向最后这样复制的提交。 结果是:
G--H <-- master (HEAD)
/
A--B--C--D--EF <-- upstream/master
\
E--F <-- origin/master
\
G--H [abandoned]
现在我们有一个不同的问题。 现在我们必须让我们的Git将G'
和H'
传递给GitHub上的Git,我们称之为origin
- 这部分很容易 - 然后强制 Git改变它的master
,我们称之为origin/master
,指向提交H
,即使这让它放弃了提交EF
。 为此,我们可以使用--force
:
git push --force origin master
这会传递复制的提交,然后命令他们的Git而不是请求礼貌地将其名称master
移动到指向提交H'
,就像我们自己的master
一样。 假设他们遵守命令, 2他们将更改其存储库以读取:
A--B--C--D--EF--G'-H' <-- master [on origin at GitHub]
现在我们可以使用GitHub上的clicky按钮从这个GitHub存储库发出拉取请求到我们在笔记本电脑upstream
调用的存储库。 该拉取请求将向它们传递G'-H'
链。 他们可能会再次进行“壁球和合并”操作之一,以便他们最终得到:
A--B--C--D--EF--GH <-- master [on upstream at GitHub]
在此之后,我们将不得不放弃我们的G
和H
,转而支持他们的GH
,就像我们与E
和F
有利于他们的组合EF
。
任何rebase或squash操作都涉及将一些提交复制到一些新的提交。 新提交具有新的不同哈希ID。
任何git fetch
或git push
操作都涉及将提交从一个存储库复制到另一个存储库。 这些复制的提交共享其哈希ID。
这意味着,如果你改变或压制自己的提交,特别是承诺你从来没有放弃给别人,一切都很简单。 您是唯一拥有这些哈希ID的人。 您现在拥有更新,更闪亮的替换提交,具有新的和不同的哈希ID,但您是唯一拥有旧的哈希ID的人,并且您是唯一拥有新的哈希ID并且您自动使用新哈希的人。
但是,如果您对已发布的提交进行重新设定或压缩,那么您正在为已经执行这些提交并开始使用它们的任何其他人开展工作。 他们有那些专门提交通过哈希值的ID,当你有新的和改善的提交替换它们,你强迫他们来代替他们提交了同样的方式。
在这种情况下,他们正在进行替换,从而迫使你做一些工作。 他们已经用压缩的EF
提交替换了您的EF
提交序列,因此您必须复制所有后续工作。
这不一定是坏事 ,但对于你来说,肯定比你刚刚按原样提交更多的工作。 如果他们这样做了,你本可以按原样交付你的GH
提交。
1对于大多数提交,Git可以使用Git计算的辅助ID,即Git调用补丁ID 。 补丁ID仅取决于从提交(单个)父级到提交的更改 。 也就是说,实际上,Git在父级运行git diff
并运行或运行git show
,它执行相同的操作 - 然后去掉一些其他可更改的信息,例如行号和空格,并计算哈希值结果。
计算出的补丁ID旨在解决简单的挑选操作。 它对南瓜没有帮助。
2,因为你能够控制自己的GitHub存储库中调用一个origin
,从你的笔记本电脑,你可以确保你允许自己做到这一点。 通常,控制每个存储库的人还控制是否允许强制推送。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.