在我们开始之前
在我深入探讨答案之前,请注意,如果您想为每个远程跟踪名称都有一个本地分支名称,您可以简单地创建该本地分支名称,而无需使用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
这里的意思是所有参考文献。但什么是“参考”呢?
嗯,任何分店名称是一个参考。但任何也是如此tag name。特别的名字refs/stash
,使用者git stash
,是一个参考。远程跟踪名称仅供参考。注释参考文献(来自git notes
) 为参考。有关此术语和其他 Git 术语的更多信息,请参阅git 术语表 https://www.kernel.org/pub/software/scm/git/docs/gitglossary.html(请注意,该特定条目位于ref
而不是reference
).
当你第一次使用时git clone
要克隆存储库,您需要告诉自己的 Git:在我给您的 URL 上创建一些现有存储库的新的独立副本,以便我可以完成自己的工作,然后根据需要共享或不共享。 But their存储库——URL 上的“他们”是谁——拥有its own分支机构名称。他们有their master
,这并不总是与您的相同master
。所以你的 Gitrenames他们的名字:他们的master
成为你的origin/master
, 等等。这些远程跟踪名称是参考。
After git clone
完成将所有提交复制到您的存储库,并将所有名称重命名为您的远程跟踪名称,这是最后一步git clone
是检查一个分支。但你不have还没有任何分支机构。这就是一个特殊的技巧git checkout
确实出现了:如果你要求 Git 按名称签出一个分支,不存在,Git 会查看所有远程跟踪名称。如果其中之一匹配,Git 将create本地分支名称——一个新的引用——指向samecommit 作为此远程跟踪名称。
因此,您的存储库有一系列提交,所有这些提交都以向后的方式相互链接:
first <--next ... <--almost-last <--last
(如果它们都是线性的,但它们几乎从来都不是)我们可以将其绘制为:
A--B--...--H--I
其中每个大写字母代表一次提交。一组具有某些“分支性”(分支性?)的提交可能如下所示:
C--D
/
A--B
\
E--F--G
如果有合并提交,则向后指向two以前的提交而不是只有一次,它会更加复杂。
The names我们最关心的是分支名称和远程跟踪名称,尤其是 Git 的一种方式find最后一次提交:
...--H--I <-- origin/master
名字origin/master
据说point to commit I
。当你的 Git 创建你自己的master
, your master
now also指着I
:
...--H--I <-- master, origin/master
如果您创建自己的新提交master
,这就是发生的情况:
...--H--I <-- origin/master
\
J <-- master
Git 为新的提交创建一个新的 ID——它是一些明显随机的又大又难看的哈希 ID,但在这里我们只是称之为它J
-进而changes你的名字master
指向这个新的提交。
如果你跑git fetch
并引入新的提交origin
他们已经更新了their主人,你现在得到:
...--H--I--K <-- origin/master
\
J <-- master
现在你的master
和他们的origin/master
已经出现分歧。
这些名字,master
and origin/master
,对做出承诺具有重要影响可达的。也就是说,通过跟随每个名称中的箭头,Git 可以找到提交J
and K
。然后,使用向后箭头——实际上是提交的parent提交哈希 ID — 来自J
to I
或来自K
to I
,Git可以找到commitI
。使用向后箭头I
本身,Git 可以找到H
,依此类推,一直回到第一次提交,即操作停止的地方。
All 无法到达的提交——那些通过从所有这些起始(结束?)点开始并向后走而没有找到的提交——将在某个时刻被删除,因此它们实际上不存在。对于大多数遍历图表的 Git 命令来说,情况也是如此。 (有一些特殊目的的恢复技巧可以让您将已删除的提交恢复 30 天,但 filter-branch 不支持这些。)
这一切对过滤器分支意味着什么
的工作git filter-branch
is to 复制提交。它会遍历图表,使用您提供的起始(结束?)点来查找所有可到达的提交。它将它们的哈希 ID 保存在临时文件中。然后,朝相反的方向前进——即时间向前,而不是 Git 通常的向后——它提取每个提交。也就是说,它会对其进行检查,以便该快照中的所有文件都可用。然后过滤器分支应用过滤器,然后从结果文件中进行新的提交。因此,如果您的过滤器进行简单的更改,结果就是copy原始图的:
A--B--C------G--H <-- master, origin/master
\ /
D--E--F
becomes:
A'-B'-C'-----G'-H' <-- master, origin/master
\ /
D'-E'-F'
原始提交会发生什么?好吧,它们仍然在那里:过滤器分支对找到它们的名称所做的是rename他们,使用refs/original/
在他们的内部全名前面:
A--B--C------G--H <-- refs/original/refs/heads/master, refs/original/refs/remotes/origin/master
\ /
D--E--F
过滤器分支有这么多过滤器选项的原因之一是这个过程非常慢。将每个文件提取到临时目录中需要很长时间。因此,某些过滤器可以在根本不提取文件的情况下工作,这会快得多(快得多!)。
另一个原因是有时我们不想复制every提交,我们只想复制some满足某些标准的提交。情况就是如此--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'
, not A'
, since A'
没有相关的子目录。
这里还有一个非常重要的附带问题:过滤器分支在完成所有提交复制后会更新哪些引用?答案在文档中:
该命令只会重写positive中提到的参考文献
命令行(例如,如果您通过a..b, only b将被重写)。
既然你正在使用--all
,这将重写所有origin/*
远程跟踪名称。 (--all
算作对这里每一位裁判的积极提及。标签有一些额外的技巧:如果你想重写你的标签,添加--tag-name-filter cat
作为过滤器。)
Summary
在你的过滤分支操作之后,你有一系列refs/original/*
指向原始(预过滤)提交的名称,从原始全名重命名。您有一系列新更新的参考资料,包括所有分支名称(refs/heads/*
)和远程跟踪名称(refs/remotes/*
)指向被复制的最后一个提交。
新的存储库将是bigger比原来的,因为它contains原始的,加上复制的提交。请参阅缩小存储库的清单的部分the git filter-branch文档 https://www.kernel.org/pub/software/scm/git/docs/git-filter-branch.html, 接近尾声。但请注意,如果您使用git clone
复制过滤后的存储库,仅复制您的branch名字,不是你的远程追踪名称,因此此时,如果您尚未为每个远程跟踪名称创建分支,则应该立即执行此操作。
或者,您可以在删除所有存储库后将复制的存储库保留在适当的位置refs/original/
命名空间名称。那么你可以git checkout develop
创建你自己的refs/heads/develop
根据您的(过滤后的)refs/remotes/origin/develop
, 等等。您所做的就是创建新名称(提交本身是 Git 真正关心的内容,并且由重写的远程跟踪名称引用它们),然后检查该特定提交,以便它位于您的索引和工作树中。 (这git branch -t
我们在开始时显示的命令创建了名称,而不将提交复制到索引和工作树。)