Git 变基与分叉远程

  • 本文关键字:分叉 Git git rebase
  • 更新时间 :
  • 英文 :


有一个主 git 存储库; 主存储库; 主分支 - my-remote.
我把它分叉到我的个人资料中:主存储库; 主分支 - 起源

在远程分叉后添加了对远程的提交.
在分叉后添加了对源的提交。所以现在这是一个提前提交和一个提交后面。

目标:将我的遥控器重新定位为原点。以这样的方式,源应该首先从 my-remote 提交,然后从源提交。我知道这将创建新的提交(SHA)。

我正在从我的个人资料克隆.
git clone url2. -起源

添加了远程存储库作为另一个远程.
git remote add my-remote gitRepoToRemote

我在我的本地主分支上,该分支正在跟踪 origin.
Try to rebase.
git fetch --all.
git rebase my-remote/master.

遇到冲突.
手动解决.
然后git add file-with-conflict.
继续变基git rebase --continue.
git push origin HEAD:master.
出错。为什么我在推送前解决冲突时会收到此错误?

! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'gitRepoToOrigin'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details

正如评论中暗示(但未说明)的那样,您将需要某种强制推送。 原因并不复杂,但找出原因而不是谈论它有很大帮助。 这里的问题是,用一种有趣的方式来说,你有三个不同的存储库,都名为 Bruce,一个名为 Bruce 的新提交,以及一个名为 Bruce 的新提交的重写。 你现在需要告诉

