我现在已经完成了我的一个小图书馆。当我开始使用它时,我不知道 clang-format。现在我想用它格式化整个存储库。我知道随着提交哈希值的变化,这会破坏其他人的存储库。但是,由于还没有人在使用我的库,所以这对我来说没问题。
因此,我必须做什么才能为我的历史记录中的每个提交运行 clang-format?
Git 附带了一个git filter-branch命令,该命令是帮助完成此类任务的工具。请注意,git filter-branch它本身并不能完成工作:它只是您可以使用的工具,以便您可以完成工作。您仍然必须编写自己的命令。您最终可能会使用的是:
git filter-branch --tree-filter '<some command here>' --tag-name-filter cat -- --all
Run Code Online (Sandbox Code Playgroud)
这里有一个基本问题:没有提交,一旦做出,就不能以任何方式改变。 提交的任何内容都不能更改:创建者的姓名不能更改,日期和时间戳不能更改,快照也不能更改,其父提交的原始哈希 ID 也不能更改。所以git filter-branch不这样做。
相反,它所做的是提取每个提交(从一些提交中——在你的情况下,你希望这个集合是所有提交),一次一个,然后在提取的提交上运行一些任意的、用户指定的命令. 不管这样做, filter-branch 然后从结果中进行新的提交。
如果新提交与原始提交完全 100% 完全相同,则实际上会重新使用原始提交。否则,它会使用新的不同哈希 ID 进行新提交。
一旦您进行了新的不同提交,随后的每个提交通常至少会略有不同:它将具有不同的父项。filter-branch 工具会为您处理这个再生育过程。所以它所做的两项艰巨的工作是:
剩下的艰巨工作当然是编写和运行过滤器。那个,过滤分支留给你。
在--tree-filter可能是最容易使用的过滤器,因此是一个你想要的。值得一提的是,这--index-filter要快得多——但如果你的工作是以某种方式修改每个提交中的快照,那么它就更难使用了。过滤分支有很多的筛选选项,因为 --tree-filter是最慢的过滤器,并因为它只是改变了良好的快照。例如,--msg-filter可以在每次提交中编辑或替换消息文本。但是,只要您想clang-format遍历每个快照中的所有文件,请坚持使用--tree-filter.
让我们简要地看看这在实践中是如何工作的,从一个只有三个提交的示例开始。这三个提交有大而难看的哈希 ID,但为了简单起见A,我们将它们称为B、 和C。你开始:
A <-B <-C <-- master
Run Code Online (Sandbox Code Playgroud)
分支名称master包含 commit 的哈希 ID C,以便我们(和 Git)可以看到哪个是最后一次提交。CommitC本身持有 commit 的 hash ID B,commitB持有commit的 hash ID A,这样 Git 就可以从最后一次提交到第一次向后工作。CommitA没有父级,因为它是第一个,所以这让 follow-everything-backwards 动作停止。
要运行,git filter-branch您可以使用:
git filter-branch --tree-filter '<command to run>' -- master
Run Code Online (Sandbox Code Playgroud)
最后的东西master——是filter-branch当它列出它应该操作的所有提交时你想要使用的分支名称。也就是说,它将开始master并向后工作,直到它不能再向后退。然后它将复制这些提交中的每一个,应用过滤器,然后重新提交。完成后,它将更新的一个分支名称是master.
Using--all告诉它从每个分支开始(以及标记和其他引用——这可能会在stashref上出现错误行为,有时--branches --tags可能会更好,但--all至少是传统的)。我们稍后也会回到这个--tag-name-filter选项。现在让我们一起去master。
在--以前master是你把分支名称从选项,其中一些可能被认为类似于有效的分支名称其余部分分开。这就是全部:只是标记“过滤器选项结束,分支名称开始”的样板。
最后,让我们看看--tree-filter而不看如何编写树过滤器。这只是意味着:运行树过滤器。因此 filter-branch 会将每个提交提取到一个临时目录中,该目录只包含提交的文件。此临时目录没有.git子目录,也不是您的工作树。(它实际上是-d您传递的目录的子目录,或者默认情况下,是 filter-branch 创建的临时目录的子目录。)您的树过滤器应该:
例如,如果您想在每个文件中插入一个标题行,您可以使用:
find . -type f -print | xargs <command to insert header line in every file>
Run Code Online (Sandbox Code Playgroud)
您可以将此命令放入脚本中,以便在使用前进行测试。如果clang-format有正确的选项(它可能会这样做),您可能根本不需要脚本,只需指定:
--tree-filter 'clang-format <options>'
Run Code Online (Sandbox Code Playgroud)
但无论哪种方式,filter-branch 都会使用内置的 shellexec来运行树过滤器。因此,您必须确保您的命令由有效的 shell 命令组成,并且其中没有returnor exitshell 命令(至少在没有首先生成子 shell 的情况下不会)。如果您要运行的命令是您编写的脚本,请确保可以通过 找到此脚本$PATH,或提供脚本的完整路径名:
--tree-filter "sh $HOME/scripts/filter-script.sh"
Run Code Online (Sandbox Code Playgroud)
例如。
让我们假设提交中A有一个文件,README.md. 让我们假设提交B添加了一个foo.cc将被重新格式化的新文件,并且该提交C修改README.md而foo.cc根本没有改变。您的过滤器仅更改任何.cc和.h文件,而不更改README.md. 因此,首先, filter-branch 本身枚举所有提交,将它们按适当的顺序排列:A, then B, then C, 在这种情况下。
现在的树过滤操作:
A;README.md;由于您的命令不涉及README.md,因此新提交与原始A. Filter-branch 因此重新使用原始 commit A。
现在 filter-branch 移动到 commit B。它将B的两个文件提取到(现在是空的)临时目录中并运行您的命令。这一次你的命令改变了foo.cc,但它仍然保持README.md原样。所以现在 filter-branch 使用修改后的foo.cc. 重新使用原始提交的作者姓名和电子邮件等保留原始元数据,但现在快照已更改,因此现在我们获得了一个新的不同哈希 ID,我们将调用B':
A--B--C <-- [original master]
\
B' [in progress]
Run Code Online (Sandbox Code Playgroud)
Filter-branch 现在转移到 commit C。它将所有文件提取到(重新清空的)临时目录中,因此您拥有相同的两个文件。您的过滤器现在foo.cc以与对 commit 内容进行操作时相同的方式进行修改B。Filter-branch 进行新的提交。新提交的快照有一个修改的foo.cc和相同README.md的C- 新的foo.cc匹配,而B'不是 -并且它有一个新的父级,B', 而不是B:最后一部分是 filter-branch 为你处理的。所以现在我们有:
A--B--C <-- [original master]
\
B'-C' [in progress]
Run Code Online (Sandbox Code Playgroud)
在这一点上,我们已经用完了要复制的提交,所以 filter-branch 做了最后几个技巧:
如果存在指向现有提交的标签,并且您指定了 a --tag-name-filter,则 Git 会生成指向这些现有提交副本的新标签。任何指向的标签A都可以保持不变,但是如果一个标签指向B,filter-branch 会将它复制到一个指向 的新标签B';如果一个标签指向C,filter-branch 会将它复制到一个指向 的新标签C'。这些新标签的名称来自--tag-name-filter:旧名称进入过滤器,出来的是新标签名称。
如果你没有标签,这一切都无关紧要。
然后,对于您在命令行的分支部分中命名的每个分支,filter-branch 将最后复制的提交的哈希 ID 存储到该分支中。所以在这里, filter-branch 将名称设置master为指向C'.
如果出现任何问题,filter-branch 会将所有原始分支和标签名称复制到refs/original/: 旧的 master 变成refs/original/refs/heads/master. 如果一切顺利,您最终会想扔掉这些refs/original/名字。
上面的最终绘图将是:
A--B--C <-- refs/original/refs/heads/master
\
B'-C' <-- master
Run Code Online (Sandbox Code Playgroud)
正如 Schwern 的回答一样,如果一切都出现严重错误,您可能希望能够恢复。一种方法是在存储库的副本(例如,克隆)上运行 filter-branch ,而不是在原始存储库上运行。另一种方法是注意你总是可以强制所有更新的引用恢复到它们保存时的方式refs/original/(但这通常需要一些编程)。
| 归档时间: |
|
| 查看次数: |
1061 次 |
| 最近记录: |