
同意,当项目中的源代码看起来美观且一致时,它非常好用。 这有助于他的理解和支持。 我们向您展示并告诉您如何使用
clang-format ,
git和
sh来实现源代码格式。
格式化问题以及如何解决
在大多数项目中,有一些设计代码的规则。 如何确保所有参与者都执行它们? 特殊程序可以挽救( clang格式,astyle,unrustify),但是它们有其缺点。
格式化程序的主要问题是它们会更改整个文件,而不仅仅是更改行。 我们将使用ClangFormat作为开发电子固件的项目之一,其中以C ++为主要语言,来告诉您如何处理。 团队中有几个人在工作,因此提供统一的代码风格对我们很重要。 我们的解决方案不仅适用于C ++程序员,而且适用于使用C,Objective-C,JavaScript,Java,Protobuf编写代码的人员。
对于格式化,我们使用了clang-format-diff-6.0 。 首先,他们开始了团队
git diff -U0-无颜色| clang-format-diff-6.0 -i -p1 ,但是存在问题:
- 该程序仅通过扩展名确定文件类型。 例如,带有ts扩展名的文件(我们使用xml格式)被视为JavaScript,并且在格式化时崩溃。 然后,由于某种原因,她试图修复Qt项目的pro-file,可能类似于Protobuf。
- 在将文件添加到git索引之前,必须手动启动该程序。 很容易忘记它。
解决方案
结果是以下sh脚本,作为
预提交运行-钩住git:
#!/bin/sh CLANG_FORMAT="clang-format-diff-6.0 -p1 -v -sort-includes -style=Chromium -iregex '.*\.(cxx|cpp|hpp|h)$' " GIT_DIFF="git diff -U0 --no-color " GIT_APPLY="git apply -v -p0 - " FORMATTER_DIFF=$(eval ${GIT_DIFF} --staged | eval ${CLANG_FORMAT}) echo "\n------Format code hook is called-------" if [ -z "${FORMATTER_DIFF}" ]; then echo "Nothing to be formatted" else echo "${FORMATTER_DIFF}" echo "${FORMATTER_DIFF}" | eval ${GIT_APPLY} --cached echo " ---Format of staged area completed. Begin format unstaged files---" eval ${GIT_DIFF} | eval ${CLANG_FORMAT} | eval ${GIT_APPLY} fi echo "------Format code hook is completed----\n" exit 0
该脚本的作用:
GIT_DIFF =“ git diff -U0 --no-color” -输入
clang-format-diff-6.0的代码更改
。- -U0 :通常git diff显示所谓的“上下文”:围绕已更改的代码的几行未更改的代码。 但是clang-format-diff-6.0也会格式化它们! 因此,在这种情况下不需要上下文。
CLANG_FORMAT =“ clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex'。* \。(Cxx | cpp | hpp | h)$'” -用于格式化通过标准接收的diff的命令输入。
- clang-format-diff-6.0 - clang-format-6.0软件包中的脚本。 还有其他版本,但是所有测试仅在此版本上进行。
- -p1来自文档中的示例,提供了与git diff输出的兼容性。
- -style = Chromium-现成的代码格式样式预设。 其他可能的值: LLVM,Google,Mozilla,WebKit 。
- -sort-includes-用于按字母顺序对#include指令进行排序的选项(可选)。
- -iregex'。* \。(cxx | cpp | hpp | h)$'是一个正则表达式,用于按扩展名过滤文件名。 这里仅列出那些需要格式化的扩展名。 这样可以避免程序掉落和意外故障。 该列表很可能需要在新项目中进行补充。 除C ++外,您还可以格式化C / Objective-C / JavaScript / Java / Protobuf 。 尽管我们没有测试这些类型的文件。
GIT_APPLY =“ git apply -v -p0-” -将上
一条命令发布的补丁应用于代码。
- -p0 :默认情况下, git apply会跳过文件路径中的第一个组件,这与clang-format-diff-6.0生成的格式不兼容。 在此禁用了跳过。
FORMATTER_DIFF = $(已转换| eval $ {GIT_DIFF} –
eval $ {CLANG_FORMAT}) -索引的格式化程序更改。
回声“ $ {FORMATTER_DIFF}” | eval $ {GIT_APPLY} --cached在索引中格式化源代码(在
git add之后 )。 不幸的是,在将文件添加到索引之前,没有钩子起作用。 因此,格式化分为两个部分:格式化索引中的内容和不添加到索引中的内容。
评估$ {GIT_DIFF} | 评估$ {CLANG_FORMAT} | eval $ {GIT_APPLY} -索引中未包含代码格式(仅在索引中已格式化某些内容时才开始)。 通常,格式化项目中所有当前的更改(在版本控制下),而不仅限于上一步。 乍看之下,这是一个有争议的决定。 但是事实证明很方便,因为 迟早也需要格式化其他更改。 您可以使用
-i选项替换
“ | eval $ {GIT_APPLY}” ,这将强制
$ {CLANG_FORMAT}自己更改文件。
工作示范
- 安装clang-format-6.0
- cd / tmp && mkdir temp_project && cd temp_project
- git初始化
- 添加版本控制并提交任何名称为错误.cpp的 C ++文件。 最好> 50行未格式化的代码。
- 制作上面显示的.git / hooks / pre-commit脚本。
- 分配运行脚本的权限(对于git): chmod + x .git / hooks / pre-commit 。
- 手动运行脚本.git / hooks / pre-commit ,它应该与消息“ Nothing to be formatted”一起运行,并且没有解释器错误。
- 创建具有内容int main(){for(int i = 0; i <100; ++ i){std :: cout <<“ First case” << std :: endl;的file.cpp std :: cout <<“第二种情况” << std :: endl; std :: cout <<“第三例” << std :: endl; }}的一行或另一种格式错误。 最后-换行!
- git add file.cpp && git commit -m“ file.cpp”应该是来自脚本的消息,例如“ Patch file.cpp应用无错误 。 ”
- git log -p -1应该显示添加了格式化文件。
- 如果file.cpp真正格式化了提交,则只能在diff中测试格式化。 更改错误.cpp行,以便格式化程序响应它们。 例如,在代码中添加缩进不足以及其他更改。 git commit -a -m“仅格式化差异”应填写格式化的更改,但不影响文件的其他部分。
缺点与问题
git diff --staged (这里是
$ {GIT_DIFF} --staged )仅对添加到索引中的文件进行比较。
clang-format-diff-6.0可以访问其外部完整版本的文件。 因此,如果您更改文件,请先执行
git add ,然后再更改同一文件,然后
clang-format-diff-6.0将生成一个补丁,用于基于其他文件格式化代码(在索引中)。 因此,最好不要在
git add之后和提交之前编辑文件。
这是此类错误的示例:
- 将额外的std :: endl添加到file.cpp中 , “第二种情况” 。 (std :: cout <<“第二种情况” << std :: endl << std :: endl;)和在行前的一些额外缩进的选项卡。
- git添加file.cpp
- 用“第一种情况”清除该行(在同一文件中),以便仅将换行符保留在其位置(!)。
- git commit -m“提交时发生格式化程序错误” 。
该脚本应报告
“错误:搜索时:” ,即
git apply找不到
clang-format-diff-6.0发行的补丁的上下文。 如果您不明白这里是什么问题,只需在
git添加文件之后和
git commit之前不要更改文件。 如果需要更改,可以先提交(无需推送),然后
git commit-用新的更改进行修改。
最严重的限制是每个文件末尾都必须有一个换行符。 这是旧的git功能,因此大多数代码编辑器都支持在文件末尾自动插入这种翻译。 如果没有此功能,该脚本将在提交新文件时崩溃,但不会造成任何危害。
极少数情况下, clang-format-diff-6.0会不适当地格式化代码。 在这种情况下,您可以在代码中添加一些无用的元素,例如分号。 或者,在有问题的代码周围加上注释, / * clang-format关闭* /和/ * clang-format打开* / 。
另外, clang-format-diff-6.0可能会产生补丁不足。 这最终导致git apply不接受它,并且提交部分的代码保持不变。 原因是在clang-format-diff内部。 没有时间了解所有程序错误。 在这种情况下,您可以使用命令git diff -U0 --no-color HEAD ^ |查看格式补丁。 clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex'。* \。(cxx | cpp | hpp | h)$' 。 最简单的解决方案是将-i选项添加到上一个命令。 在这种情况下,该实用程序将不会发出补丁,而是会格式化代码。 如果这样做没有帮助,您可以尝试完全格式化单个文件的格式clang-format-6.0 -i -sort-includes -style = Chromium file.cpp 。 接下来是git add file.cpp和git commit --amend 。
假设您的.clang-format配置越接近预设值之一,您看到的此类错误就越少。 (在此将其替换为-style = Chromium选项)。
侦错
如果要查看脚本将对当前编辑(不在索引中)进行哪些更改,请使用
git diff -U0 --no-color | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex'。* \。(cxx | cpp | hpp | h)$'例如,您还可以检查脚本在最新提交中的工作方式,在30点:
git filter-branch -f --tree-filter“ $ {PWD} /。git / hooks / pre-commit” --prune-empty HEAD〜30..HEAD 。 该命令应该已经格式化了先前的提交,但是实际上只有它们的ID更改了。 因此,值得在项目的单独副本中进行此类实验! 之后她变得无法使用。
结论
从主观上讲,这样的决定胜于伤害。 但是您需要使用代码样式的配置来测试项目代码上不同版本的
clang-format-diff的行为。
不幸的是,我们没有为Windows执行相同的git-hook。 在评论中建议如何在那里做。 并且,如果您需要快速使用
clang-format的文章 ,建议您查看
ClangFormat描述 。