有一个主 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-remote
和origin
都存储在同一台物理计算机上,它们就可以共享底层提交。 如果它们存储在具有独立永久内存系统(旋转磁盘、闪存等)的独立物理机器上,它们需要使用原始提交字节的副本,而不是从字面上共享底层存储,但我们并不关心这一点。
到目前为止的摘要
截至此时(过去的某个时间),所有三个存储库的内容看起来非常相似。 您的笔记本电脑存储库中的主要且可观察到的区别是,您有origin/master
,以及提交H
的工作副本。 除了这些差异(对于完成工作很重要,但在其他方面无关紧要)之外,所有三个看起来都如下所示:
...--F--G--H <-- master
事物会随着时间的推移而变化
但是,接下来,您在笔记本电脑上进行了新的提交。 我们称之为提交I
,H
之后的下一个字母。 所以在笔记本电脑上,我们现在有:
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
(如果有的话)的确切参数,这最终会将您的笔记本电脑具有的提交发送到origin
,origin
没有。 所以现在,在origin
上,你有:
origin:
...--F--G--H
I <-- master
当此推送成功时,您的笔记本电脑将更新其origin/master
。 这是因为你的git push
让你的笔记本电脑通过互联网呼叫GitHub,要求GitHub连接到存储库origin
(这有效)。 你的笔记本电脑 Git发送了构成提交I
的数据,然后要求GitHub 设置他们的master
,他们说确定,所以你的笔记本电脑 Git 知道origin
Git 有其指向提交I
master
。 所以现在laptop
你有:
laptop:
...--F--G--H
I <-- master (HEAD), origin/master
我们不需要我们在这里绘制的图表中的所有这些小扭结,但我故意使用它们。 请注意,origin
和laptop
都还不知道提交K
! 同样,my-remote
从未听说过提交I
。
乐趣(?
此时,你开始做一些花哨的事情:
添加了远程存储库作为另一个远程存储库。 git remote add my-remote gitRepoToRemote
您的laptop
Git 现在知道存在另一个名为my-remote
的 Git。它尚未连接到它。
git fetch --all
大多数人滥用--all
,但在这里你正确地使用了它(恭喜! ).--all
选项指示 Git 针对每个远程运行git fetch
。
这有点矫枉过正,因为这让你的 Git 调用了origin
和my-remote
,以查看这两个存储库中的新功能(如果有的话)。origin
没有什么新东西,也不可能有,因为你将是唯一更新它的人。 不过没关系! 但是,当你的 Git 调用名为my-remote
的 Git 时,有一些新的东西:他们提交了K
,而你没有。
因此,您的 Git从my-remote
中获取了提交K
,并将其保留在您的存储库中。 然后,您的 Git 创建了一个远程跟踪名称(my-remote/origin
,因为这是与my-remote
的第一次联系)。 所以现在laptop
有:
laptop:
K <-- my-remote/master
/
...--F--G--H
I <-- master (HEAD), origin/master
(现在你可以看到为什么我在图形图中保留了扭结)。
你可以告诉你的 Git 告诉origin
Git 关于提交K
(在进程2个提交。 您可以选择提交I
或提交K
,但不能同时选择两者。 要指向这两个提交(以便可以找到I
和K
),您至少需要两个名称。
您的存储库在laptop
上具有必要的名称:my-remote/master
指向K
,master
指向I
。origin/master
也指向I
,所以你现在可以在任何地方自由地拥有你的master
点:要查找提交K
,您可以使用名称my-remote/master
,而要查找I
,您可以使用名称origin/master
。
2由于origin
是my-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
取代了I
,I
不再有用?这里唯一的答案是,我们知道它,因为我们做到了。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
以指向提交J
!GitHub 上的 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
输出中),我们不必费心绘制它,在这一点上。