“git svn clone”失败(需要完整历史记录)的解决方法

31

我想把Subversion代码库中的一个子目录(这里用module表示)转换为带有完整历史记录的git代码库。在我的Subversion代码库历史记录中,有许多svn copy操作(Subversion用户称之为分支)。发布策略是,在每次发布或其他创建分支后,旧URL将不再使用,新URL将替换旧URL以包含工作内容。

通过我的阅读,最理想的情况似乎是这样做:

$ git svn clone --username=mysvnusername --authors-file=authors.txt \
    --follow-parent \
    http://svnserver/svn/src/branches/x/y/apps/module module

(其中branches/x/y/代表最新的分支)。但是我遇到了一个错误,看起来像这样:

W: Ignoring error from SVN, path probably does not exist: (160013): Filesystem has no item: '/svn/src/!svn/bc/100/branches/x/y/apps/module' path not found
W: Do not be alarmed at the above message git-svn is just searching aggressively for old history.

(更新: 添加选项--no-minimize-url并不能消除错误信息。)

目录module被创建和填充,但最新的svn copy提交以后的Subversion历史记录没有被导入(创建的git仓库最终只有两个提交,而我预期的是数百个提交)。

问题是,如何在这种情况下导出完整的Subversion历史记录?

可能的原因

  1. 搜索错误信息时,我发现了这个帖子:git-svn anonymous checkout fails with -s,其中链接到了这个Subversion问题:http://subversion.tigris.org/issues/show_bug.cgi?id=3242

    根据我的阅读,Subversion 1.5中的某些内容改变了客户端访问存储库的方式。使用更新版本的Subversion,如果URL路径的某个上一级目录没有读取访问权限(对我来说是真实的,svn ls http://svnserver/svn失败并显示403 Forbidden),则会在某些Subversion操作中失败。

  2. Jeff Fairley在他的回答中指出,Subversion URL中的空格也可能导致此错误消息(由用户Owen确认)。查看他的解决方案,以了解如何解决因相同原因而导致git svn clone失败的情况。

  3. Dejay Clayton在他的回答中揭示,如果分支和标记svn url中最深层子目录组件的名称相同(例如.../tags/release/1.0.0.../branches/release-candidates/1.0.0),则可能会出现此错误。


1
我不能对功能进行评论,但我可以就警告发表意见。您可以忽略“忽略错误”的部分。它与您链接的subversion.tigris.org问题无关。如果有什么问题,根据阅读git-svn源代码,它应该为tigris案例抛出一个补充错误。 - Christopher
1
我怀疑你关于读取权限问题的说法是正确的;我只是想表达这个错误信息可能是错误的(至少,如果它是针对你的用例抛出的,那么它应该更清楚地说明问题的原因)。你可以尝试在 Git 列表中寻求帮助。 - Christopher
1
我认为如果你的更新是一个解决方案,你应该将其作为单独的答案发布,这样这个问题就可以被标记为已解决。 - Alex M
@Alaksey - 我认为你是对的。由于没有更好的解决方法,我将我的不愉快的解决方案从问题中移动到一个独立的答案,并接受为解决方案(同时等待更好的解决方法)。 - FooF
请看下面我的答案,如果您在标签或分支中有同名的子目录,这个问题也可能会出现。 - Dejay Clayton
显示剩余4条评论
5个回答

10

我在分支或标签中有相同名称的子目录时遇到了这个问题。

例如,我有标签candidates/1.0.0和releases/1.0.0,这导致出现文档中的错误,因为子目录1.0.0同时出现在candidates和releases中。

根据git-svn文档

当使用多个--branches或--tags时,git svn不会自动处理名称冲突(例如,如果来自不同路径的两个分支具有相同的名称,或者分支和标记具有相同的名称)。在这些情况下,请使用init设置您的Git存储库,然后在第一次提取之前编辑$GIT_DIR/config文件,以便将分支和标记与不同的名称空间关联起来。

因此,以下命令由于具有类似名称的candidatesreleases标签而失败:

git svn clone --authors-file=../authors.txt --no-metadata \
    --trunk=/trunk --branches=/branches --tags=/candidates \
    --tags=/releases --tags=/tags -r 100:HEAD \
    --prefix=origin/ \
    svn://example.com:3692/my-repos/path/to/project/
以下命令序列可正常运作:
git svn init --no-metadata \
    --trunk=/trunk --branches=/branches --tags=/tags \
    --prefix=origin/ \
    'svn://example.com:3692/my-repos/path/to/project/'

git config --add svn-remote.svn.tags \
    'path/to/project/candidates/*:refs/remotes/origin/tags/Candidates/*'

git config --add svn-remote.svn.tags \
    'path/to/project/releases/*:refs/remotes/origin/tags/Releases/*'

git svn fetch --authors-file=../authors.txt -r100:HEAD

请注意,这样做只是因为在branchestags中没有其他冲突。如果有,我就需要类似地解决它们。

成功克隆SVN仓库后,我执行了以下步骤:将SVN标签转换为GIT标签;将trunk转换为master;将其他引用转换为分支;并重新定位远程路径:

# Make tags into true tags
cp -Rf .git/refs/remotes/origin/tags/* .git/refs/tags/
rm -Rf .git/refs/remotes/origin/tags

# Make other references into branches
cp -Rf .git/refs/remotes/origin/* .git/refs/heads/
rm -Rf .git/refs/remotes/origin
cp -Rf .git/refs/remotes/* .git/refs/heads/ # May be missing; that's okay
rm -Rf .git/refs/remotes

# Change 'trunk' to 'master'
git checkout trunk
git branch -d master
git branch -m trunk master

8

虽然不是完整的答案,但也许你缺失的部分就是下面这段代码(我也对迁移很感兴趣,所以我找到了这个谜题的一部分)。

当你查看git-svn文档时,你会发现以下选项:

--no-minimize-url 

当跟踪多个目录(使用--stdlayout、--branches或--tags选项)时,git svn将尝试连接到Subversion仓库的根目录(或最高允许级别)。这种默认设置允许更好地跟踪历史记录,如果整个项目在仓库内移动,但可能会在存在读取访问限制的仓库上引起问题。传递--no-minimize-url将允许git svn接受URL,而不尝试连接到更高级别的目录。当只跟踪一个URL/分支时,默认情况下关闭此选项(它没有什么好处)。
这适用于您的情况,以便git svn不会尝试读取目录树的更高级别(这将被阻止)。
至少您可以试一试...

感谢您的建议。我刚试图将这个选项添加到 git svn clone --follow-parent 命令中,但它似乎并没有显著的效果(看起来不适用于那个场景 - 由于存储库的结构方式,我无法使用 --stdlayout, --branches, 或 --tags 选项)。 - FooF

3

我最近将一长串SVN仓库迁移到Git中,但在最后遇到了这个问题。我们的SVN结构相当混乱,所以我不得不经常使用 --no-minimize-url。通常,我会运行如下命令:

$ git svn clone http://[url]/svn/[repo]/[path-to-code] \
            -s --no-minimize-url \
            -A authors.txt

我最近运行的几个迁移任务中,URL 中有一个空格。我不知道是空格还是其他原因,但我看到了和你一样的错误。如果不必修改配置文件,我不想涉及它,幸运的是我最终找到了解决方法。我最终选择跳过 -s --no-minimize-url 选项,而是显式地声明路径。
$ git svn clone http://[url]/svn/[repo]/ \
            --trunk="/[path-to-code]/trunk" \
            --branches="/[path-to-code]/branches" \
            --tags="/[path-to-code]/tags" \
            -A authors.txt \
            --follow-parent
  • 请注意,我从您的示例中添加了--follow-parent,但我也不确定它是否有任何区别。
  • 请记住,这些仓库中有空格,因此在trunk/branches/tags路径周围加上了""

你是否曾经遇到过类似于我这样的情况,并且可以通过简单的 svn ls http://[url]/svn 测试其他错误报告(是返回 403 还是成功)。我个人感觉空格不应该有影响 - 毕竟编写容忍空格的 Perl 代码比编写 shell 脚本要容易得多... :-) - FooF
我没有可用的原始设置来测试您的解决方案是否有效。看起来非常有前途 - 我不知道提供相对路径--trunk--tags--branches的可能性。如果我有测试这个的可能性,或者如果有人确认使用主干、标签、分支的相对路径解决了权限问题的建议,我将选择此答案。谢谢! - FooF
这是我从转换中得到的.git/config。我没有[remote-svn],但我有一个[svn-remote] - Jeff Fairley

2

[我知道这应该是对Jeff Fairley答案的评论,但我没有足够的声望来发布它。既然原帖要求确认方法是否有效,我将其作为答案提供。]

我可以确认他的解决方案适用于他(和我)遇到的由于路径中有空格而引起的问题。我有相同的需求(从一个带有历史记录的SVN仓库克隆单个模块),但我完全不需要担心分支或标签。

我尝试了提供模块完整路径的多种排列组合(例如使用--no-minimise-url,指定--trunk--stdlayout),但都没有成功。对我来说,结果通常是一个具有完整历史记录日志但没有任何文件的git仓库。这可能与FooF遇到的问题(在SVN中没有读取访问权限)相同,但肯定是由于我的模块路径中有空格引起的。

只使用SVN仓库基础作为URL,然后在--trunk中提供我的模块路径再次尝试,结果完美无缺。之后,我的.git/config看起来像这样:

[core]
        repositoryformatversion = 0
        filemode = false
        bare = false
        loggallrefupdates = true
        symlinks = false
        ignorecase = true
        hideDotFiles = dotGitOnly
[svn-remote "svn"]
        url = https://[url]/svn/[repo]
        fetch = trunk/[path-to-code]:refs/remotes/trunk
[svn]
        authorsfile = ~/working/authors-transform.txt

接下来的gitgit svn命令都没有出现任何错误。感谢Jeff!


1

[这是原帖发布者写的。以下内容曾经是对问题的更新,但由于解决了该问题(尽管不符合我的口味),我将其作为一个答案发布,缺乏更好的解决方案。]

我不喜欢这样做,但最终我把clone拆分成了initfetch,并在两者之间进行了一些.git/config的编辑(repopath=apps/modulegitreponame=module):

$ git svn init--username=mysvnusername \
            --branches=/src/branches/ \
            --trunk=/src/trunk/${repopath} \
            --tags=/src/tags/ \
            http://svnserver/svn/src ${gitreponame}
$ cd ${gitreponame}
$ sed -i.bak "s|*:|*/${repopath}:|" .git/config
$ git svn fetch --authors-file=../authors.txt --follow-parent

我找不到如何使用git svn指定子目录迁移的分支 - 因此需要编辑.git/config文件。下面是使用sed编辑后的统一差异效果:

 [svn-remote "svn"]
        url = http://svnserver/svn/src
        fetch = trunk/apps/module:refs/remotes/trunk
-       branches = branches/*:refs/remotes/*
-       tags = tags/*:refs/remotes/tags/*
+       branches = branches/*/apps/module:refs/remotes/*
+       tags = tags/*/apps/module:refs/remotes/tags/*

由于实际所需的HEAD位于另一个URL中,因此我只需向.git/config添加另一个[svn-remote]部分即可:
+ [svn-remote "svn-newest"]
+       url = http://svnserver/svn/src
+       fetch = branches/x/y/apps/module:refs/remotes/trunk
+       branches = branches/*/apps/module:refs/remotes/*
+       tags = tags/*/apps/module:refs/remotes/tags/*

(在真实的实验中,我还添加了一些未被第一次获取的分支,并进行了再次获取:)
$ git svn fetch --authors-file=../authors.txt --follow-parent svn-newest

这样一来,我就成功将完整的Subversion历史记录迁移到了新生成的git存储库中。
注意1:我可能只需告诉我的“主干”为branches/x/y/apps/module,因为对于git-svn来说,“主干”的意义似乎基本上是指git HEAD(trunk、branch、tag的概念在技术上没有深刻的基础,它们是社会公认的惯例)。
注意2:可能--follow-parentgit svn fetch中不是必需的,但我现在无法知道或进行实验。
注意3:早些时候阅读svn2git时,它似乎是git-svn的包装器,我没有看到动机,但看到标签的混乱表示后,我有点明白了。如果我再次尝试这样做,我会尝试使用svn2git

P.S. 这种方式操作相当尴尬。这里的第二个问题(为什么需要外部编辑.git/config)似乎是:

  1. Subversion 分支没有任何基本的技术含义(Subversion 中的 分支标签 只是版本化文件系统副本的社会约定标签,并且按照“标准”或其他社会协议完成副本复制 - 主干 也没有技术含义),以及
  2. git svn 实现严格假设社交 Subversion 规则被遵循到一定程度(如果只想迁移子目录而不是整个 Subversion 存储库,则不可能实现这一点)。

待办事项: 在这里解释一下.git/config文件的格式,因为它与git svn有关,这将非常有帮助 - 例如,在撰写原始答案的一年半后,我现在不知道上面的[svn-remote "svn-newest"]是什么意思。此外,可以通过编写脚本来自动化处理,但这超出了我对该问题目前的兴趣范围,并且我无法访问原始的Subversion存储库或问题的复制。


1
查看我的答案,可以绕过处理.git/config的方式。 - Dejay Clayton

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