目前,我当前正在使用命令“ git filter-branch --subdirectory-filter MY_DIRECTORY---all”从此git repo的所有30个分支中获取某个目录。在执行此filter branch命令之前,请确保签出每个分支以确保--all命令正常运行。
我的问题是,在执行全部git-filter操作之前我是否必须检出每个分支?还是必须检出我正在查看的所有30个分支,否则git-filter仍可以正常工作?现在,每个分支几乎都为3GB,因此整个结帐过程将花费很长时间。任何澄清都很好!
答案 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/master
和master
具有使其提交 reachable 的重要作用。也就是说,通过跟随每个名称中的箭头,Git可以找到提交origin/master
和J
。然后,使用后退箭头(确实是提交的 parent 提交哈希ID)从K
到J
或从I
到K
,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。)