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

时间:2017-10-30 19:45:44

标签: git merge branch code-formatting

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

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

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

2 个答案:

答案 0 :(得分:7)

具有假设的配方

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

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

选择运行过滤器的位置;在这里,我将其设置为" clean"仅过滤。

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

[filter "my-xyz-language-formatter"]
    clean = ~/Downloads/android-studio/bin/format.sh
    smudge = cat

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

然后,如果需要,创建一个.gitattributes文件。它将应用于您在其中创建的目录以及所有子目录,除非在这些子目录中被覆盖,因此将其放在最合理的位置,通常是存储库的根目录,但有时位于source/下面或src/或任何目录。添加行以通过格式化程序直接匹配某些模式的文件。我们在此假设所有名为*.xyz的文件都应格式化:

*.xyz   filter=my-xyz-language-formatter

此过滤器现在将应用于*.xyz个文件的所有提取和插入。 The gitattributes documentation谈到这些在退房和登记入住时适用,但这并不完全正确。相反,只要Git从工作树复制到索引(基本上是git add - 在git commit之前,就会应用 clean 过滤器,除非您使用git commit -a或类似的标志)。只要Git从索引复制到工作树(基本上是git checkout,而且还有一些其他情况,例如git reset --hard),就会应用涂抹过滤器。

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

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

描述(见下文)

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

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

(因为你可以接管整个合并过程,你可以编写一个更聪明的合并,使用语义分析。但这是非常困难的。我所知道的唯一系统就是这样做的,或接近这一点的是Ira Baxter的商业软件,而我从未真正使用过它;我只是理解它背后的理论。)

一种不依赖于让Git变得更聪明的解决方案。如果您有一个输出格式一致的语义分析器,无论输入形式如何,您都可以为基础 L 对于左侧或本地或--ours R 用于右侧或远程或其他或--theirs - 进入此格式化程序:

reformat < B > B.formatted
reformat < L > L.formatted
reformat < R > R.formatted

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

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

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

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

完成所有设置后,即可运行git merge -X renormalize。 Git将像往常一样提取 B L R 版本,然后通过&#34;虚拟结账运行它们签入&#34;步骤,进行三次临时提交, 1 B.formatted 等等。然后它使用三个临时提交进行合并,而不是从原来的三个提交进行。

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

1 从技术上讲,它只是制作树对象;没有必要进行实际提交。

答案 1 :(得分:1)

虽然torek可能让我走上了良好的轨道,但它并没有帮助我在分支机构之间完成重新格式化。问题是在git添加了这些

之后应用了过滤器
<<<< HEAD
bla foo 123
====
bla 123
>>>> otherBranch

阻止,因此过滤器会缩进冲突标记...这不好。

虽然这可能有一些解决方案,但我选择了自定义合并工具:

#!/bin/bash

BASE=$1
LOCAL=$2
REMOTE=$3
MERGED=$4

if echo "$BASE" | grep -q "\.java"; then
    echo "Normalizing java file";
    astyle $BASE
    astyle $LOCAL
    astyle $REMOTE
    astyle $MERGED
fi


meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"

.gitconfig中配置为:

[merge]
    tool = customMergeTool
[mergetool "customMergeTool"]
    cmd = /path/to/customMergeTool.sh \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"

通过我的方法,git仍会检测到在我的脚本中处理的冲突在我的100个案例中有40个没有合并冲突,因此torek的方法可能会加快那里的速度但是我遇到了合并其他40个文件的严重问题,所以我暂时放弃了。