这是我的起源,最初是上游的,
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历史。有人可以告诉我正确的方法吗?我已经尝试过进行变基,压扁和其他操作,但是没有用,可能是我做这些的方法可能是错误的。我什至想不出适合我问题的标题。
答案 0 :(得分:3)
您将要使用git rebase --onto
。正确使用它有点棘手,特别是因为您将需要用力推动自己的叉子。
我想再次发出请求,但是由于git认为提交E,F和EF不同,我该怎么做?
它们不同。
请记住,提交的“真实名称”是其原始哈希ID。 1 E的哈希ID与F和EF的哈希ID不同;这三个都是独立的唯一提交。 EF是挤压E和F的结果没关系(嗯,对您来说很重要; 问题在于,它对 Git 没有任何帮助,所以Git无法/不会在这里为您提供帮助。
我们需要了解Git是分布式的,Git实现这种分布的方式是给出提交的副本,这些副本由其哈希ID标识。每个提交本身主要是一个独立的快照,只是每个提交记录其 parent 提交的哈希ID。 名称,无论是分支名称(如master
或develop
还是远程跟踪名称(如origin/master
或upstream/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/master
或upstream/master
,具体取决于我们是否在{{ 1}}或git fetch origin
。如果git fetch upstream
和upstream
都具有这个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并将其提交origin
和E
发送给它,以便 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存储库中。您不能更改提交upstream
和G
,但是可以将它们复制到新的提交中。我们将它们称为H
和G'
,因为它们非常像 H'
和G
。 H
和G
之间的主要区别在于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。
任何E
或F
操作都涉及将提交从一个存储库复制到另一个存储库。这些复制的提交 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
的存储库),因此可以确保自己允许这样做。通常,控制每个存储库的人还可以控制是否允许强制推送。