前言
我们有一个很大的SVN存储库(超过200k的提交以及数百个分支和标签)。大的,不祥的,难以维护的,令人沮丧的混乱。为了更有效地工作,大约一年前,我在开发机器上进行了git svn克隆,因此我在GIT上进行本地开发,然后推送到SVN。
我们现在正在考虑拆分存储库,并将主要开发分支移至git,或者至少将我们的开发分支移至git。
由于我有本地git存储库,因此我想通过克隆它的一部分并将其推送到我们公司的GitLab进行一些测试,但没有成功,可能是因为我缺乏一些Git机制的知识
开始吧
为了在不推送整个30GB存储库的情况下进行一些快速测试,我想对本地Git存储库进行浅表克隆,并使用以下命令来推送克隆:
git clone --depth=1 --no-single-branch file:///path/to/repo
我想克隆每个分支的HEAD版本,但是克隆只包括master分支和我们的开发分支,其他都没有(我不确定标签,我没有检查)。一段时间后,我意识到该克隆仅包含我们的dev分支,因为它是我签出的唯一分支(即使git svn存储库是SVN存储库的完整克隆)。
然后我尝试做一个
git clone file:///path/to/repo
然后我又只得到了master和我的开发部门,
在这两次尝试中,我注意到该克隆文件(200-700MB)比原始git存储库(30GB)小得多。在第二次尝试中,我期望存储库的大小与原始存储库相同。
因此,我意识到git仅克隆了检出的分支,而不是远程的分支(remots / svn / *)。为什么,由于git svn repo是svn repo的完整副本?为什么不克隆所有分支?他们在那里(否则git svn repo不会那么大),只是他们没有被签出。而且...我们如何谈论“远程”分支?它们不是git svn repo的一部分,应该被认为是本地的吗?
那么在克隆git svn repo时我如何告诉git考虑所有这些分支?我不想大量检查git svn repo中的所有分支,对我来说听起来像一个笨拙而混乱的解决方案。
更新
感谢您的回复。很抱歉没有尽快给您答复,但是您留下了很多文档来阅读,而且我还必须自己做一些其他研究!
因此,如果我的理解是正确的,我的git-svn信息库将包含原始svn信息库的所有提交,并且知道svn信息库包含分支和标签,但是在本地它与提交的SHA1之间没有关联和标签(即分支名称),我必须手动添加这些关联。
您的代码段是一个非常有用的起点,谢谢!
我还发现了clone命令的魔术参数--mirror
,它也导入了遥控器,因此我不必触摸git-svn repo,但是后来我直接在克隆的git上创建了分支回购。
答案 0 :(得分:0)
TL; DR:您需要为要作为分支的每个分支创建实际的分支名称。克隆时,远程跟踪名称只是不计算在内(通常)。这可以很便宜!请继续阅读以获取详细说明。
这是一种通过每个refs/remotes/svn/*
名称创建本地分支的廉价方法:
git for-each-ref --format='%(refname)' refs/remotes/svn |
while read name; do
local=${name#refs/remotes/svn/} # remove the icky part from the name
[ "$local" == HEAD ] && continue
git branch $local $name
done
这(注意:未经测试,可能有一些小错误)将为那些具有相应本地分支名称的名称显示一条错误消息。大概您可以忽略它。
...所以我意识到git只克隆了检出的分支,而不是远程的分支...
实际上没有“远程分支”之类的东西。好吧,除非您以存在的方式定义“远程分支”。最终,我们一开始就遇到了定义“分支”的问题:请参见What exactly do we mean by "branch"?在注意这一点时(与日常交谈相反),我想确保使用两个词组分支名称来指代master
之类的名称,这些名称实际上已经被缩短:请参见下文。
Git处理的是 commits ,由 names 和其他提交找到。有关 reachability 和许多相关内容的正确定义,请参见Think Like (a) Git,但通常的想法是使用名称,例如{{1} }或refs/heads/master
-每个都保存一个提交的哈希ID。那个提交会记住哪个提交在它之前。这些提交- parent 提交-记住他们的前任提交,祖父母记得其前任提交,依此类推。
refs/remotes/svn/foo
的作用是:
git clone
在该目录中创建一个新的空存储库; git init
)和一个URL(以及一些配置-可以移至步骤4,或视为步骤3的一部分)组成; origin
;最后git fetch
,或者在最糟糕的后备情况下尝试使用git checkout
。这里的第5步对您来说是最重要的,因为git checkout master
是所有主要动作所在的地方。
为什么不克隆所有分支?
运行git fetch
时,它会从 other Git获得一个列表,其中另一个Git会告诉它有关其 all 名称的信息。另一个Git会说,例如我有git fetch
,即提交refs/heads/master
;我有a123456...
,就是refs/remotes/svn/foo
,依此类推。
您的 Git,然后丢弃 not 以b789abc...
或refs/heads/
开头的任何名称。结果名称列表是其Git的分支名称和标签名称。所有其他名称都属于其他类别。特别是,任何以refs/tags/
开头的名称都是远程跟踪名称, 2 ,因此会被丢弃。
然后,您的Git要求其Git提交提交(通过哈希ID)以及完成提交和使用所需的其他任何对象。只要您使用标签,您的Git还会要求通过标签名称标识的对象-尽管当refs/remotes/
选项变得非常复杂时,准确地获取了哪个标签。
一旦您的Git具有提交对象和其他内部对象(如果需要),则您的Git然后将其分支名称(其git fetch
等)复制到 远程跟踪名称。他们的refs/heads/master
成为您的refs/heads/master
。他们的refs/remotes/origin/master
(如果有的话)将成为您的refs/heads/develop
。
所有这些操作都在refs/remotes/origin/develop
步骤(步骤5)中发生。诸如git fetch
或--single-branch
之类的选项会影响其匹配的分支名称,但不会影响从分支名称到远程跟踪名称的转换。 --no-single-branch
选项确实会影响转换,完全消除了转换,但是有时也暗示了--mirror
的副作用。
最后一步,即步骤6中的--bare
具有很大的副作用。您刚刚创建的新克隆没有 no 分支名称。 3 所以git checkout
或其他任何名称显然注定要失败,对吧?但这并不会失败。相反,Git使用了一个巧妙的(?)技巧:当您要求签出不存在的分支名称时,Git会查看远程跟踪名称以查看是否存在一个。如果是这样,Git将使用存储在相应远程跟踪名称中的提交哈希ID 创建(本地)分支名称。
因此,该创建您所请求的任何分支-或在这种情况下,由于您没有指定一个分支,另一个Git告诉您的Git另一个Git推荐哪个分支名称。 (无论如何,通常通常只是git checkout master
。)步骤6是创建它的原因。
如果master
存储库中有标签,则新克隆中也将包含一些标签-介于零到全部之间。您可以稍后再使用origin
来显式地请求标签,也可以不这样。您可以在克隆时明确要求 not 在新克隆中包含标签。此时,您所拥有的标记只是从其他存储库中的标记复制而来。这里的想法是,与分支名称完全独立于每个存储库的分支名称不同,标记名称将在所有存储库中共享,并通过存储库连接传播,就像某种病毒一样。 4 < / p>
由于您的源存储库大多只具有远程跟踪名称,而不是分支,因此您的克隆(无论是否浅)都省略了这些名称和,这些提交仅可从到达这些名字。
1 这与SVN有很大的不同,在SVN中,只有一个中央服务器,可以简单地按顺序对每个修订进行编号。从字面上来看,Git 不能依赖于顺序编号,因为可能存在依次但平行地(此处为非单词表示歉意)的单独克隆获得不同的提交。也就是说,假设克隆A和B相同,并且每个克隆都有500个提交。然后,在克隆A中工作的爱丽丝创建提交#501。同时,在克隆B中工作的Bob会创建提交#501。这两个提交是不同的(可能在不同的分支上),并且它们都是#501。连续数字在这里不起作用。
2 Git将此称为远程跟踪分支名称。我曾经用过这个词,但现在我觉得这里的 branch 一词更具误导性,而不是有用的。您可以随心所欲地对其进行命名:只需记住它不是分支名称,因为这些名称实际上以git fetch
开头。
注意:在打印名称时,Git通常会在这里剥离refs/heads/
,refs/heads/
和refs/tags/
部分,但前提是输出仍然足够清晰。有时,Git只会剥离refs/remotes/
:尝试refs/
,然后尝试git branch -r
。 (为什么这些不同?这是一个谜。)
3 如果使用git branch -a
,则新克隆具有所有分支名称,但是--mirror
跳过步骤6。新克隆是光秃秃的,所以没有工作树,并且无法使用git clone
。
4 这也是提交传播的方式。假设您连续提交了W,X和Y,而他们没有。您以git checkout
操作连接到他们的Git,并给他们所有这三个提交,并要求他们设置他们的名字之一以记住提交push
,记住Y
,记住X
,它会记住他们已经提交的提交。
或者:他们有这些提交,而您没有。您以W
操作连接到他们的Git,他们给了全部三个,而您的Git设置了fetch
以便立即记住提交origin/whatever
。
基本上,您有两个Git存储库要配对。一个发送,另一个接收。接收者获得了接收者要求发送者发送的所有新内容,即使最终接收者根本并不需要它:在这一点上,接收者可以拒绝更新某些名称的请求来记住提交链中的 last 提交。因此,接收方保留其旧名称及其旧的哈希ID,或者没有名称(也没有哈希ID)。
提交或其他Git对象的哈希ID无法找到它,最终将被垃圾回收并扔掉。对于裸存储库,这往往会更快,并且自Git 2.11起,服务器“接收提交和其他Git对象”过程首先将它们粘贴在隔离区中,然后再确定它们是否良好并接受它们,或者确定它们是合格的。坏,拒绝他们。然后,被接受的邮件将被隔离,从隔离区迁移到真实的存储库数据库,被拒绝的邮件将被迅速丢弃。在2.11之前的版本中,接收到的对象立即进入,暂时使服务器膨胀,例如拒绝大型文件(考虑GitHub的100MB文件大小限制)。
浅表克隆会修改(其中的)一些规则:使用浅表克隆,接收方Git会具有一个特殊的文件,其中包含散列ID。它缺少那些实际的提交,但是假装包含它们,因此,当发送方询问“您是否拥有提交X”时,答案为“是”,因此发送方从不发送提交X。