Git Filter-Branch All 命令

6

目前,我正在使用以下命令:"git filter-branch --subdirectory-filter MY_DIRECTORY -- --all" 来从这个 git 存储库的所有30个分支中获取特定目录。在执行此筛选分支命令之前,我会确保检出每个分支,以确保 --all 命令能够正常工作。

我的问题是,在执行 git-filter all 命令之前,我是否必须检出每个分支,或者 git-filter all 命令是否仍然可以在不必检出我所查看的所有30个分支的情况下正常工作?目前,每个分支的大小都接近3GB,因此整个检出过程需要很长时间。希望您能给予澄清!

1个回答

15

开始之前

在我开始回答之前,请注意,如果你想为每个远程跟踪名称创建本地分支名称,你可以直接创建该本地分支名称,而无需使用 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 意味着 所有引用。 那么什么是“引用”呢?
嗯,任何 分支名称 都是一个引用。 但是,任何 标签名称 也是一个引用。 由 git stash 使用的特殊名称 refs/stash 是一个引用。 远程跟踪名称是引用。 笔记引用(来自 git notes)是引用。 有关此及其他 Git 术语的详细信息,请参见 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,然后将您的名称master更改为指向此新提交。

如果您运行git fetch并从origin获取新提交,并且它们已更新他们的主分支,则现在会出现以下情况:

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

现在你的masterorigin/master已经分叉。

这些名称,masterorigin/master,有一个重要的作用,使它们的提交可达。也就是说,通过从每个名称开始的箭头,Git可以找到提交JK。然后,使用向后的箭头——实际上是提交的父提交哈希ID——从JI或从KI,Git可以找到提交I。使用I本身的向后箭头,Git可以找到H,以此类推,一直回溯到第一个提交,操作停止。

所有不可访问的提交——那些从所有这些起始(结束?)点开始并向后行走时未找到的提交——将在某个时候被删除,因此它们实际上不存在。对于大多数遍历图形的Git命令来说,这也是如此。(有一些特殊的恢复技巧可以让您在30天内重新获取已删除的提交,但filter-branch不支持这些技巧。)

filter-branch对所有这些的影响

git filter-branch的工作是复制提交。它遍历图形,使用您提供的起始(结束?)点找到所有可访问的提交。它将它们的哈希ID保存在一个临时文件中。然后,向相反的方向——即向前而不是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'

原始提交内容会发生什么变化呢?实际上,它们仍然存在:filter-branch 会用 refs/original/ 作为前缀,对找到的原始提交进行重命名,但并不会删除它们的内部全名。

A--B--C------G--H   <-- refs/original/refs/heads/master, refs/original/refs/remotes/origin/master
    \       /
     D--E--F

一个使用filter-branch的原因是这个过程非常缓慢。将每个文件提取到临时目录需要很长时间。因此,一些过滤器可以在不提取文件的情况下工作,速度快得多。
另一个原因是有时我们不想复制每个提交,只想复制符合某些条件的一些提交。这就是--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',因为A'没有相关的子目录。
这里还有一个非常重要的问题:filter-branch在完成所有提交复制后会更新哪些引用?答案在文档中:
引用如下:

该命令仅将命令行中提到的正数引用进行重写(例如,如果传递a..b,则只会重写b)。

由于您正在使用--all,因此这将重写所有origin/*远程跟踪名称。(在此处,--all计为每个引用的正数提及。关于标签还有一些额外的技巧:如果要重写标签,请添加--tag-name-filter cat作为过滤器。)
总结: 在filter-branch操作之后,您拥有一系列指向原始(经过过滤之前)提交的refs/original/*名称,它们已从其原始全名重新命名。您拥有一系列新的更新引用,包括所有分支名称(refs/heads/*)和远程跟踪名称(refs/remotes/*),它们都指向最后一个被复制的提交。
新仓库将比原始仓库更大,因为它包含原始内容以及复制的提交。请参阅git filter-branch文档中“缩小存储库的清单”部分,但要注意,如果您使用git clone复制过滤后的存储库,则只会复制您的分支名称,而不是您的远程跟踪名称,所以此时,如果您尚未为每个远程跟踪名称创建分支,则应立即执行该操作。

另一种方法是,在删除所有refs/original/名称空间后,将复制的仓库保留在原地。 然后,您可以git checkout develop,根据(过滤后的)refs/remotes/origin/develop创建自己的refs/heads/develop,以此类推。您所做的只是创建新名称 - 提交本身才是Git真正关心的内容,并且它们由重写的远程跟踪名称引用 - 然后检出该特定提交,以便它位于您的索引和工作目录中。(我们在开头展示的git branch -t命令创建了没有将提交复制到索引和工作目录的名称。)


这太棒了,我真的非常感谢你的指导和解释!我现在完全明白我需要做什么了。 - JWill23

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