如何同步两个远程 Git 仓库?

30

我有两个代码库的URL,希望将它们同步,使它们都包含相同的内容。在Mercurial中,我想要做的是:

hg pull {repo1}
hg pull {repo2}
hg push -f {repo1}
hg push -f {repo2}

这将导致两个仓库都有两个分支(我知道拥有两个分支并不常见,但我正在进行同步,需要非交互式的方式。其中一个仓库的分支将手动合并,然后再次运行同步)。
我想在Git中做同样的事情。例如,没有用户交互,将所有更改应用于两个仓库,稍后合并多个分支/头等等。
我试图使用命令中的URL而不是添加远程仓库来实现这一点,因为可能涉及多个仓库,为它们全部添加别名只会让我的脚本更加复杂。
我目前正在使用git clone --bar {repo1}克隆仓库,但我无法“更新”它。我尝试了get fetch {repo1},但似乎没有拉取我的更改;git log仍然没有显示在repo1中添加的变更集。
我还尝试在我的pushclone中使用--mirror,但那似乎会删除在本地不存在的repo2的变更集,而我需要保留来自两个仓库的变更:/。
最好的方法是什么?

编辑:为了让我的意图更加清晰...

我有两个代码库(例如 BitBucket 和 GitHub),希望人们能够向其中任何一个推送(最终,一个将是 Git,一个将是 Mercurial,但现在假设它们都是 Git 以简化问题)。我需要能够运行一个脚本,以“同步”这两个代码库,使它们都包含两组更改,并且可能需要手动合并。

最终,这意味着我只需与其中一个代码库(例如 Mercurial)交互,我的脚本将定期拉取 Git 更改,我可以进行合并,然后再次推送。

在 Mercurial 中,这非常容易!我只需从两个代码库中拉取,然后使用 -f/--force 推送以允许推送多个分支。然后,任何人都可以克隆其中一个代码库,合并分支,然后再次推送。我想知道如何在 Git 中实现最接近的类似事情。它必须是 100% 非交互式的,并且必须保持两个代码库处于可以无限重复该过程的状态(这意味着不要重写历史记录/更改 changesets 等)。

6个回答

30
Git分支没有Mercurial中的“heads”。只有一件事叫做HEAD,它实际上是指向您当前检出的提交的符号链接。对于像GitHub这样的托管存储库,在这种情况下没有检出任何提交 - 只有存储库历史记录本身(称为“裸”存储库)。
这种差异的原因是Git分支名称完全是任意的;它们不必在存储库的副本之间匹配,并且可以随意创建和销毁它们。[1] Git分支就像Python变量名,可以随意移动并附加到任何值; Mercurial分支就像C变量,其引用固定的预分配内存位置,然后填充数据。
因此,当您在Mercurial中拉取时,同一分支有两个历史记录,因为分支名称在两个存储库中都是一个固定的有意义的东西。每个历史记录的末端是一个“head”,通常会将它们合并以创建单个“head”。
但在Git中,获取远程分支实际上并不会影响你的分支。如果你从origin获取master分支,它只会进入一个称为origin/master的分支。git pull origin master只是两个步骤的简单操作:将远程分支获取到origin/master,然后将该分支合并到当前分支中。但它们不必具有相同的名称,你的分支可以被称为development或trunk或其他任何名称。你可以将任何其他分支拉取或合并到它,也可以将其推送到任何其他分支。Git不关心这些。
这就带我回到你的问题:你不能将“第二”分支头推送到远程Git存储库,因为这个概念不存在。你可以推送到名称混乱的分支(bitbucket_master?),但据我所知,你无法远程更新远程的remotes。
I don't think your plan makes a lot of sense, though, since with unmerged branches exposed to both repositories, you'd either have to merge them both, or you'd merge one and then mirror it on top of the other... in which case you left the second repository in a useless state for no reason.
Is there a reason you can't just do this:
1. 选择一个仓库作为主仓库,我假设是BitBucket。克隆它,它成为“origin”。 2. 添加另一个仓库作为名为“github”的远程仓库。 3. 编写一个简单的脚本定期获取两个远程仓库并尝试将“github”分支合并到“origin”分支中。如果合并失败,则终止并发送电子邮件或其他通知。如果合并是微不足道的,则将结果推送到两个远程仓库。
当然,如果您只在功能分支上进行所有工作,这一切都变得不那么困难了。 :)
[1] 更妙的是:您可以合并来自不同存储库的分支,它们根本没有任何历史记录。我曾经用这种方法来合并分别启动的项目;它们使用不同的目录结构,所以这个方法很有效。GitHub 在其 Pages 功能中使用了类似的技巧:您的 Pages 的历史记录存储在一个名为 gh-pages 的分支中,该分支位于同一存储库中,但与您的项目的其余部分没有任何历史记录。
[2] 这是个小谎言。分支仍然被称为 master,但它属于称为 origin 的远程分支,并且斜杠是引用它的语法。这种区别很重要,因为 Git 对分支名称中的斜杠毫不犹豫,所以您可能会有一个名为 origin/master 的本地分支,它会遮盖远程分支。

