[英]How can I format the code in a multi-branch project?
所以我们有几十万行代码的 git 存储库,自从我 2 年前加入这个项目以来,格式让我很头疼。 它不仅让我感到困扰,而且当开发人员随机“修复”格式时,当仅在一侧应用代码格式时,合并会导致头痛。 现在重新格式化代码是一个两分钟的任务,但也会导致合并冲突地狱。 我最近将 master 合并到一个长期存在的功能分支并尝试:
现在我想知道是否值得合并,因为还有另外 15 个分支都需要完全相同的代码审查,并且手动合并容易出错,我想知道是否有某种方法可以做到这一点而不会出现这些合并冲突。
我只是在增强下面 Rufus 评论的信号:
https://github.com/emilio/clang-format-merge包含提供合并驱动程序的代码,而不是清洁和涂抹过滤器。 不过,它看起来可能很有用,尤其是对于从未强制执行标准格式的存储库。
(注意:我没有测试过这些)
我们假设重新格式化程序位于~/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/
或src/
下或任何目录。 通过格式化程序将行添加到与某些模式匹配的定向文件。 我们在这里假设所有名为*.xyz
的文件都应该被格式化:
*.xyz filter=my-xyz-language-formatter
此过滤器现在将应用于*.xyz
文件的所有提取和插入。 gitattributes 文档讨论了这些在签出和签入时应用的内容,但这并不完全正确。 相反,每当 Git 从工作树复制到索引时,都会应用一个干净的过滤器(本质上, git add
——在git commit
之前,除非你使用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 更智能。 如果您有一个语义分析器输出格式一致的代码,无论输入形式如何,您都可以提供所有三个版本 - B表示基本, L表示左或本地或--ours
, R表示右或远程或其他或--theirs
——进入这个格式化程序:
reformat < B > B.formatted
reformat < L > L.formatted
reformat < R > R.formatted
现在您可以让 Git 合并所有三个格式化版本,而不是合并原始可能尚未格式化(但可能已格式化)的版本。
当然,此合并的结果将被重新格式化。 但大概这就是你想要的。
使用 Git 的内置工具实现此目的的方法是使用所谓的涂抹和清洁过滤器。 当文件从存储库中提取到工作树中时,会将涂抹过滤器应用于文件。 每当文件从工作树进入存储库时,都会对文件应用干净的过滤器。
在这种情况下,污迹过滤器可以“对数据不做任何事情”,准确地保留提交的内容。 干净的过滤器可以是重整器。 或者,如果您愿意,污迹过滤器可以是重新格式化器,而清洁过滤器可以是重新格式化器,或无操作过滤器。 一旦你有了这个——这是你在.gitattributes
中设置的东西,通过路径名为特定文件定义一个过滤器,在.git/config
或你的主(用户或系统范围) .gitconfig
中定义过滤器驱动程序。
完成所有设置后,您可以运行git merge -X renormalize
。 Git 将像往常一样提取B 、 L和R版本,然后通过“虚拟签出和签入”步骤运行它们,进行三个临时提交, 1 B.formatted等等。 然后它使用三个临时提交而不是原始的三个提交进行合并。
困难的部分是找到一个可以满足您想要/需要的重新格式化程序。 一些现代系统有它们,例如gofmt
或clang-format
。 如果有一个可以满足您的需求,那么只需将所有这些整合在一起,并获得团队其他成员的支持,这种重新格式化是一个好主意。
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 个文件时遇到了严重问题,所以我给了它现在。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.