我现在已经完成了我的一个小库。在我开始使用它时,我不知道clang-format。现在我想用它来格式化整个代码库。我知道这会破坏其他人的代码库,因为提交哈希值会发生变化。然而,由于还没有人使用我的库,所以这对我来说没有问题。
那么,我需要怎么做才能在历史记录中的每个提交上运行clang-format呢?
我现在已经完成了我的一个小库。在我开始使用它时,我不知道clang-format。现在我想用它来格式化整个代码库。我知道这会破坏其他人的代码库,因为提交哈希值会发生变化。然而,由于还没有人使用我的库,所以这对我来说没有问题。
那么,我需要怎么做才能在历史记录中的每个提交上运行clang-format呢?
Git带有一个git filter-branch
命令,这是一种帮助处理此类任务的工具。请注意,git filter-branch
本身不会完成任务,它只是一个你可以使用的工具,以使你能够完成任务。你仍然需要编写自己的命令。最终使用的命令可能是:
git filter-branch --tree-filter '<some command here>' --tag-name-filter cat -- --all
有一个基本问题:一旦提交,就不能以任何方式进行修改。提交的任何内容都不能更改:不是制作它的人的姓名,也不是日期和时间戳,不是快照,也不是其父提交的原始哈希 ID。因此git filter-branch
并没有修改提交。
它实际上是从一组提交中(在您的情况下,您希望这个集合包括所有的提交)提取每个提交,然后对提取的提交运行一些任意用户指定的命令。无论它做了什么,filter-branch都会用结果生成一个新的提交。
如果新提交与原提交完全,完全,100%按位相同,这实际上重新使用了原提交。否则,它将创建一个具有不同哈希值的新提交。
一旦创建了一个新的、不同的提交之后,每个随后的提交通常都会稍微有所不同:它将具有不同的父提交。filter-branch工具会为您处理这个重置父提交过程。因此,它的两个主要任务是:
剩下的难事当然是编写和运行筛选器。这个任务由你来完成。
--tree-filter
可能是最容易使用的筛选器,因此它也是您想要的筛选器。值得一提的是,--index-filter
速度更快,但如果您的任务是以某种方式修改每个提交中的快照,则很难处理。filter-branch有很多过滤选项,因为--tree-filter
是最慢的过滤器且仅适用于更改快照。例如,--msg-filter
可以编辑或替换每个提交中的消息文本。只要您想在每个快照中的所有文件上运行clang-format
,就坚持使用--tree-filter
。
让我们简要地看一下这如何实际操作,在这个例子中只有三个提交。这三个提交具有庞大丑陋的哈希 ID,但出于简单起见,我们将它们称为A
、B
和C
。您从以下内容开始:
A <-B <-C <-- master
master
持有提交C
的哈希ID,以便我们(和Git)可以看到哪个是最后的提交。提交C
本身持有提交B
的哈希ID,提交B
持有提交A
的哈希ID,因此Git可以从最后一次提交向后工作到第一个提交。提交A
没有父级因为它是第一个,所以这让追溯每个提交的操作停止。git filter-branch
,您可以使用:git filter-branch --tree-filter '<command to run>' -- master
master
是你想要filter-branch
在列出所有应该操作的提交时要使用的分支名称。也就是说,它将从master
开始向后工作,直到无法再向后前进为止。然后,它将复制每个提交,应用过滤器,并重新提交。完成后,它将更新一个分支名称,即master
。--all
选项告诉它从每个分支(和标签和其他引用)开始(这可能会在stash
引用上出问题,有时--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>
为了方便测试,您可以将此命令放入脚本中。如果clang-format
具有正确的选项(它很可能有),则您可能根本不需要脚本,只需指定:
--tree-filter 'clang-format <options>'
但无论如何,filter-branch将使用shell的内置exec
来运行tree-filter。因此,您必须确保您的命令由有效的shell命令组成,并且没有return
或exit
shell命令(至少不要在没有首先生成子shell的情况下使用)。如果您要运行的命令是您编写的脚本,请确保可以通过$PATH
找到此脚本,或者提供脚本的完整路径名称:
--tree-filter "sh $HOME/scripts/filter-script.sh"
假设提交 A
中有一个文件 README.md
。假设提交 B
添加了一个新的 foo.cc
文件,需要进行重新格式化,并且提交 C
修改了 README.md
但完全未更改 foo.cc
。您的过滤器仅更改任何 . cc
和.h
文件,而不是README.md
。因此,首先,filter-branch枚举所有提交,并将它们放在适当的顺序中:在本例中为A
,然后B
,然后C
。
树形过滤器现在执行以下操作:
A
;README.md
;由于您的命令没有涉及README.md
,因此新提交与原始的A
完全相同,100%,一模一样。因此 filter-branch 重用原始提交A
。
现在 filter-branch 转到提交B
。它将B
的两个文件提取到(现在为空的)临时目录中,并运行您的命令。这次,您的命令更改了foo.cc
,但仍然保持README.md
不变。因此,filter-branch 现在创建了一个包含修改后foo.cc
的新提交。重新使用原始提交的作者姓名、电子邮件等信息保留了原始元数据,但是现在快照已更改,因此我们获得了一个新的和不同的哈希 ID,我们将其称为B'
:
A--B--C <-- [original master]
\
B' [in progress]
Filter-branch现在转到提交C
。它将所有文件提取到(重新清空的)临时目录中,所以您拥有相同的两个文件。您的筛选器现在以与操作提交B
的内容相同的方式修改foo.cc
。 Filter-branch创建一个新的提交。新提交的快照具有修改后的foo.cc
和与C
中相同的README.md
- 新的foo.cc
与B'
中的匹配 - 并且它具有新的父级B'
而不是B
:这是filter-branch为您处理的最后一部分。因此,我们现在有:
A--B--C <-- [original master]
\
B'-C' [in progress]
--tag-name-filter
,Git会创建一个新的标签,该标签指向那些现有提交的副本。指向A的标签可以不动,但是如果标签指向B,则filter-branch会将其复制到一个新标签,该标签指向B';如果标签指向C,则filter-branch将其复制到一个新的标签,该标签指向C'。这些新标签的名称来自于--tag-name-filter
:将旧名称输入过滤器,输出的是新的标签名称。refs/original/
:旧的master成为refs/original/refs/heads/master
。如果一切顺利,您最终会想要放弃refs/original/
名称。A--B--C <-- refs/original/refs/heads/master
\
B'-C' <-- master
就像Schwern的答案所说,你可能希望能够在一切都出现严重问题时进行恢复。一种方法是在存储库的副本(例如克隆)上运行filter-branch,而不是在原始存储库上运行。另一种方法是注意到您始终可以强制将所有更新过的引用退回到保存在refs/original/中的状态(但这通常需要一定的编程技巧)。
clang-format -i file
调用clang-format。我猜想根据您的示例,这将是find . -type f -print | xargs clang-format -i $1
,对吗?此外,clang-format在当前目录中搜索.clang-format
文件。我猜测在这个临时目录中找不到它...是否有任何保证临时目录会被创建在哪里? - jan.sendefind . -type f
,或者是 find . \( -name '*.cc' -o -name '*.h' \)
。如果你的文件名中有空格,你可能还需要使用 -print0
和 xargs -0
。至于临时目录,你可以使用 -d
将其指定到某个位置,或者利用一些未记录的特性(因此可能会更改):如果你不使用 -d
,那么临时目录将位于 .git
目录下,这意味着 clang 可能会在你的工作树中找到 .clang-format
文件。 - torekfind
和 xargs
的文档,因为没有 $1
参与。如果您使用的是 Windows 操作系统,则可能没有这些程序,请参阅 https://dev59.com/C14b5IYBdhLWcg3wnS0e。 - torekgit filter-branch --tree-filter your_rewrite_command --all
your_rewrite_command
,并使用结果重写提交。git-filter-branch
之前进行测试。使用git ls-files
获取提交中所有文件的列表,并对每个文件运行clang-format
。