如何在 Powershell 中正确生成和应用 git 补丁(对比 bash)?

How to generate and apply git patches correctly in Powershell (vs bash)?

请注意以下简短场景(在 Powershell 中):

PS> git diff -U3 -r -M HEAD -- .\Metadata\LegacyTypeModules\xyz.Web.Main.draft.json | Out-File -Encoding ascii c:\temp.diff

PS> git apply --cached C:\temp.diff
error: patch failed: Metadata/LegacyTypeModules/xyz.Web.Main.draft.json:69
error: Metadata/LegacyTypeModules/xyz.Web.Main.draft.json: patch does not apply

这失败了,因为文件中的最后一行没有以 CRLF 结尾:

但是,当 运行 in bash:

时,同样的命令也有效
$ git diff -U3 -r -M HEAD -- Metadata/LegacyTypeModules/xyz.Web.Main.draft.json > /c/Temp/2.diff

$ git apply --cached /c/Temp/2.diff

P11F70F@L-R910LPKW MINGW64 /c/xyz/tip (arch/1064933)

两个补丁的区别在于:

所以问题似乎发生了,因为 Powershell 使用 CRLF 终止了通过管道的每一行,而 bash 保留了原始行结尾。

我理解为什么会发生这种情况 - Powershell 对对象进行操作并且对象是字符串不包括 EOL 字符。写入文件时,Powershell 将对象转换为字符串(对于字符串,转换为 nop)并使用默认的 EOL 序列来分隔行。

是否意味着Powershell在EOL敏感场景下完全不能使用?

您可以尝试使用 join 将 CRLF 替换为 unix EOL:

(git command arguments . . .) -join "`n" | out-file c:\temp.diff -NoNewline

标准差异(也称为补丁)以 LF 行结尾终止行。那是因为那是 POSIX 为 diff 的输出指定的内容。所有行必须包含一个 LF 行结尾。

当 CR 在补丁中位于 LF 之前时,它被认为是要打补丁的内容的一部分。因此,您的情况下的补丁可能不适用,因为旧内容被列为具有 CRLF 行结尾,而事实并非如此。

不幸的是,PowerShell 在这方面完全崩溃了,它的管道破坏了通过它们的数据。对于任何类型的二进制数据尤其如此。如果您在 Unix 上使用任何类型的 运行 工具,例如 Git,您需要避免使用 PowerShell 的管道。

确实:

  • PowerShell 始终解码 外部程序的输出为文本(使用[Console]::OutputEncoding

  • 然后当线路可用时,它通过管道逐行发送解码输出。

  • 文件输出 cmdlet,例如 Out-File 然后总是使用 platform-native 换行序列 - Windows 上的 CRLF -在写入目标文件时终止每个(字符串化的)输入对象(使用 its 默认字符编码(或通过 -Encoding 指定的字符编码),这在技术上与编码无关用于解码外部程序输出)。

换句话说:PowerShell 管道(和重定向)支持通过原始二进制数据传递,从 PowerShell 7.2 开始 - 未来的原始数据支持正在 GitHub issue #1908 中讨论。

解决方法

  • 手动 加入并终止解码输出行与 LF 换行符 ("`n"),并按原样写入生成的多行字符串(-NoNewLine)到目标文件,如图.

  • 在这种简单的情况下,最容易委托给cmd.exe /c,因为cmd.exe的管道和重定向 原始字节管道 :

cmd /c @'
git diff -U3 -r -M HEAD -- .\Metadata\LegacyTypeModules\xyz.Web.Main.draft.json > c:\temp.diff
'@

请注意使用 here-string 以提高可读性和任何嵌入引用的便利性(此处为 n/a)。