保持两个git仓库同步

5
我面临以下问题,目前已经无法想到解决方案:
我们公司不允许开发人员直接访问互联网。因此,我们非常需要自己的git存储库。目前为止都还好。我们的开发人员正在开发的项目得到了外部公司的支持,该公司也在为我们开发。他们有自己的git存储库。他们不能直接访问我们的git存储库,我们也不能直接访问他们的存储库。只能通过一个能够访问他们存储库的隔离服务器进行访问。
更好地理解:
我的公司存储库 = A,外部公司存储库 = B
这两个存储库都需要保持同步。两者都具有相同的分支,并且在A中进行的更改应该传递到B,反之亦然。两家公司同时在所有分支上工作。我告诉他们要保持分离的分支来工作,但他们没有听。无论如何...
迄今为止,我的解决方案是我在这里找到的一段脚本:
$ORIGIN_URL=EXTERNAL REPO B
$REPO1_URL=INTERNAL REPO A

/usr/bin/git clone -c http.sslVerify=false --bare $ORIGIN_URL
/usr/bin/git remote add --mirror=fetch repo1 $REPO1_URL
/usr/bin/git -c http.sslVerify=false fetch --all
/usr/bin/git fetch repo1 --tags
/usr/bin/git push origin --all
/usr/bin/git push origin --tags
/usr/bin/git push repo1 --all
/usr/bin/git push repo1 --tags

问题在于,由于两个公司都在同一个分支上工作(即:A/fix1和B/fix1),我经常会遇到冲突(更新被拒绝,因为推送的分支头落后于其远程(非快进))。
我正在尝试找到一些脚本,可以为我和两个公司解决这个问题。
我甚至会感激一些关于如何解决我一次又一次面对的这个冲突的建议。
谢谢您的帮助。
敬礼
L.

1
任何存储库在没有合并新的上游更改之前都不应该进行推送。 - pishpish
2个回答

7
听起来你认为他们的分支名称相同,所以他们的分支与你的分支是“同一个分支”。其实并不一定如此。有一种看法是,git永远不会把两个仓库中的分支视为“同一个分支”,它只有在集成仓库之间的更改时才有规则。根据你配置这些规则的方式,你可能会认为它们是“同一个分支”。
因此,首先要配置规则。实际上,git的默认行为在这里并不太糟糕;但设置--mirror=fetchrepo1远程上覆盖了默认行为,可能没有帮助。如果我们不这样做,事情就会简单一些。我们还可以通过手动添加两个远程,而不是克隆一个仓库,使事情更简单一些。 (这不是必需的;我只是觉得更清晰些。)
git init --bare
git remote add external $ORIGIN_URL
git remtoe add internal $REPO1_URL
git fetch --all

假设每个资源库都有一个branch1和一个branch2,并且两者都分叉了,那么您的新资源库如下所示:
       E <--(remotes/external/branch2)
      /
o -- x -- D <--(remotes/internal/branch2)
      \
       x -- A -- B <--(remotes/internal/branch1)
        \
         C <--(remotes/external/branch1)

从这里开始,您可以通过为分支命名空间来避免名称冲突的情况下将外部分支共享到内部代码库中。

