有没有办法将另一个 repo 的 master 镜像到一个非 master 分支?

Is there a way to mirror another repo's master to a non-master branch only?

我有两个回购 - 回购 1 和回购 2,回购 1 有两个分支 - 主分支和分支 1。我希望能够将回购 2 的主分支仅镜像到分支 1,并保留回购 1 的主分支原样。有没有办法做到这一点?是否还有一种方法可以将特定分支镜像到另一个仓库的主服务器(即镜像仓库 1 的分支 1 到仓库 2)?

我试过按照典型的 git clone --mirror URL ,然后 git push --mirror URL 到 repo 1。但是,这似乎只将镜像推送到 repo 1 的主分支。我也看到一些人提到可能会做一个 git push branch1 --mirror URL,但为此,我得到了一个致命的: --mirror 不能与 refspecs 结合使用。

在其他一些与此类似的问题中,解决方案不一定是从特定分支进行镜像和镜像。不幸的是,我正面临这个特殊的用例,无法镜像到 master 或从 master 镜像,但需要从特定的分支进行。

你问了两个问题:

  • 镜像 repo1:branch1repo2:master
  • 镜像 repo2:masterrepo1:branch1

您可以使用基本相同的步骤完成这两项操作。


镜像 repo1:branch1repo2:master

  1. 克隆 repo1.

    git clone <git URL for repo1>
    
  2. 输入您的新克隆。

    cd repo1
    
  3. repo2master 分支设置新的遥控器。

    git remote add -t master repo2 <git URL for repo2>
    
  4. 结帐repo1:branch1

    git checkout branch1
    
  5. repo1:branch1 推送到 repo2:master

    git push repo2 +branch1:master
    

镜像 repo2:masterrepo1:branch1

  1. 克隆 repo2.

    git clone <git URL for repo2>
    
  2. 输入您的新克隆。

    cd repo2
    
  3. repo1 设置新的遥控器。

    git remote add -t branch1 <git URL for repo1>
    
  4. 签出 master repo2(可能已经签出)

    git checkout master
    
  5. repo2:master推到repo1:branch1

    git push repo1 +master:branch1
    

在这里你可以为所欲为。真正的诀窍是首先弄清楚你想要什么。在那之后,这只是 refspecs 的问题,我稍后会谈到。不过,鉴于您上面所说的,您想要任何--mirror选项。

获取、推送、命名和提交哈希

首先,请记住 Git 实际上就是 提交 。提交由它们的哈希 ID 唯一标识。这些哈希 ID 是通用的:每个地方的每个 Git 都同意某个特定的提交具有 那个 哈希 ID。任何地方的其他 Git 都不能对不同的提交使用相同的哈希 ID,并且任何地方的每个 Git 都必须使用 that 哈希 ID .

换句话说,哈希ID具有真实、具体的含义。重要的是哈希 ID。它们是 Git-to-Git 交易所的货币。

分支名称,相比之下,特定于一个 Git 存储库或另一个。你的 master 可以与 my master 完全无关,即使我们的两个 Git 存储库要交换提交。我有我的 Git 打电话给你的 Git;你的 Git 告诉我 你的 master 是提交 a123456...;我从你那里得到了那个提交,它仍然是 a123456...,但是我 Git 使用的名字完全是另外一回事,例如 origin/masterdinh/masteryourbranch 或任何 I 想要调用它。如果你说 a123456... 而我说 a123456... 我们可以告诉我们都有 that commit。你的 name,如果有的话,和我的 name,不需要同意,而 git fetch,他们通常 don 't同意。

git push 命令不是很对称,但它的工作原理非常相似:我让我的 Git 调用你的 Git,为你提供一些原始提交哈希 ID(你的 Git 和我的像往常一样同意这些)。然后我要求(常规推送)或命令 (force-push) your Git 设置一个或多个 your 名称识别一些特定的提交,我用它们的通用名称命名,agreed-on 哈希 ID。

当我 运行 git fetch 到您的存储库时,我的 Git 可以看到您的名字。 git fetch 的默认设置是我 Git 复制你的名字, 重命名 它们:这就是为什么你通常会有一个 origin/master Git,在您 Git 从另一个 Git 复制内容后,您正在调用 origin。同时,我让我的 Git 告诉你的 Git 当我 运行 git push 来自 我的存储库时要使用的一些名称。为了方便起见,我通常会在 git push 时告诉你 Git 我的 分支名称。这就是为什么你通常将你的 master 推送到来源的 master:这里没有默认重命名。

哈希 ID 与引用

如果散列 ID 是 Git 查找提交的方式——而且确实如此——那么我们究竟为什么要有分支名称?好吧,考虑实际 Git 存储库中的一些实际哈希 ID:

83232e38648b51abbcbdb56c94632b6906cc85a6
aa8c8d914e4ae709e4fd025f359594f62653d9e5
061ed420ec2dc97e2a922a6f02992869089cefb3

这是三个提交,按照它们的顺序(最新的在前)。你能记住这些数字吗? (也许——但我不想。)Git 需要记住全部,但是设置了 Git,以便提交 83232e... 本身记住数字 aa8c8d...,而 aa8c8d... 记住数字 061ed4...。所以我们只需要记住那个又大又丑的83232e...。我们可以把它写下来;但如果我们 Git 把它写下来 我们可能会更好。

我们可以使用分支名称、标签名称或任何其他此类名称来完成此操作。 name 将保留 83232e...,现在我们只需要记住 name。这些名称统称为 Git 所称的 refsreferences。分支名称是以 refs/heads/ 开头的名称,标签名称以 refs/tags/ 开头,remote-tracking 名称以 refs/remotes/ 开头并以远程名称继续(例如,refs/remotes/origin/).在最后一个斜杠之后,您有分支、标签或 remote-tracking 名称本身。 Git 倾向于隐藏前缀,但它偶尔会出现,尤其是在使用完整引用时。

