注意:这个特定的 StackOverflow 答案并不能解决您的问题(我确实无法正确解决它,因为我没有 Java 解析器)。这完全是关于您将遇到的所有其他绊脚石,以及如何避免它们,以便您的任务实际上只是与 Java 相关的部分。
需要注意的是,这里的每个文件都有三个副本:
- 你当前提交中的那个
HEAD:MyFile.java(使用git show HEAD:MyFile.java查看这个);
-
提议的下一次提交中的那个,
:MyFile.java(同样,使用git show 来查看);和
- 您的工作树中的那个
MyFile.java,您可以直接查看和编辑。
git diff 命令通常会选择 三个中的两个进行比较。
不带参数运行git diff,或只选择文件(不是提交)的参数,比较文件的index副本与工作树 复制。它不会提取当前提交的文件。索引副本是 git commit 将写入 new 提交的副本,因此它实际上是您现在提议提交的内容。
使用 git diff --cached 告诉 Git 将 HEAD 中的文件与索引中的文件进行比较。使用 git diff HEAD 告诉 Git 将 HEAD 中的文件与工作树中的文件进行比较。因此,这些是您选择要比较的文件的对 的方式。但无论如何,每个git diff 只选择一个pair 文件,或者如果让Git 比较所有文件,则选择一组文件。
如果您在此处运行git commit -a——我建议您不要——这大致相当于git add -u && git commit,除了它使用更新的文件构建临时索引。在这里的各种提交钩子中事情变得特别棘手,因为现在有多个不同的索引文件具有不同的建议下一个提交。这就是为什么我建议在这里避免使用git commit -a。处理和推理文件的三个副本已经够难的了,并且使用棘手的提交选项,例如-a或--only或--include会抛出第四个甚至有时第五组副本加入其中。
(Git 一次只能处理一个索引文件。标准git commit 只有一个标准索引文件。标准索引文件具有将或将进入下一次提交的文件的副本。1 这些选项导致 Git 创建额外的临时索引文件,在其中构建建议的新提交,然后在环境中设置 $GIT_INDEX_FILE 运行其余操作(包括您的挂钩)以使这些子-commands 查看要使用的临时索引。如果一切顺利并且git commit 最终做出新的提交,这些临时索引文件之一,根据选项和参数,任何内容都是适当的,成为新的索引,之后你回到正常情况,每个文件只有三个副本。)
由于您的计划是在预提交挂钩中工作,您可能应该将HEAD 文件与索引中建议提交的文件进行比较,即,您可能应该是在这里使用git diff --cached。但是,如果您打算通过计算机程序来执行此操作,而不是作为人类闲暇时阅读的东西,那么您根本不应该使用git diff。前端的git diff 命令是供人类 使用的,这就是它对输出进行分页和着色以及执行所有那些只会惹恼计算机程序的事情的原因。 Git 将这些花哨的前端称为瓷器命令。
每种git diff 都由后端管道 命令实现。将提交(技术上是树)与索引进行比较的管道命令是git diff-index,它仍然需要--cached 来告诉它进行所需的比较:git diff-index --cached HEAD 产生可预测的输出,这不取决于每个用户的首选寻呼机、颜色样式等。
(如果您专门为自己使用编写此钩子,则可以使用git diff 或git diff-index,因为您可以补偿您自己的个人git diff 设置。但从某种意义上说,这样做更好无论如何都要使用管道命令——那么就不需要补偿任何东西。)
无论您在此处选择什么,您仍然需要编写自己的代码来解释差异输出。相反,您可能会选择编写一个程序来简单地提取两个感兴趣的文件——HEAD:MyFile.java 和 :MyFile.java,即从当前提交和索引中提取,并在自己的程序中比较它们,而不是使用 @987654356 @ 一点也不。您可以使用git show 提取文件,但这有一个小缺陷,即它是另一个瓷器命令。您可以使用底层管道命令git cat-file -p 直接提取文件,而无需通过git show。
实际上解析 Java 代码将是最可靠的方法,这样您就不会被某种愚蠢的格式更改绊倒。一种更hacky的方法,例如假设除了某一特定形式的一行之外的所有内容都必须匹配,这在 awk 中并不太困难(一次读取两个文件一行,检查两个文件中只有一行不同文件并且它具有预期的形式)。所有这些似乎都比尝试解析 diff 输出更简单,但如果你想解析 diff 输出,非 Git 非上下文 diff 可能更简单。
最后,关于:
我的目标是通过预提交挂钩还原这些文件。
这是可以的(Git 会正确处理它,对于“正确”的一些定义),但对于许多 Git 用户来说,这也有点令人惊讶。像这样的 Git 钩子不应该改变东西。编写 Git 的人的目的是让像这样的 Git 挂钩仅验证事物。如果验证步骤失败,钩子应该退出非零值,这将导致git commit 停止。任何修复都应该通过一些非钩子操作来完成。
请注意,git commit --no-verify 会完全跳过预提交挂钩。
1从技术上讲,索引引用每个文件的只读副本。因为这些副本是只读的,所以它们可以共享。所以“复制”一个索引很便宜,因为它实际上只是复制了所有的引用。此外,提议的新提交中的每个文件与某个现有提交中已经存在的文件 100% 逐位相同,实际上只是对该文件的引用,因为存储在每个提交中的每个文件本身都是完全读取的-仅限。