如何克隆、同步/更新/推送一个派生分支与上游主分支

4
我认为我已�阅读了一些教程,但我被完全基础的东西��了(我很少使用命令行 git,请�心点😊)。
我想�的就是更新我的 fork (https://github.com/abelbraaksma/visualfsharp) 到最新版本的 Master (https://github.com/Microsoft/visualfsharp) �上游仓库。由�我有一些���的本地更改,所以我决定创建一个新的克隆(之�我使用过 GUI 工具,但它们�常混乱和�制性,所以我放弃了它们,转而深入研究 git 命令 ;)。
我�了以下�作:
cd /D/Projects/OpenSource/VisualFSharp2
git init
git clone https://github.com/abelbraaksma/visualfsharp
git fetch https://github.com/Microsoft/visualfsharp
git remote add upstream https://github.com/Microsoft/visualfsharp
git remote add origin https://github.com/abelbraaksma/visualfsharp
git fetch upstream
git checkout master
git merge upstream/master

前两条命令的输出为:

git checkout master
已经位于 'master' 分支
您的分支已经和 'upstream/master' 保持一致。

git merge upstream/master
已经是最新的了。

我意识到我做错了一些事情,因为我来自 SVN 和 Mercurial 的世界,所以经常被术语搞混。我明白当前我在上游仓库的 "master" 分支。但是我需要将上游仓库合并到我的 fork 仓库(即 origin)。我认为我需要更新本地副本以匹配我的 fork 的 head(但 git checkout master 并没有这样做)。
我基本上尝试遵循 这个同步指南,结合配置远程分支
我到底哪里搞混了,或者说哪些命令我搞反了?
运行 git remote -v 命令可以得到以下输出:
origin  https://github.com/abelbraaksma/visualfsharp (fetch)  
origin  https://github.com/abelbraaksma/visualfsharp (push)  
upstream        https://github.com/Microsoft/visualfsharp (fetch)  
upstream        https://github.com/Microsoft/visualfsharp (push)
2个回答

7

简而言之

你的情况还可以,但是你有一个额外的代码库,最好是把它删除。通常应该先克隆(使用 git clone)想要在Git中称之为origin的代码库,然后 git remote add upstream <另一个url> ,从那里开始工作。

阅读下面的详细说明,了解你现在的情况以及如何处理它。

详细说明:你所做的事情

git init
这将在当前目录中创建一个新的空Git存储库。如果已经有一个Git存储库,即如果git rev-parse --git-dir会打印一些目录名称而不是失败并说“我找不到存储库”,它基本上什么也不做,这样运行是安全的。虽然这里有一些特殊情况,但你不太可能遇到它们。由于你打算克隆存储库,所以你不需要这样做,因为正如我们马上要看到的那样,git clone也会执行git init
在我们继续下面的git clone之前,让我们花点时间了解一下新的空存储库的奇怪状态。你可能已经熟悉了像master这样的“分支名称”实际上只持有一个(1)提交的哈希ID的想法。Git使用该名称来查找分支上的最后一个提交,Git称其为“尖端提交”。Git然后使用尖端提交来查找先前或“父”提交,并使用父级的父级来向后工作。通过跟随父级链,Git找到所有可从分支名称到达的提交。
但是,空存储库没有提交。对于名称master指向的尖端,不存在可以在名称master下存储其哈希ID的最新提交。Git的解决方案是暂时没有master分支。同时,Git声明你正在“分支master”上,正如git status所说-因此你正在一个尚不存在的分支上。
这种怪异的情况后来会发挥作用。现在,让我们继续进行git clone,看看它做了什么。在这种情况下,它创建了另一个独立的存储库,你随后根本不使用它。
git clone https://github.com/abelbraaksma/visualfsharp

这基本上等价于以下一系列命令:

  • mkdir visualfsharp: 在当前目录(当前目录为/D/Projects/OpenSource/VisualFSharp2)中创建一个新的子目录
  • cd visualfsharp: 进入新的子目录
  • git remote add origin https://github.com/abelbraaksma/visualfsharp: 添加名为origin的远程库(这也会配置一些设置)
  • git fetch origin: 获取所有他们的提交
  • git checkout somebranch,其中somebranch通常是master:从origin/*名称之一创建一个本地分支名称,并将其作为当前分支。

完成后,您回到原始目录(即仍然是/D/Projects/OpenSource/VisualFSharp2)。请注意,您的原始目录是一个Git存储库,而其visualfsharp子目录是另一个存储库。

现在,我们将看到您再次执行大多数这些命令,但这次应用于当前为空的存储库,该存储库处于奇怪的状态,即您在master上,但master不存在。

git fetch https://github.com/Microsoft/visualfsharp

这将调用位于 https://github.com/Microsoft/visualfsharp 的 Git 并从中获取提交和其他对象,导入到你之前空的仓库(不是你刚克隆的那个!)。 这类似于 git fetch remote,但没有远程跟踪名称——没有 origin/*upstream/* ——因为没有远程来构建这样的名称。 这种特殊形式的 git fetch 可追溯到古老时代(2005年),在发明 git remote 之前,你可能永远不应该使用它。 它不会对你造成有害的影响,只是在这里不是有益的

git remote add upstream https://github.com/Microsoft/visualfsharp
git remote add origin https://github.com/abelbraaksma/visualfsharp

以下这些内容都是正确的:它们设置了两个远程仓库。远程仓库只是一个短名称,其作用如下:

  • 保存一个URL
  • 提供了远程跟踪分支名的前缀,分别为 upstream/*origin/*
git fetch upstream
这几乎是你之前执行的“git fetch”的重复操作。不过,这次你的Git使用了你分配的名称——“upstream”——来获取URL。因此,你的Git再次调用了https://github.com/Microsoft/visualfsharp上的Git。你的Git从它们那里获取所有新提交(以及与这些提交一起需要的任何其他Git对象),自上次提取以来——可能没有,具体取决于你在第一次提取和第二次提取之间的时间间隔。如果你之前没有运行过“git fetch”,则会在获取所有提交时获取每个Git对象。
但现在,已经获取了提交,存在一个关键的区别:你的Git将他们的所有分支名称更名为你的远程跟踪名称拼写为upstream/whatever。现在可以这样做,因为你正在使用远程而不仅仅是原始URL。远程——字面意思上的字符串upstream——让你进行了这种重命名。1因此,你的Git和他们的Git非常快速地传输了所有新对象(可能没有),然后你的Git根据他们的master等设置了你的upstream/master等。
git checkout master
这里涉及到存储库的奇怪状态。你的Git会显示:
Branch master set up to track remote branch master from upstream.
Already on 'master'

发生的情况是git checkout查找master,但未找到(因为您没有分支),所以它创建了一个分支。首先,它查找所有远程跟踪名称,在此例中为upstream/*。它找到了一个匹配的分支:master vs upstream/master,然后创建了您的master,指向与您的upstream/master相同的提交。然后还设置了您的master,将upstream/master设置为其上游设置。
在完成所有这些操作 - 创建master后,git checkout尝试将您放在master上,并发现您只在master上并打印出令人困惑的“已经在”消息。在此过程中,您的HEAD已正确附加,检出所有文件,即,复制它们到索引和工作树。
您可能或可能不希望使用此方式设置您的master - 一旦创建了origin/master,您更有可能希望您的master开始指向与origin/master相同的提交,并将origin/master设置为其上游。有关上游的更多信息,即将一个分支设置为跟踪另一个分支的含义,请参见例如我的答案以及如何使用git设置多个跟踪级别的分支
您的最后一条命令是:
git merge upstream/master

你自己的 master 分支刚刚从你的 upstream/master 创建而来,因此没有需要合并的内容:这两个名称都指向相同的提交哈希 ID。

你还没有从你的 origin 获取任何内容。现在你可能应该这样做:

git fetch origin

一旦你这样做了,你将拥有origin/masterupstream/master3。如果你想要自己的master跟踪origin/master而不是upstream/master(并从那里开始),你应该:
  1. 确保没有需要提交的内容(考虑到上述顺序,不应该有,但在使用git reset --hard之前检查一下总是明智的);
  2. 运行git reset --hard origin/master,使你的master指向与origin/master相同的提交;并且
  3. 运行git branch --set-upstream-to=origin/master master来更改上游设置。
现在,你可以运行git merge upstream/master。如果上游自你的分支派生以来有新的提交,那么它将合并这些提交,如果必要,使用完整合并或者快进不真正合并的操作。
在任何情况下,你可能想删除额外的存储库。
1 Git实现此重命名的基本机制非常复杂,可能是由于历史原因,但在正常实践中,它只是“将其master更改为你的remote/master”等等。 2请注意,Git在这里使用更加混乱的术语:如果一个分支名称“跟踪”一个“远程跟踪名称”(这是你的Git基于找到的另一个Git的名称创建的本地名称,该Git的URL通过“远程”找到),那么我们称其为分支(或分支名称)的“上游”。这与已跟踪和未跟踪文件完全不同。天哪! 3我在这里假设https://github.com/abelbraaksma/visualfsharp上的Git存储库是你自己的,并且你使用GitHub的Web GUI界面中的“fork a repository”点击按钮创建了它。当你这样做时,GitHub在GitHub本身上进行了相当复杂的git clone,从你选择的任何源存储库创建了你的存储库。这意味着你的GitHub存储库具有与原始源存储库相同的所有分支。
GitHub所克隆的副本不会重命名分支。它还设置了特殊的GitHub专用功能,以允许GitHub提供的拉取请求工具;这不是Git的一部分。GitHub团队还安排共享底层磁盘对象,并有各种其他方法,使其比天真地完成要快得多。因此,从原则上讲,这是一个常规的克隆,但他们通过他们的Web界面进行了调整,使其更加有用。这就是他们让你使用GitHub而不仅仅是自己完成所有工作的方式。

谢谢你这个棒极了的答案!比我想象中的更好,整个教程从我的“如何不做”到你的“如何做以及为什么”都非常好。 - Abel
"Git使用了更加令人困惑的术语" >> 真没错! "git fetch追溯到古老的时代(2005年)...可能永远都不应该使用它。" >> 是的,我以为它和使用别名(remote)是同义词,但现在我理解它们并不完全相同,使用别名在幕后会添加额外的操作。 - Abel
很棒的答案!但是为什么每次克隆我的分支时都要设置上游呢?Git不应该已经知道我从哪个仓库进行了分支吗? - Sharak
2
@Sharak:不,Git没有办法知道这个。GitHub知道它,但他们不告诉Git。git clone的模型没有地方放置那些信息。 - torek

3
我做的事情与你很相似,以下是我如何操作:
  1. 获取fork的URL。
  2. 切换到终端。使用cd命令切换到我们想要克隆的目录。
  3. git clone fork-url-here命令将克隆我们的fork并将其设置为remote/origin
  4. cd fork-name/切换到克隆的目录。
  5. git remote add upstream upstream-url-here命令将上游设置为remote/upstream
  6. git fetch upstream命令从上游拉取所有分支。
  7. git checkout master命令,因为我们已经在origin/master上,所以会收到一条通知信息,说明一切正常,这不表示有问题。
  8. git branch -a命令列出所有本地+remote/origin/*+remote/upstream/*分支,其中之一将是upstream/master(最初我使用git branch命令只显示本地分支,这让我有点困惑,因为我看不到列表中的upstream/master)。
  9. git merge upstream/master命令将把upstream/master分支合并到当前分支(即origin/master),从而与上游同步。
你遇到的问题是在将上游添加为远程前,你从上游获取了(代码块中第四行)。这会阻止你获取所有上游的分支。其他东西看起来对我来说很好。
附:我看到这是一个老问题,但我认为我可以帮助像我一样的git初学者,他们可能匆忙而不能阅读toerk所给出的非常好的、详细的答案。

编辑/扩展 1:另一件事是将fork(origin)主分支强制与原始repo(upstream)主分支保持相同水平。

!!!注意:这将放弃您在origin主分支上做的任何和所有提交!!!

如果您非常确定要继续,请按照上述步骤,在第9步中使用以下命令:

  • git reset --hard upstream/master命令将本地origin/master的内容替换为upstream/master的内容。
  • git push origin master --force命令将强制推送您对远程origin所做的更改。
我建议进行此更改,因为最近我自己不得不这样做,并发现这可能有助于某些人(前提是他们知道自己在做什么)。但是,因为它也有可能破坏有价值的工作,所以我已经多次强调了其中的风险。

1
感谢您的指导。我现在对术语和git命令有了更好的理解,但这确实可能仍然有助于其他人 :) - Abel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接