首页 > 解决方案 > Git Filter-Branch All 命令

问题描述

目前,我正在使用命令“git filter-branch --subdirectory-filter MY_DIRECTORY -- --all”从这个 git repo 的所有 30 个分支中获取某个目录。在执行此过滤器分支命令之前,我确保检查每个分支以确保 --all 命令正常工作。

我的问题是,我是否必须在执行 git-filter all 之前检查每个分支,还是 git-filter all 仍然可以工作,而无需检查我正在查看的所有 30 个分支?现在每个分支几乎是 3GB,所以整个结帐过程需要很长时间。任何澄清都会很棒!

标签: gitgit-filter-branch

解决方案


在我们开始之前

在我深入回答之前,请注意,如果您想为每个远程跟踪名称设置一个本地分支名称,您可以简单地创建该本地分支名称,而无需使用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 filter-branch很好地使用 Git(包括这个特定的操作)。

让我们从这个开始:--all这里的意思是所有的引用。但是什么是“参考”呢?

好吧,任何分支名称都是参考。但是任何标签名称也是如此。refs/stash由使用的特殊名称git stash是一个参考。远程跟踪名称是参考。注释 refs (from 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

其中每个大写字母代表一个提交。一组带有一些“分支”(分支?)的提交可能看起来像:

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

如果有合并提交,它指向两个先前的提交而不是一个,它会更加复杂。

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

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

origin/master据说名字指向commit I。当您的 Git 创建自己的master时,您的masternow指向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

现在你master和他们origin/master已经分道扬镳了。

这些名称masterorigin/master具有使其提交可访问的重要作用。也就是说,通过每个名称中的箭头,Git 可以找到提交JK. 然后,使用向后箭头(实际上是提交的提交哈希 ID) from JtoI或 from Kto I,Git 可以找到 commit I。使用I自身的向后箭头,Git 可以找到H,依此类推,一直回到第一个提交,动作停止的地方。

所有无法到达的提交——那些从所有这些起点(终点?)开始并向后走而没有找到的提交——将在某个时候被删除,因此它们实际上不存在。对于遍历图表的大多数 Git 命令而言,情况也是如此。(有一些特殊用途的恢复技巧可以让您在 30 天内恢复已删除的提交,但 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-branchgit clone

或者,您可以在删除所有refs/original/命名空间名称后保留复制的存储库。然后,您可以根据您的 (filtered)git checkout develop创建自己的,依此类推。你所做的只是创建新名称——提交本身是 Git 真正关心的,它们被重写的远程跟踪名称引用——然后检查那个特定的提交,以便它在你的索引和工作树中. (我们在开始时展示的命令创建了名称,而没有将提交复制到 index-and-work-tree。)refs/heads/developrefs/remotes/origin/developgit branch -t


推荐阅读