布鲁斯忘记布鲁斯,因为布鲁斯有了新的布鲁斯,布鲁斯需要与布鲁斯匹配。(以上有点矫枉过正,但也见有过有过判决。

与其称每个人和所有东西为"Bruce",或者试图给出每个提交的确切哈希 ID——这会导致类似(尽管不同)的问题,因为人类不擅长哈希 ID,将每个 ID 转换为类似"Bruce"的哔哔声——让我们将每个存储库绘制为一系列提交,提交由单个大写字母指定。 我们从您最初所说的"主存储库"开始,然后在您使用git remote add时称为my-remote

my-remote:
...--F--G--H   <-- master

此存储库中存在名称master。 该名称存储提交H的哈希 ID(一些丑陋的大哈希 ID)。 提交H,在右边,是最新的。 它包含:

  • 每个文件的完整快照,以及
  • 包含早期提交G的哈希 ID 的元数据

因此,提交H向后链接到早期的提交G。 当然,较早的提交G具有每个文件的快照(来自较早的文件),以及较早的提交F的哈希 ID。 这一直重复到时间的开始:这就是 Git 存储提交的方式。

然后,您使用 GitHub 的fork按钮制作了此存储库的副本,我们将将其称为origin。 此副本有自己的分支名称,但与原始存储库共享实际提交1无论如何,在 GitHub 上的此副本中,名称master具有相同的哈希 ID:

origin:
...--F--G--H   <-- master

然后,您将GitHub分叉克隆到您自己的计算机上(我们称之为"笔记本电脑",即使它是台式机或台式计算机,只是为了给它一个令人难忘但独特的名称)。 这复制了提交,但副本与原始副本相同——它们必须是;任何提交后都不能更改任何部分,然后将其分支名称复制到远程跟踪名称,因此您拥有:

laptop:
...--F--G--H   <-- origin/master

然后,您的git clone命令根据您的-b master参数或您缺少任何-b参数以及 GitHub 的建议在笔记本电脑存储库中创建一个新的master分支,即您的 Git 在此处使用名称master

laptop:
...--F--G--H   <-- master, origin/master

最后,在您的笔记本电脑上,您的 Git 运行了git checkout master以从提交H填充一个工作树(以及 Git 的索引,尽管我们不会在这里介绍这一点),使master当前分支并在当前提交H提交laptop

laptop:
...--F--G--H   <-- master (HEAD), origin/master

1他们是共享原始底层提交还是复制它们与我们无关。 事实是,提交是 100%,逐位相同。 这意味着只要my-remoteorigin都存储在同一台物理计算机上,它们就可以共享底层提交。 如果它们存储在具有独立永久内存系统(旋转磁盘、闪存等)的独立物理机器上,它们需要使用原始提交字节的副本,而不是从字面上共享底层存储,但我们并不关心这一点。

<小时 />

到目前为止的摘要

截至此时(过去的某个时间),所有三个存储库的内容看起来非常相似。 您的笔记本电脑存储库中的主要且可观察到的区别是,您有origin/master,以及提交H的工作副本。 除了这些差异(对于完成工作很重要,但在其他方面无关紧要)之外,所有三个看起来都如下所示:

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

事物会随着时间的推移而变化

但是,接下来,您在笔记本电脑上进行了新的提交。 我们称之为提交IH之后的下一个字母。 所以在笔记本电脑上,我们现在有:

laptop:
...--F--G--H   <-- origin/master

I   <-- master (HEAD)

与此同时,有人在 GitHub 上发送了一个新的提交,以便在my-remote存储库上master。 通常我会称此提交为J但没有明显的原因,我将在这里将其称为K。 所以他们有这个,我也会无缘无故地奇怪地画出来:

my-remote:
K   <-- master
/
...--F--G--H

请注意,名为origin的存储库尚未发生任何更改。

此时,您在笔记本电脑上运行git push。 无需过多担心您在此处传递给git push(如果有的话)的确切参数,这最终会将您的笔记本电脑具有的提交发送到originorigin没有。 所以现在,在origin上,你有:

origin:
...--F--G--H

I   <-- master

当此推送成功时,您的笔记本电脑将更新其origin/master。 这是因为你的git push让你的笔记本电脑通过互联网呼叫GitHub,要求GitHub连接到存储库origin(这有效)。 你的笔记本电脑 Git发送了构成提交I的数据,然后要求GitHub 设置他们的master他们说确定,所以你的笔记本电脑 Git 知道originGit 有其指向提交Imaster。 所以现在laptop你有:

laptop:
...--F--G--H

I   <-- master (HEAD), origin/master

我们不需要我们在这里绘制的图表中的所有这些小扭结,但我故意使用它们。 请注意,originlaptop都还不知道提交K! 同样,my-remote从未听说过提交I

乐趣(?

此时,你开始做一些花哨的事情:

添加了远程存储库

作为另一个远程存储库。 git remote add my-remote gitRepoToRemote

您的laptopGit 现在知道存在另一个名为my-remote的 Git。它尚未连接到它。

git fetch --all

大多数人滥用--all,但在这里你正确地使用了它(恭喜! ).--all选项指示 Git 针对每个远程运行git fetch

这有点矫枉过正,因为这让你的 Git 调用了originmy-remote,以查看这两个存储库中的新功能(如果有的话)。origin没有什么新东西,也不可能有,因为你将是唯一更新它的人。 不过没关系! 但是,当你的 Git 调用名为my-remote的 Git 时,有一些新的东西他们提交了K,而你没有。

因此,您的 Gitmy-remote中获取了提交K,并将其保留在您的存储库中。 然后,您的 Git 创建了一个远程跟踪名称(my-remote/origin,因为这是与my-remote第一次联系)。 所以现在laptop有:

laptop:
K   <-- my-remote/master
/
...--F--G--H

I   <-- master (HEAD), origin/master

(现在你可以看到为什么我在图形图中保留了扭结)。

你可以告诉你的 Git 告诉originGit 关于提交K(在进程2个提交。 您可以选择提交I提交K,但不能同时选择两者。 要指向这两个提交(以便可以找到IK),您至少需要两个名称。

您的存储库在laptop上具有必要的名称:my-remote/master指向Kmaster指向Iorigin/master也指向I,所以你现在可以在任何地方自由地拥有你的master点:要查找提交K,您可以使用名称my-remote/master,而要查找I,您可以使用名称origin/master


2由于originmy-remote的分支,理论上origin可以更直接地看到提交K。 从理论上讲,GitHub可以安排这一点。 如果他们这样做,如何以及何时这样做取决于他们:你的 Git 不需要知道任何关于它的信息。 如果、如何以及何时 GitHub 添加Web机制,而不是这种有点迂回的获取和推送方法,将提交K引入origin中,同样取决于 GitHub。 但是,从今天开始,您必须使用环形交叉路口方法。

<小时 />

变基

rebase 命令可以这样总结:我有一些我最喜欢的提交,但这些提交有一些我不喜欢的东西。 我知道我不能改变任何现有提交的内容,所以我想将现有的提交复制到新的和改进的提交中,其中我不喜欢的东西被更改,我喜欢的东西被保留。

让我们再看看我们在这一点上laptop的内容:

laptop:
K   <-- my-remote/master
/
...--F--G--H

I   <-- master (HEAD), origin/master

我们不喜欢提交I有两件事:

  • 它没有来自提交K的更新,并且
  • 它出现在提交H之后,而不是在提交K之后。

我们喜欢它的是这些东西:

  • 它对提交H有一些更改,并且
  • 它有一个精心制作的、令人愉快的详细提交消息,解释了为什么它有这些变化。

我们希望保留这两件事,同时做出新的承诺来修复我们不喜欢的两件事。 我们使用git rebase来做到这一点(在内部,它包括挑选我们想要复制的每个提交;只有一个,这就是为什么我们只得到一个新的提交)。

不用太担心这是怎么回事——有时会变得混乱:

有冲突。 手动解决。 然后 git 添加冲突文件。 继续变基git rebase --continue

但看起来您正确地完成了所有这些操作 -最终结果是一个新的提交:

laptop:
J   <-- master (HEAD)
/
K   <-- my-remote/master
/
...--F--G--H

I   <-- origin/master

这个新的提交,J,在现有的提交K之后,并进行正确的更改(由于现在解决的冲突而根据需要进行修改),并携带正确的提交消息文本。 因此,新的提交J是我们想要的。 它完全淘汰了旧的提交I

我们怎么知道J取代了II不再有用?这里唯一的答案是,我们知道它,因为我们做到了Git不知道也不在乎。 只有我们知道和关心。 我们的 Git,在laptop上,我们的master指向现在提交J。 提交I已被放弃,除了我们的origin/master仍然可以找到它。

我们现在想告诉origin也这样做

我们现在可以运行:

git push origin master

或:

git push origin HEAD:master

让我们的 Git 调用 GitHub,让他们连接到origin存储库,并向他们发送新的提交J。 这部分有效 - 但随后我们要求他们将master设置为指向提交J他们拒绝。 他们特别说不,错误说如果我这样做,我将放弃一些现有的提交:

! [rejected]        master -> master (non-fast-forward)

这条non-fast-forward消息意味着我将丢失一些master提交。

在这种情况下,他们将丢失的提交是提交I。 提交I被提交J过时但他们不知道。我们必须迫使他们放弃承诺I.

我们这样做的方法是使用各种强制选项之一(相当适合星球大战日):

git push --force origin master

或:

git push --force-with-lease origin master

这些之间是有区别的,但在这种特殊情况下(origin是"你的"分叉,由你控制,而不是被其他人推到),这并不重要。 它们都像常规git push一样工作,只是最后,而不是发送礼貌的请求:如果没问题并且没有丢失任何东西,请更新您的master以记住提交J现在,这种推送以表单的命令结束更新您的master以指向提交JGitHub 上的 Git 将服从此命令,并在origin存储库中留下以下内容:

origin:
J   <-- master
/
K
/
...--F--G--H

I   ???

GitHub 上的 Git 最终(这可能需要一天或一周或更长时间)运行git gc删除提交I,我们现在应该开始将其存储库绘制为:

origin:
...--F--G--H--K--J   <-- master

来简化它。 我们也可以从您的笔记本电脑上开始执行此操作,除了我们需要my-remote/master指向K,尽管如此,我们得到:

laptop:
J   <-- master (HEAD), origin/master
/
...--F--G--H--K   <-- my-remote/master

你的笔记本电脑 Git 实际上会挂起来提交I很长一段时间——默认情况下至少 30 天——然后扔掉它,但由于它不可见(在git log输出中),我们不必费心绘制它,在这一点上。

最新更新