Git SVN门卫仓库

15
我已经使用git相当长时间了,主要是使用git-svn。现在我想说服我的同事们从svn转移到git。但不幸的是前提条件是svn仓库还要存在一段时间。所以我寻找解决方案,找到了Jon Loeliger的《使用Git进行版本控制》一书。我买了这本书,它真的很好,但我不太理解如何设置git svn门卫库的指南。
在第16章中,他描述了这样一种情况:有一个Subversion仓库,至少有几个用户想要使用Git。他建议建立一个单一的“门卫”git仓库,是与Subversion之间唯一的接口。在使用git svn clone从Subversion仓库克隆(带有--prefix=svn/选项)之后,所有分支都会被推送到一个裸仓库(git push ../svn-bare.git 'refs/remotes/svn/:refs/heads/svn/'),然后告诉其他git用户克隆此仓库,该仓库现在包含所有svn远程的本地分支。
这部分工作正常,我认为我完全理解了它。但是我不理解下一部分:
如果克隆裸仓库的开发人员将更改推回到裸仓库,然后我在裸仓库中dcommit到svn,那么由于git-svn创建的替换提交,用户推送的提交将永久丢失,这是有充分理由的。或者我错了吗?这是怎么工作的?
书中说:
然后,在门卫仓库中合并回Subversion,你要做的是:
git checkout svn/trunk (或其他分支 - 这是检出一个分离的HEAD,因为svn/trunk是一个远程分支) git merge --no-ff new-feature git svn dcommit
我如何在裸仓库中checkout一个分支?我不认为这可行。
这将导致一个分离的头上的合并提交,然后修改的内容将保存到新的提交中,其中包括从Git存储库复制的子树,该存储库是从Subversion存储库转换而来的。最后,使用git svn dcommit将提交上传到Subversion存储库。在添加了git-svn-id行后,commit被放置在真正的svn / trunk分支上。

"真正的svn / trunk"是指什么?

在分离的头部上进行的提交“比多余还糟糕。对它进行其他任何操作最终都会导致冲突。所以,请忘记这个提交。如果您一开始没有将其放在分支上,那么忘记它就更容易了”(Jon Loeliger)。

我有点困惑。有人可以更好地解释如何创建git svn门卫库吗?我已经搜索了网站,但没有找到适合我的内容。

当与同事协作时,我很厌倦浪费太多时间进行svn分支和合并。


所以你现在有开发人员<-->看门人git仓库<-->svn的情况?如果是这样,我不明白它的优势在哪里。比起使用git-svn而言,这个方式更好吗?除非抛弃svn,否则似乎不能真正利用git的分支/合并功能。 - dgnorton
@dgnorton 的一个优点是你不需要使用 git-svn,因为并非所有的 IDE 都支持它。EGit(Eclipse 插件)显然不支持,并且也没有即将支持的计划。 - Robert Munteanu
@Robert:我建议在下面解释一下“SVN门卫裸仓库”的概念,或许可以为这个过程带来一些启示。 - VonC
可能http://stackoverflow.com/questions/29050377/unexpected-merge-error-in-a-git-svn-system也描述了这个概念的自动化测试案例... - sdaau
3个回答

6
我如何在裸仓库中检出一个分支?我认为这行不通。 是的,可以:您只需在本地克隆bare repo,同时创建一个非裸仓库,在此过程中,您可以在本地检出/创建(再次本地)尽可能多的分支。 需要一个裸仓库作为上游仓库来推送到。(参见“git push only for bare repositories?”) 但是,为了将任何内容推送到其中,即其他开发人员将他们在非svn分支中的更改推回到gatekeeper repo,其他开发人员必须首先克隆该裸仓库,对本地副本进行所有相关修改,并推回到裸仓库。 此外,您可以设置一些钩子来验证您的推送:请参见“Hooks for git-svn”。
然后进行 dcommit 操作时,门卫也会克隆该门卫仓库,从中进行以下操作:
  • 检出与 svn 相关的远程分支(“svn” 是远程仓库的名称),例如“svn/trunk
  • 将相关更改合并到该未命名分支中
  • git-svn dcommit
因此,总结一下:
  • 开发人员克隆该门卫仓库以记录其更改,并在自己的分支上推送更改(由于门卫仓库是裸仓库,因此可以推回)
  • 负责将更改推回实际 SVN 仓库的人员也会克隆该门卫仓库,以便选择需要合并到专门与 SVN 相关联的 Git 分支的内容(例如参见“克服 git svn 的注意事项”)。

所以基本的工作流程似乎涉及到“gitted”开发人员提交到共享的git存储库1(裸)中,然后该(仅git)存储库被传播到另一个git存储库(非裸),该存储库使用git-svn进行克隆。此时,非裸存储库与SVN存储库同步。请告诉我是否理解正确。 - Robert Munteanu
@Robert:我认为只能涉及一个裸仓库,但实际上你的解释更适合创建一个更安全的“门卫”设置:即开发人员不是克隆一个通用的裸仓库(通用于Git和SVN dcommit),而是克隆一个仅限于Git的仓库,门卫可以克隆他/她自己的Git仓库,在那里他/她:a/首先从第一个裸仓库中拉取以获取开发人员引入的所有更改,b/检出SVN远程分支并合并后再进行dcommit。 - VonC

