Git Filter-Branch All命令

时间:2018-06-21 12:31:39

标签: git git-filter-branch

目前,我当前正在使用命令“ git filter-branch --subdirectory-filter MY_DIRECTORY---all”从此git repo的所有30个分支中获取某个目录。在执行此filter branch命令之前,请确保签出每个分支以确保--all命令正常运行。

我的问题是,在执行全部git-filter操作之前我是否必须检出每个分支?还是必须检出我正在查看的所有30个分支,否则git-filter仍可以正常工作?现在,每个分支几乎都为3GB,因此整个结帐过程将花费很长时间。任何澄清都很好!

1 个答案:

答案 0 :(得分:4)

开始之前

在深入探讨答案本身之前,请注意,如果您希望为每个远程跟踪名称都拥有一个本地分支名称,则可以简单地创建该本地分支名称,而无需使用git checkout

git branch -t develop origin/develop
git branch -t feature/X origin/feature/X
git branch -t foo origin/foo

,依此类推。这是git checkout的子集,并且非常快,因为创建新的分支名称仅意味着写入一个文件。

(如果愿意,可以使用此技术并在此处停止,但是此答案的其余部分应该非常有用。)

简短的回答

简短的答案是您不必签出(或创建新的)分支名称。但是,您将需要更好地理解Git(包括此特定的git filter-branch操作)。

让我们从这里开始:--all在这里表示所有引用。但是什么是“参考”?

好吧,任何分支名称都是参考。但是任何标签名称也是如此。 refs/stash使用的特殊名称git stash是参考。远程跟踪名称是参考。注释refs(来自git notes)是引用。有关此术语和其他Git术语的更多信息,请参见the gitglossary(请注意,该特定条目位于ref下,而不是reference下。)

当您第一次使用git clone克隆存储库时,您是在告诉自己的Git:使用我给您的URL创建一些现有存储库的新的独立副本,以便我可以我自己的工作,然后根据需要共享或不共享。但是,存储库(无论它们位于URL处)都具有自己的分支名称。他们的他们的 master并不总是与您的master相同。因此,您的Git 重命名他们的名字:他们的master成为您的origin/master,依此类推。这些远程跟踪名称是参考。

git clone完成将所有提交复制到您的存储库,并将其所有名称重命名为您的远程跟踪名称之后,git clone的最后一步是签出一个分支。但是您还没有拥有任何分支。这是git checkout的一个特殊技巧:如果您要求Git通过名称检出不存在的分支,则Git会遍历您的所有远程跟踪名称。如果其中一个匹配,Git将创建一个本地分支名称(一个新引用),该分支名称指向相同提交作为此远程跟踪名称。

因此,您的存储库具有一系列提交,所有这些提交都以向后的方式彼此链接:

first  <--next ... <--almost-last  <--last

(如果它们都是线性的,它们几乎从不存在),我们可以将其绘制为:

A--B--...--H--I

其中每个大写字母表示一次提交。一组带有一些“支离破碎”(branchiness?)的提交可能看起来像:

     C--D
    /
A--B
    \
     E--F--G

,并且如果存在合并提交,这些合并提交会指向以前的两个提交而不是一个,那么它将更加复杂。

我们在这里最关心的名称(尤其是分支名称和远程跟踪名称)是Git查找最后一次提交的一种方式:

...--H--I   <-- origin/master

说名称origin/master指向 提交I。当您的Git创建自己的master时,您的master现在还 指向I

...--H--I   <-- master, origin/master

如果您在master上创建自己的新提交,则会发生以下情况:

...--H--I   <-- origin/master
         \
          J   <-- master

Git为新提交组成了一个新ID,它是一个看上去很随机的丑陋哈希ID,但在这里我们仅将其称为J,然后更改您的名字{{ 1}}指向此新提交。

如果您运行master并从git fetch引入新的提交,并且它们已经更新了母版,则您将获得:

origin

现在您的...--H--I--K <-- origin/master \ J <-- master 和他们的master有所分歧。

这些名称origin/mastermaster具有使其提交 reachable 的重要作用。也就是说,通过跟随每个名称中的箭头,Git可以找到提交origin/masterJ。然后,使用后退箭头(确实是提交的 parent 提交哈希ID)从KJ或从IK,Git可以查找提交I。使用I本身的向后箭头,Git可以找到I,依此类推,一直返回到第一次提交,操作停止。

