克隆git-svn存储库会导致“消失”的分支

时间:2019-10-30 23:59:37

标签: git git-branch git-svn git-clone

前言

我们有一个很大的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上创建了分支回购。

1 个答案:

答案 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的作用是:

  1. 创建一个新的空目录(或使用您告诉它使用的目录);
  2. 使用git clone在该目录中创建一个新的空存储库;
  3. 添加一个远程,它由一个简单的名称(如git init)和一个URL(以及一些配置-可以移至步骤4,或视为步骤3的一部分)组成;
  4. 进行任何其他必要的配置;
  5. 运行origin;最后
  6. 使用您提供的名称或其他Git提供的名称运行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。