我想让这两个仓库保持同步,以便它们可以在任一端合并。正常的同步过程将处理将此推送到另一个仓库。想象一下通过BitBucket和GitHub都允许贡献。我只想要一些自动化的过程来在它们之间复制更改,并且它们可以在任一端手动合并。 - Danny Tuppeny
好的,我喝了一些咖啡后重新阅读了您的帖子,我想我明白了。看起来为了做我需要的事情,我需要在我的合并仓库中有两个分支(每个分支都是主分支)。 - Danny Tuppeny
如果我在BB中有master和master_github,在GH中有master和master_bitbucket,我能否以一种方式合并它们,以便我可以将其推送到两个存储库,并且只有一个主库,或者不同的名称会搞砸这个过程? - Danny Tuppeny
为什么它们需要在“任一端”可合并?Git克隆存在是为了让其他贡献者可以使用Git,对吧?我怀疑他们不希望该过程的第一步是“合并其他人的任意代码”。 - Eevee
抱歉,我的评论与Hg无关,我是指如果我两边都有Git。不管怎样,我现在已经成功地让所有东西按照我需要的方式工作了。我添加了github作为远程,并执行了fetch --all,然后将github/master推送到origin/github,在那里进行合并,然后再次执行fetch并将其推回github/master,一切似乎都很顺利 :-) - Danny Tuppeny
显示剩余4条评论

11

我使用类似的代码,在两个存储库中使用webhook触发器同步GitLab和Bitbucket的主分支:

git pull origin master
git pull gitlab master
git push origin master
git push gitlab master

也许这并不是你问题所需要的,但它可能有助于其他只需要同步一个分支的人。


我正在将一个小项目在Github和Launchpad之间同步,并将尝试您的命令。 - Niklas Rosencrantz

9
以下是针对该问题的经过验证的解决方案: http://www.tikalk.com/devops/sync-remote-repositories/ 需要运行的命令:
#!/bin/bash

# REPO_NAME=<repo>.git
# ORIGIN_URL=git@<host>:<project>/$REPO_NAME
# REPO1_URL=git@<host>:<project>/$REPO_NAME

rm -rf $REPO_NAME
git clone --bare $ORIGIN_URL
cd $REPO_NAME
git remote add --mirror=fetch repo1 $REPO1_URL
git fetch origin --tags ; git fetch repo1 --tags
git push origin --all ; git push origin --tags
git push repo1 --all ; git push repo1 --tags

3
脚本首次运行时正常,但在执行上述脚本后,每当ORIGIN发生更改时,REPO1都不会更新。 请提供任何方法以确保每次更改时REPO1与ORIGIN保持同步。 谢谢。 - rameshthoomu

3

git-repo-sync
它可以精确同步一对远程 Git 存储库,旨在为双方远程开发工作提供支持。
就像您有两个入口点到单个存储库一样,您的两个远程 Git 存储库将几乎像一个单一的存储库一样运行。

