更改文件名时补丁的奇怪行为

Weird behavior of patch when filename is changed

最近在学习diffpatch。我创建了两个文件,文件 a 内容 abc 和文件 b 内容 def。然后我使用diff -u a b > ppatch < p,它表现正确,如下所示:

[joe@joe-pc c]$ ls
a  b
[joe@joe-pc c]$ more a
abc
[joe@joe-pc c]$ more b
def
[joe@joe-pc c]$ diff -u a b > p
[joe@joe-pc c]$ more p
--- a   2018-12-20 22:56:33.865661540 +0800
+++ b   2018-12-20 22:54:15.241516269 +0800
@@ -1 +1 @@
-abc
+def
[joe@joe-pc c]$ patch < p
patching file a
[joe@joe-pc c]$ more a
def
[joe@joe-pc c]$ more b
def
[joe@joe-pc c]$ ls
a  b  p
[joe@joe-pc c]$

但是如果我将文件名从 a 更改为 ab,就会发生一些奇怪的事情。 patch < p 命令告诉我
patching file b Reversed (or previously applied) patch detected! Assume -R? [n]

[joe@joe-pc c]$ ls
ab  b
[joe@joe-pc c]$ more ab
abc
[joe@joe-pc c]$ more b
def
[joe@joe-pc c]$ diff -u ab b > p
[joe@joe-pc c]$ more p
--- ab  2018-12-20 22:57:29.767980973 +0800
+++ b   2018-12-20 22:54:15.241516269 +0800
@@ -1 +1 @@
-abc
+def
[joe@joe-pc c]$ patch < p
patching file b
Reversed (or previously applied) patch detected!  Assume -R? [n] ^C
[joe@joe-pc c]$

文件内容是一样的,为什么第二种情况patch找不到要打补丁的正确文件ab

以上操作是在一台Linux机器上执行的bash shell.

提前致谢。

这是 GNU 补丁的一个特性。由于您没有指定要修补的文件,因此必须以某种方式从输入中推断出它。基本上,如果您不指定路径(仅基本名称),它假定它必须用更短的名称修补文件,除非传递 --posix 命令行参数或设置 POSIXLY_CORRECT 环境变量:

patch --posix <p
# or
POSIXLY_CORRECT=1 patch <p

在您的情况下,在 ab 之间,第一个被正确选择,但是对于 abb,第二个被选为补丁目标(如 patching file b 行所示),但修补失败,因此出现错误。

您也可以通过明确指定补丁目标来修复此行为:

patch ab <p

深入研究文档

GNU 补丁使用以下逻辑(参见 patch's manual, "10.6 Multiple Patches in a File" section):

First, patch takes an ordered list of candidate file names as follows:

  • If the header is that of a context diff, patch takes the old and new file names in the header. ...

...

Then patch selects a file name from the candidate list as follows:

  • If some of the named files exist, patch selects the first name if conforming to POSIX, and the best name otherwise.

对于“uniform context format”,"old" 文件在 --- 之后提及(在您的情况下为 aab),并且 "new" 文件在 +++ 之后提到(在你的情况下是 b)。

如果两个文件都存在并且 patch 未配置为 "confirming to POSIX"(例如,通过设置 POSIXLY_CORRECT 环境变量或 --posix 命令行参数,请参阅 "10.12 patch and the POSIX Standard" section of the manual),然后 patch 将从两个名称中选择 "the best" 个名称。 "Name" 这里包括从补丁文件中获取的完整路径(在你的情况下无关紧要)。详情稍后详述:

To determine the best of a nonempty list of file names, patch first takes all the names with the fewest path name components; of those, it then takes all the names with the shortest basename; of those, it then takes all the shortest names; finally, it takes the first remaining name.

"Name component"这里基本上是一个folder/file名称(例如/foo/bar/baz有三个),"basename"只是文件的名称(baz). 因此,如果名称为 a(旧)和 b(新)并且两个文件都存在,则没有 "best name",因此第一个被修补。 但是,如果名称为 ab(旧)和 b(新)并且两个文件都存在,则 b 为 "better",因此该工具会尝试修补它并失败。

我不知道为什么将此行为设置为默认行为。