所有 unreachable 提交(在所有这些开始点(结束点)和向后走都找不到的提交)将在某个时刻被删除,因此它们实际上不存在。对于遍历图形的大多数Git命令而言,情况也是如此。 (有一些特殊用途的恢复技巧,可以让您将删除的提交恢复30天,但是filter-branch并不兑现这些。)

这对过滤分支意味着什么

H的工作是复制提交。它遍历图形,使用起点(终点)给它查找所有可到达的提交。它将其哈希ID保存在一个临时文件中。然后,朝相反的方向移动(即,按时间向前移动而不是Git通常向后移动),它将提取每个提交。也就是说,它会将其签出,以便该快照中的所有文件都可用。然后filter-branch应用过滤器,然后从生成的文件中进行新提交。因此,如果您的过滤器进行了简单的更改,结果就是原始图的副本

git filter-branch

成为:

A--B--C------G--H   <-- master, origin/master
    \       /
     D--E--F

原始提交会怎样?好的,它们仍然存在:筛选器分支对找到它们的名称的作用是使用其内部全名前面的A'-B'-C'-----G'-H' <-- master, origin/master \ / D'-E'-F' 重命名它们:

refs/original/

filter-branch有这么多过滤器选项的原因之一是,此过程非常缓慢。将每个文件提取到一个临时目录需要很长时间。因此,某些过滤器可以完全不提取文件而工作,这要快得多(很多!)。

另一个原因是,有时我们不想复制每次提交,我们只希望复制符合某些条件的 some 提交。 A--B--C------G--H <-- refs/original/refs/heads/master, refs/original/refs/remotes/origin/master \ / D--E--F 就是这种情况:仅当更改涉及所涉及子目录的文件(相对于其父提交)时,它才复制提交。因此,在某些情况下,它可以跳过提取大量提交。当然,子目录过滤器在提取并重新提交的过程中还会重命名文件,以删除子目录路径。结果是将较大的提交图复制到较新的较小的提交图:

--subdirectory-filter

可能会变成:

A--B--C------G--H   <-- master
    \       /
     D--E--F

保留的B'--G'--H' <-- master \ / E' 仍将指向提交refs/original/refs/heads/master,而重写的H将指向复制的提交refs/heads/master。请注意,新图中的第一个提交是H',而不是B',因为A'没有相关的子目录。

这里还有一个非常重要的附带问题:哪些参考文件在完成所有提交复制后会进行分支过滤更新?答案在文档中:

  

该命令将仅重写 positive 引用中提到的    命令行(例如,如果您通过 a..b ,则只会重写 b )。

由于您使用的是A',因此它将重写所有--all的远程跟踪名称。 ({origin/*被视为这里所有引用的肯定提及。标签还有一些额外的技巧:如果要重写标签,请添加--all作为过滤器。)

摘要

过滤器分支操作之后,您有一系列--tag-name-filter cat名称指向原始(预过滤)提交,并从其原始全名重命名。您将拥有一系列新的更新参考,包括所有分支名称(refs/original/*)和远程跟踪名称(refs/heads/*),它们指向复制的任何提交中的最后一个。

新的存储库将比原始存储库更大,因为它包含原始存储库以及复制的提交,因此会包含原始存储库。快结束时,请参见the git filter-branch documentation收缩存储库清单部分。但是请注意,如果您使用refs/remotes/*复制已过滤的存储库,那么该存储库仅复制您的分支名称,而不是您的远程跟踪名称,因此,在这一点上,如果尚未为每个远程跟踪名称创建一个分支,则应该立即执行此操作。

或者,您可以在删除所有git clone名称空间名称之后,将复制的存储库保留在适当的位置。然后,您可以refs/original/根据(过滤的)git checkout develop创建自己的refs/heads/develop,依此类推。您要做的就是创建新名称-提交本身就是Git真正关心的,并由重写的远程跟踪名称引用它们-然后检查该特定提交,以使其位于索引和工作树中。 (我们在开始时显示的refs/remotes/origin/develop命令创建的名称没有将提交复制到index-and-work-tree。)