如何在多分支项目中格式化代码?

Gis*_*zmo 6 git merge branch code-formatting

所以我们有成千上万行代码git存储库,自从我2年前加入该项目以来,格式化让我感到困惑.它不仅会让我感到烦恼,而且随着开发人员随意"修复"这种情况,当代码格式仅应用于一侧时,合并会导致头痛.现在重新格式化代码是一个两分钟的任务,但也导致合并冲突地狱.我最近将master合并到一个长期存在的功能分支并尝试:

  • master中的格式代码,合并到功能分支:3路合并工具融合给了我上面提到的混乱.不检测功能边界.合并真的没有乐趣.
  • master中的格式代码,功能分支中的格式代码,merge master:现在我仍然可以获得30个更容易理解的冲突文件

现在我想知道它是否值得合并,因为还有另外15个分支都需要完全相同的代码审查,并且手动合并容易出错我想知道是否有某种方法可以做到这一点而不会出现这些合并冲突.

tor*_*rek 7

食谱与假设

(注意:我没有测试过这个)

我们假设重新格式化,~/Downloads/android-studio/bin/format.sh并且[注意:显然这是一个不好的假设!]它读取stdin并写入stdout,并且一次处理一个文件.(这可能,但非常困难,使用一次需要多个文件的东西.但是你不能在这种情况下使用这个配方.Git的基本过滤机制要求每个过滤器只读取stdin并写入stdout.默认情况下,Git假定过滤器有效,即使它以失败状态退出.)

选择运行过滤器的位置; 在这里我只将其设置为"干净"过滤器.

~/.gitconfig或中.git/config,添加过滤器的定义:

[filter "my-xyz-language-formatter"]
    clean = ~/Downloads/android-studio/bin/format.sh
    smudge = cat
Run Code Online (Sandbox Code Playgroud)

(这假设运行cat运行一个过滤器,向其stdout写入其未更改的输入;在任何类Unix系统上都是如此).

然后,.gitattributes根据需要创建文件.这将适用于你创建的目录,所有子目录,除非这些子目录重写,所以将其放置在最高合理的位置,库通常根,但有时下面一个source/src/或任何目录.添加行以通过格式化程序直接匹配某些模式的文件.我们假设所有命名的文件*.xyz都应格式化:

*.xyz   filter=my-xyz-language-formatter
Run Code Online (Sandbox Code Playgroud)

此过滤器现在将应用于所有*.xyz文件的提取和插入. gitattributes文档讨论了在签出和签入时应用这些内容,但这并不完全正确.相反,只要Git从工作树复制到索引(除了你使用或类似的标志之前,除了之前的-well),应用一个干净的过滤器.甲涂抹滤波器应用于从每当索引GIT中拷贝到工作树(基本上,但也有一些附加的情况下,例如).git addgit commitgit commit -agit checkoutgit reset --hard

请注意,为每个文件启动一个过滤器可能会非常慢.如果您对过滤器有很多控制权,可以使用"长时间运行的过滤器过程"协议,这可以加快速度(特别是在Windows上).但这超出了这个答案的范围.

git merge正常运行不使用过滤器(它适用于已在索引中的副本,这在过滤步骤之外).但是,添加-X renormalize到标准合并将使git merge下面描述的"虚拟签入和签出",以便它将应用筛选器.这种情况发生在合并中涉及的所有三个提交中(并且在两个方向上 - 清理和涂抹 - 因此它比仅一次提交慢大约6倍).

说明(见下文)

Git本身在这里只是部分有用.

从根本上说,问题是Git是愚蠢的,面向行的:它git diff从合并基础提交到每个提示提交.如果这些中git diff的一个或两个看到很多格式更改,它会认为那些重要且值得应用于基础.它没有输入代码的语义知识.

(既然你可以接管整个合并过程中,你可以写一个聪明的合并是使用语义分析,这是相当困难的,但我知道的是,只有系统这样做,或做到接近这一点,是艾拉巴克斯特的商业软件,我从来没有真正使用过它;我只是理解它背后的理论.)

还有就是不依赖于制作的Git聪明的解决方案.如果您有一个语义分析器输出一致格式化的代码,无论输入形式如何,您都可以提供所有三个版本 - B为base,L为left或local --ours,以及R为right或remote或其他或--theirs-into此formatter:

reformat < B > B.formatted
reformat < L > L.formatted
reformat < R > R.formatted
Run Code Online (Sandbox Code Playgroud)

现在你可以让Git合并所有三个格式化版本,而不是合并原始可能尚未格式化(但可能是格式化)的版本.

当然,这种合并的结果将被重新格式化.但无论如何,这可能是你想要的.

使用Git的内置工具实现这一目标的方法是使用它所谓的涂抹清洁过滤器.当文件从存储库中提取到工作树中时,将对文件应用污迹过滤器.无论何时从工作树进入存储库,都会对文件应用干净的过滤器.

在这种情况下,涂抹过滤器可以"对数据不做任何事情",准确保留提交的内容.清洁过滤器可以是重新格式化器.或者,如果您愿意,涂抹过滤器可以是重新格式化器,清洁过滤器可以是重新格式化器,也可以是无操作过滤器.一旦你有了这个 - 这是你设置的.gitattributes,通过路径名定义特定文件的过滤器,.git/config或主要(用户或系统范围)中的过滤器驱动程序.gitconfig.

完成所有设置后,即可运行git merge -X renormalize.Git将像往常一样提取B,LR版本,然后通过"虚拟签出和签入"步骤运行它们,进行三次临时提交,1 B.formatted等等.然后它使用三个临时提交进行合并,而不是从最初的三个提交进行.

困难的部分是找到一个可以满足您想要/需要的重新格式化器.一些现代系统有它们,例如,gofmtclang-format.如果有一个可以满足您的需求,那么只需将所有这些联系在一起 - 并从您的团队的其他成员那里获得支持,这种重新格式化是一个好主意.


1从技术上讲,它只是制作树状物体; 没有必要进行实际的提交.