git push internal refs/remotes/external/*:refs/heads/external/*

现在你的内部代码库看起来像这样:
       E <--(external/branch2)
      /
o -- x -- D <--(branch2)
      \
       x -- A -- B <--(branch1)
        \
         C <--(external/branch1)

当然,外部更改与内部更改并没有整合在一起,但这与按照您最初的建议使用不同的分支名称是相同的。这是预期的——在某个时候,必须将外部更改合并到内部分支(或反之亦然),这就是需要解决冲突的时候。
当然,您可以使用某些实践方法使合并冲突的解决尽可能轻松,比如倾向于使用短期分支和频繁的增量集成。但您无法完全消除它们。
您也可以以未整合的形式类似地共享内部更改到外部仓库;例如通过执行以下操作:
git push external refs/remotes/internal/*:refs/heads/internal/*

但是这样会产生一些问题,比如谁来整合更改以及如何整合,特别是因为外部公司似乎没有按照要求做这样的事情。所以你可能需要在内部整合他们的更改,然后使用他们已知的分支名称共享整合后的更改。 其中的技巧是,你必须使用“拉取、整合、推送”模型来避免像你已经看到的“非快进”错误。当你的工作副本能够直接与远程通信时,通常可以这样做。
git pull
# resolve conflicts
git push

因为您需要使用此桥接存储库,但可能不想在该存储库中执行所有集成工作,因此您需要额外的步骤。这可能会很烦人,因为完成获取/集成/推送周期所需的时间越长,在您获取但尚未推送时,新更改出现的机会就越大,这将要求您执行另一个获取/集成/推送周期。当然,推送是按引用逐个接受或拒绝的,因此随着时间的推移,它应该可以解决问题(例如,尝试1成功地推送了分支A,尝试2成功地推送了分支B和C等)。
因此,集成工作流可能如下所示:
在桥接存储库上:
fetch --all
git push external refs/origins/internal/*:refs/heads/*

这尝试直接更新它们的分支。一些引用可能会被拒绝,没关系,你希望在下一个周期中得到它们。

git push internal refs/origins/external/*:refs/heads/external/*

这个操作应该总是成功的。为了确保它总是成功,你应该确保从不对 external/* 分支做内部提交。因此,你可能想使用一个非分支引用(即将外部引用保留在 refs/heads 分层结构之外),但不是完全清楚应该将它们放在哪里。你可以继续像远程跟踪引用一样处理它们。

git push internal refs/origins/external/*:refs/origins/external/*

这有点不太正常,因为内部仓库实际上没有名为external的远程...

无论如何,您的开发人员现在可以查看更改并将其集成到分支的本地版本中,解决冲突。然后,在下一次集成周期中,当您进行fetch时,您将获得合并提交,您可以尝试将其推送到远程。根据需要重复此过程。

当然,这是建立在“他们似乎不按要求执行”的基础上,以协调内部和外部更改。您让每个人都使用相同的仓库,就会避免许多麻烦。(例如,在这种情况下,必须在内部完成所有集成,并可能延迟对内部更改的外部可见性。)

从这个意义上说,我喜欢将内部引用推送到外部仓库,并将外部引用推送到内部仓库,以便两家公司的开发人员都可以看到两组更改。但是,您不希望外部开发人员提交到内部分支或反之亦然,因为这样集成就开始变得奇怪了,出现像rsfs/heads/internal/external/master之类的分支名称,这很傻。


2
为使这一切工作起来,您(公司A)和他们(公司B)需要有一个共同的协调点。这个Git库克隆不必成为“主库”或“全部真相”的源头。也就是说,你们两家公司,我们暂且假设它们不是由许多个体和/或个人克隆组成的,可以以各种不同的方式对待它,这取决于你们两个。但你们需要它作为协调站点。你可以将它托管在任何地方,只要你们两个都能够读取它,至少有一个人可以修改它,如果只有一个人可以修改它,那么这个人——再次是A或B——必须至少具有另一个发布的存储库的“读取”访问权限。
(如果被分享的克隆被认为是“主库”或“全部真相”的源头,事情就会简单得多,因为当人类有多个不同的观点时,他们通常很难确定现实情况。)
为了简单起见,我假设A和B中的某个人拥有共享仓库SR的写(推送)权限。我们称这个共享仓库为SR。其余部分只是一种方法;请参见Mark Adelsberger's answer以获取另一种方法。
为了保持组织结构,共享仓库SR中的分支名称可以很简单地加上前缀:SR不需要像masterdevelop等那样命名分支,而是可以将分支命名为A/masterB/masterA/developB/develop等。公司A的代表——无论是通过人工操作git push还是通过从SR到A内部某个暴露点的提取来进行的机器驱动更新——将A的master提交到SR的A/master等分支上。这在Git中非常容易实现,因为Git在提取方向上具有分支重命名的概念。
如果您使用push更新它们,请考虑安装一个pre-receive或update hook来验证被认证的push源是否被允许更新相关名称。也就是说,您会为A和B的代表提供不同的登录名,并检查谁在进行push:是A用户还是B用户?如果是A用户,则所有分支名称都必须以refs/heads/A/开头,这将避免意外覆盖。
如果A和B都要使用标签,则您都需要使用一些相当严格的自律来确保不会互相覆盖对方的标签。最好完全禁止SR内的标签,从A或B中都不要推送它们。这是因为虽然Git很高兴重命名分支名称,但任何各种--tags fetch或push操作却不会重命名标签名称,因此如果A中的某个人称之为v1.2,而B中的某个人称之为其他内容的v1.2,您将得到一个标签名称冲突。使用--no-tags可以避免这个问题,但代价是SR上永远没有任何标签。
在这个特定的设置中,这使得每个公司都可以拥有共享存储库SR的内部镜像。无论您是在A还是B工作,内部镜像都告诉您他们看到了什么:如果您在A,则检查B/master或B/develop以查看他们的最新情况。这个内部镜像只是复制SR中的任何内容。但它让您访问共享数据,即使您没有直接访问共享存储库SR的权限。
将某物从A发送到B,A处的一个工人提出提交请求,然后拥有适当权限的A处人员将这些提交集成到某个内部存储库中——可能是充当镜像的那个存储库,或者另一个存储库。Git使用方式鼓励这种存储库复制,实际上这样做非常有效。现在拥有权限的A处人员将提交推送到SR。如果它们到达了那里,A处的人员也会更新可访问的镜像,以便A处所有程序员都可以看到这些提交对B处程序员可用。此时,A/分支与B/分支在SR上不同。现在轮到B处的人员将其集成到他们的存储库中。一旦他们这样做了,他们将经历同样的过程(见下文),SR将再次匹配A/分支和B/分支。
当B处的程序员进行某些更新时,如果A处的程序员喜欢这些更新,他们可以将新的提交合并到自己的存储库中,然后通过相同的授权人技术将它们作为更新发送。现在,B/分支与A/分支不再落后,两者已经同步。

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