我是git-repo-sync的作者,在开发过程中我的主要想法是“安装、定期自动运行并忘记这个工具的存在”。
实际上,它的功能表现得非常出色。

git-repo-sync具有自动冲突解决策略、不同的灾难保护和许多其他功能。您最好查看该项目的README

唯一的问题是,它不会同步 Git 标签,但这是有意的。

很抱歉,我无法帮助解决此 SO 问题的 Mercurial 方面。但是,我的工具可能对那些仅寻求解决 Git 远程问题的人有所帮助。


1
请不要到处寻找可以发布有关您的工具的帖子。相反,看看这些帖子是否可以被关闭为重复帖子。 - Martijn Pieters
1
我们选择了这个答案,因为它是最完整的,而其他帖子大多数都是重复的。我可以理解你对自己的项目感到自豪,但这并不意味着它需要重新发布。相反,考虑是否可以将问题标记为重复。 - Martijn Pieters

2
您可能没有注意到,当您使用git clone --mirror --bare时,fetch实际上是有效的,因为默认情况下git不会列出其远程分支。您可以使用git branch -a列出它们。
我还没有为未命名的远程配置语法,但您可以根据URL自动添加远程配置...无论如何,最好选择每个存储库的唯一和一致的名称,以便您可以知道来自哪里的更改。
但是,您可以尝试类似于以下内容的操作:
git clone --bare --mirror --origin thing1 {repo1} repo.git
cd repo.git
git fetch thing2 --mirror
git push thing1 --mirror
git push thing2 --mirror

完成这些步骤后,thing1 将拥有 thing2 所有的分支,并作为远程分支随时可用。您可以使用 git branch -a 命令列出远程分支。
在 Github 或 Bitbucket 上,您无法通过 Web 界面看到这些远程分支,但是如果使用 --mirror 选项进行克隆,则可以看到它们存在。

我正在尝试将多个仓库合并在一起,并允许它们稍后合并,因此在Mercurial中,我只需同时拉取这两个仓库,然后使用--force/-f进行推送(默认情况下,push不会推送多个头)。 - Danny Tuppeny
在Git中,这是通过拥有两个分支来完成的,其中一个分支名为“/remote/{repo}/branchname”,除了您的本地分支。 - derekv
我并没有真正的“远程”或“本地”仓库。我只是使用一个临时仓库来合并这两个远程仓库,然后想将结果推回到每个仓库。它们应该最终变得相同。 - Danny Tuppeny
我不知道你的编辑意味着什么。the-other-master是什么? - Danny Tuppeny
让我们在聊天室继续这个讨论:http://chat.stackoverflow.com/rooms/25053/discussion-between-derekv-and-danny-tuppeny - derekv
显示剩余7条评论

1
尝试在git fetch之后使用git reset --hard HEAD。但是,我不确定我确切地理解你的目标是什么。在运行fetch、reset和push命令之前,您需要cd到单独的存储库目录中。

1
基本上,我有两个 Git 存储库(例如 GitHub 和 BitBucket),并且可能在两端进行更改。我想将更改推送到彼此,以使存储库相同。如果确实存在双方的更改,则应该能够克隆其中一个存储库,合并,然后推回。 - Danny Tuppeny
我对Git了解不多,但我相当确定reset --hard不是我想要的 - 听起来可能会丢失一些东西。这绝对不是我想要的 :/ - Danny Tuppeny
“git merge” 是合并的唯一选项,但不能保证不会出现冲突。为什么你要把同一个代码库放在 Github 和 BitBucket 上?这感觉不是个好主意。 - adamdunson
一个是Git,一个是Mercurial;我需要将它们合并 :) 但这是一个我可以处理的复杂问题;如果两个仓库都是Mercurial,我可以按照我在问题中发布的方式来自动、非交互式地完成此操作,如果人们同时向两侧推送更改,我们只会有多个分支,直到有人合并,然后一切都会好起来。我想找出如何在Git中做类似的事情。例如,合并两个仓库并允许以后进行合并。 - Danny Tuppeny
我也在问题中添加了一些更多的信息;希望这样更清晰明了。 - Danny Tuppeny

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