所以,我们有引用——分支名称、标签名称、remote-tracking 名称等等——记住 一个 哈希 ID。从那个哈希 ID,Git 找到剩余的哈希 ID。 branch names 唯一特别的是我们可以使用 git checkout 得到 "on" branch—git status 会说 on branch master—然后,一旦我们这样做了,我们就可以进行 new 提交。我们所做的新提交将为我们记住 83232e...,并且 Git 会自动将新提交的哈希 ID 填充到 master 中,该哈希 ID 将是新的且唯一的,不同于以往的所有其他提交,它现在会自动记住最新的提交。

因此,分支名称会自动指定分支中的最后 提交。从该提交中,Git 通过其哈希 ID 找到——如在名称下找到的那样——Git 找到 previous 哈希 ID,它获得 Git到提交,它获得另一个先前的哈希 ID,依此类推。结果是历史:在指定的提示处结束的一系列提交,其哈希 ID 存储在分支名称中。

git fetchgit push 交换提交时,它们通过哈希 ID 进行,但它们也会看到,有时还会复制名称。这就是 refspecs 发挥作用的地方。

参考规格

refspec 本质上是一对由冒号 : 字符分隔的引用,例如:

refs/heads/master:refs/remotes/origin/master

您可以在任何一侧放置任何参考。这里我们在左边有 master——refs/heads/ 将其标记为分支名称——在右边有 origin/master 作为 remote-tracking 名称。左边的名称是 source,右边的名称是 destination。这是 git fetch 使用的一种 refspec,因为源引用是分支名称,目标引用是 remote-tracking 名称。这告诉你的 Git: 使用他们的 master 分支,得到他们有但我没有的任何提交,并记住他们的 master 表示的那个,使用我的 origin/master.

git push,你可以写git push origin master:master。您的 Git 会将其转换为 refs/heads/master:refs/heads/master——在适当的位置扩展全名——从而接受您拥有但他们没有的任何提交,将它们发送过来,然后要求他们设置 他们 master 分支到该链中的最后一次提交。或者你可以 git push origin master:bren,告诉你的 Git 接受你的 master——左边的源——并发送你有但他们没有的任何提交,然后让他们设置他们的分支 bren.

您可以——至少在脚本中,可能应该——拼出全名,并在前面加上 refs/heads/。这可以确保如果由于某种原因有人不小心创建了一个名为 master 标签 ,就不会出现歧义:你的意思是 分支 refs/heads/master,不是 标签 refs/tags/master.

任何refspec前面都可以有一个plus-sign +字符。这样做会告诉 Git: 即使您通常会反对,也要执行此操作。 也就是说,它会为这个特定的参考更新设置 --force 标志。例如,如果更改分支名称会 丢失 一些提交,Git 通常会反对。也就是说,如果分支名称当前显示 "commit a123456" 而显示 "from here, go back one step to fedcba9",并且您要求他们将名称设置为 "fedcba9",他们将无法找到 "a123456" any more — 提交中的定向链接仅指向 向后,指向较旧的提交,从不转发至较新的提交。 (有关更多信息,请参阅 Think Like (a) Git。)

Refspec 匹配和 --mirror 选项

您可以,而且 git fetch 通常会这样做,使用特殊的 * 元字符来匹配 所有 分支:

+refs/heads/*:refs/remotes/origin/*

这个 fetch-oriented refspec 说:获取他们所有的分支名称——refs/heads/ 下的所有内容——并获取所有提交等等,然后强行更新我的所有 remote-tracking refs/remotes/origin/ 中的名称进行匹配。 这是 git fetch 用来与名为 origin 的远程通信的正常引用规范。这样,您将获得 所有 他们的分支,作为您的 remote-tracking 名称。

但是,您可以使用 git clone --mirror 将此 refspec 更改 为:

+refs/*:refs/*

这个说 获取他们所有的引用,不管它们是什么类型的名称,并用每个中的哈希 ID 覆盖所有 my 引用他们的。 这意味着每个 git fetch 替换 all 你的分支和标签名称。任何你有但他们没有的承诺,你现在都输了。他们有的任何你没有的提交,你现在都有了——你的克隆现在是他们的镜像。

git push --mirror 命令意味着您的 Git 命令(与 --force 或加号前缀一样)到:

  • 创建您拥有但他们没有的任何名称;
  • 更新您更改过的任何名称;和
  • 删除他们有但你没有的任何名字。

这会彻底清除他们的所有分支、标签和其他名称,并用您的名称替换它们。 (当然,您将首先发送他们完成此操作所需的任何提交和其他对象。)这确实取决于他们服从强制操作,但这是通常的默认设置。

这里要记住的主要事情是,在Git文档中,镜像这个词的意思是从另一个那里拿走一切: 一个 Git 存储库永远不会对任何事物具有权威性,而另一个对所有事物始终具有权威性。这显然不是你想要的!您只希望一个存储库对一个分支具有权威性。

您可以在任一方向执行此操作:您可以 git fetch repoA +refs/heads/theirs:refs/heads/mine 将您的分支 mine 替换为 theirs 上的最新版本,并且您可以 git push repoB +refs/heads/mine:refs/heads/theirs 替换他们的 分支 theirsmine 上的最新消息。除了你 运行 命令的位置和数据流的方向,这些几乎是对称的:唯一真正的区别是 git push,它们 可以 拒绝(通过 pre-receive、更新或 post-receive 挂钩)。