3

我曾尝试自动化Jon Loeliger所描述的门卫设置,并使其正常工作。他非常详细地介绍了要执行的步骤,但“合并回到Subversion”部分相对较短。我尝试过使用git-svn进行不同的设置,还遵循了Thomas Ferris Nicolaisen提供的出色演示/示例,并使用他的示例项目(经过修改)来测试“门卫设置”。
@echo 1. Clone Subversion repo
cd %WDIR%\devs\adm
call git svn clone -s --prefix=svn/ http://localhost/svn-repos/company-repo/websites --    username adm
cd %WDIR%\devs\adm\websites
call git reset --hard svn/trunk

@echo ----------------------------------
@echo  2. Create bare repo
cd %WDIR%\devs\adm
mkdir websites.git
cd websites.git
call git init --bare 

@echo ----------------------------------
@echo  3. Populate bare with content from gatekeeper 
cd %WDIR%\devs\adm\websites
call git push --all ../websites.git
call git push ../websites.git "refs/remotes/svn/*:refs/heads/svn/*"

@echo ----------------------------------
@echo  4. Setup bare as a remote in gatekeeper and fetch branches 
call git remote add bare_repo ../websites.git
call git fetch bare_repo

Jon Loeliger没有描述第四步,但我猜这就是他的意思。

当需要合并回到Subversion时,请执行以下操作:

C:\tmp\devs\adm\websites>git fetch bare_repo
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From ../websites
   e53fba9..2ac281c  svn/trunk  -> bare_repo/svn/trunk

现在我们可以按照书中的步骤进行:
C:\tmp\devs\adm\websites>git checkout svn/trunk
Note: checking out 'svn/trunk'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at e53fba9... [maven-release-plugin] prepare release kaksi

C:\tmp\devs\adm\websites>git merge --no-ff remotes/bare_repo/svn/trunk
Merge made by the 'recursive' strategy.
 0 files changed
 create mode 100644 web/howto.txt
 create mode 100644 web/readme.txt

C:\tmp\devs\adm\websites>git svn dcommit
Committing to http://localhost/svn-repos/company-repo/websites/trunk ...
        A       web/howto.txt
        A       web/readme.txt
Committed r8
        A       web/readme.txt
        A       web/howto.txt
r8 = 28da267255ae56022bd4ed3c0f4886da1ac04944 (refs/remotes/svn/trunk)
No changes between current HEAD and refs/remotes/svn/trunk
Resetting to the latest refs/remotes/svn/trunk

我的问题是这种设置(我们在书中早就被警告过了)历史记录被压缩了。
C:\tmp\devs\adm\svn\websites>svn log
------------------------------------------------------------------------
r8 | adm | 2012-05-12 23:21:11 +0200 (lø, 12 mai 2012) | 1 line

Merge remote-tracking branch 'remotes/bare_repo/svn/trunk' into HEAD
------------------------------------------------------------------------

现在考虑一下将其合并回Subversion的另一种替代方案:
C:\tmp\devs\adm\websites>git checkout -t svn/trunk
Branch trunk set up to track local ref refs/remotes/svn/trunk.
Switched to a new branch 'trunk'

C:\tmp\devs\adm\websites>git fetch bare_repo
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From ../websites
   c188a72..b1b4237  svn/trunk  -> bare_repo/svn/trunk

C:\tmp\devs\adm\websites>git rebase remotes/bare_repo/svn/trunk
First, rewinding head to replay your work on top of it...
Fast-forwarded trunk to remotes/bare_repo/svn/trunk.

C:\tmp\devs\adm\websites>git svn reset 2147483647
r7 = c188a72da6df2966e563e9e575b626d5b449400f (refs/remotes/svn/trunk)

C:\tmp\devs\adm\websites>git svn rebase
Current branch trunk is up to date.

C:\tmp\devs\adm\websites>git svn dcommit
Committing to http://localhost/svn-repos/company-repo/websites/trunk ...
        A       web/howto.txt
        A       web/readme.txt
Committed r8
        A       web/readme.txt
        A       web/howto.txt
r8 = 18b7c7b4693cc8e55098bd716c9259ed5570acf0 (refs/remotes/svn/trunk)
No changes between current HEAD and refs/remotes/svn/trunk
Resetting to the latest refs/remotes/svn/trunk

现在提交历史记录完整:
C:\tmp\devs\adm\svn\websites>svn log
------------------------------------------------------------------------
r8 | adm | 2012-05-12 23:51:48 +0200 (lø, 12 mai 2012) | 1 line

'ola added [readme.txt, howto.txt] on svn/trunk'

为了使此设置正常工作,我们需要使用“git svn reset”命令,否则dcommit第二次将失败,因为git-svn对当前修订版本感到困惑,并且落后于(与创建裸库时相同的修订版本)。这可能是因为我们使用了rebase,而rebase又是在子版本中获取良好线性历史记录所必需的。
大问题是:“git svn reset”真正做了什么?在这种情况下,“前向重置”是否是“git svn reset”的合法用途?

2

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