【问题标题】:How can I format the code in a multi-branch project?如何格式化多分支项目中的代码?
【发布时间】:2018-04-11 21:28:41
【问题描述】:

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

  • 在master中格式化代码,合并到特性分支:3路合并工具meld给了我上面提到的混乱。不检测函数边界。合并真的没有乐趣。
  • 在 master 中格式化代码,在 feature 分支中格式化代码,合并 master:现在我仍然得到 30 个更容易解决的冲突文件

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

【问题讨论】:

    标签: git merge branch code-formatting


    【解决方案1】:

    带有假设的配方

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

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

    同时选择运行过滤器的位置;在这里,我只将其设置为“干净”过滤器。

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

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

    (假设运行 cat 运行一个过滤器,该过滤器将其未更改的输入写入其标准输出;在任何类 Unix 系统上都是如此)。

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

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

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

    请注意,为每个文件启动一个过滤器可能会很慢。如果您对过滤器有很多控制权,则可以使用“长时间运行的过滤器进程”协议,这可以加快速度(尤其是在 Windows 上)。不过,这超出了此答案的范围。

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

    说明(见下文)

    Git 本身在这里只提供部分帮助。

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

    (由于您可以接管整个合并过程,您可以编写一个更智能的合并,确实使用语义分析。不过,这非常困难。我所知道的唯一系统可以做到这一点,或者接近这个的东西,是 Ira Baxter 的商业软件,我从来没有真正使用过它;我只是理解它背后的理论。)

    一个不依赖于让 Git 更智能的解决方案。如果您有一个输出格式一致的代码的语义分析器,无论 input 形式如何,您都可以提供所有三个版本 - B 用于 base,L 用于左侧或本地或--oursR 用于右侧或远程或其他或--theirs——进入此格式化程序:

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

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

    这个合并的结果当然会被重新格式化。但大概这就是你想要的。

    使用 Git 的内置工具实现此目的的方法是使用它所谓的 smudgeclean 过滤器。当文件从存储库中提取到工作树中时,会将涂抹过滤器应用于文件。每当文件从工作树进入存储库时,都会对文件应用干净的过滤器。

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

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

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


    1从技术上讲,它只是制作树对象;不需要实际的提交。

    【讨论】:

    • 我不知道涂抹和清洁过滤器!感谢那。您提出的过程基本上是我在合并变得丑陋时已经手动完成的过程。知道如何自动化这太棒了。希望有更好的解决方案,我暂时不会授予这个答案。获得全格式状态真的很有帮助。
    • 我认为要获得完整的答案,配置应该更加充实。所以假设我的格式脚本是~/Downloads/android-studio/bin/format.sh,我应该把它放在哪里?
    • 当然 - 这有点复杂,我将假设该路径以及其他一些项目,并尝试概述所有假设。
    • 好的,Android Studio 的format.sh 不仅不是流媒体,而且很奇怪。 astyle$ astyle &lt; file 语法一起使用时是流式传输的。 uncrustify 带有零样式。没有标准吗?我错过了一些明显的东西吗?我的目标是主要是 Java 和 Kotlin 的最标准格式。
    • 我既不使用 Java 也不使用 Kotlin,也不知道它们有任何格式化程序。对于其他人来说,问题不在于 a 标准,而在于 which 标准。 :-) xkcd.com/927
    【解决方案2】:

    虽然 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\"
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-30
      • 2020-02-27
      • 2011-07-14
      • 1970-01-01
      • 2014-07-18
      • 2016-07-04
      • 2011-04-13
      相关资源
      最近更新 更多