如果您曾经参与过具有分布式开发模型的大型代码库,您可能听说过人们说“Sue 刚刚发送了一个补丁”或“Rajiv 正在检出 diff”。 也许这些术语对您来说是新的,您想知道它们是什么意思。 开源在这方面产生了影响,因为从 Apache Web 服务器到 Linux 内核的大型项目的主要开发模型在其整个生命周期中都是“基于补丁”的开发项目。 事实上,您是否知道 Apache 的名称源于针对原始 NCSA HTTPd 服务器源代码收集和整理的一组补丁?
您可能认为这是民间传说,但早期 Apache 网站的捕获声称该名称源自最初的“补丁”集合;因此是 APAtCHy 服务器,然后简化为 Apache。
但历史琐事就到此为止。 开发者谈论的这些补丁和 diff 到底是什么?
首先,为了本文的目的,让我们假设这两个术语指的是同一件事。“Diff”只是“difference(差异)”的缩写;同名的 Unix 实用程序揭示了一个或多个文件之间的差异。 我们将在下面看一个 diff 实用程序的示例。
“补丁”是指文件之间特定差异的集合,可以使用 Unix diff 实用程序将其应用于源代码树。 因此,我们可以使用 diff 工具创建 diff(或补丁),并使用 patch 工具将它们应用于同一源代码的未打补丁版本。 顺便说一句(并打破我不谈历史琐事的规则),“补丁”一词来自早期计算机时代物理覆盖穿孔卡片孔以进行软件更改,当时穿孔卡片代表计算机处理器执行的程序。 下图来自描述软件补丁的 维基百科页面,展示了最初的“打补丁”概念

现在您已经对补丁和 diff 有了基本的了解,让我们探讨一下软件开发人员如何使用这些工具。 如果您没有使用过像 Git 或 Subversion 这样的源代码控制系统,我将为大多数重要的软件项目是如何开发的奠定基础。 如果您将软件项目的生命周期视为时间线上的行动集合,您可能会将软件的更改(例如向源代码文件添加功能或函数或修复错误)可视化为出现在时间线上的不同点,每个离散点代表当时所有源代码文件的状态。 我们将使用当今最流行的源代码控制工具 Git 使用的相同命名法,将这些更改点称为“提交”。 当您想查看某个提交前后或多个提交之间的源代码差异时,可以使用工具向我们显示 diff 或差异。
如果您正在使用相同的源代码控制工具 Git 开发软件,您可能在本地系统中有想要提供给其他人以潜在地作为提交添加到他们自己的树中的更改。 向他人提供本地更改的一种方法是创建本地树更改的 diff,并将此“补丁”发送给其他正在处理相同源代码的人。 这让其他人可以修补他们的树,并查看应用了您的更改的源代码树。
Linux、Git 和 GitHub
这种共享补丁文件的模式是 Linux 内核社区今天如何运作关于提议的更改的。 如果您查看任何流行的 Linux 内核邮件列表的存档 - LKML 是主要的,但其他包括 linux-containers, fs-devel, Netdev,仅举几个 - 您会发现许多开发人员发布他们希望其他人审查、测试并可能在某个时候带入官方 Linux 内核 Git 树的补丁。 详细讨论 Linus Torvalds 编写的源代码控制系统 Git 超出了本文的范围,但值得注意的是,Git 实现了这种分布式开发模型,允许补丁与主存储库分开存在,推送到不同的树并从中拉取,并遵循其特定的开发流程。
在继续之前,我们不能忽略补丁和 diff 最相关的最流行的服务:GitHub。 鉴于它的名称,您可能可以猜到 GitHub 是基于 Git 的,但它围绕 Git 工具为分布式开源项目开发提供了基于 Web 和 API 的工作流程。 在 GitHub 中共享补丁的主要方式之一不是像 Linux 内核那样通过电子邮件,而是通过创建拉取请求。 当您在自己的源代码树副本上提交更改时,您可以通过针对该软件项目的常用共享存储库创建拉取请求来共享这些更改。 GitHub 今天被许多活跃和流行的开源项目使用,例如 Kubernetes, Docker, 容器网络接口 (CNI), Istio 和许多其他项目。 在 GitHub 世界中,用户倾向于使用基于 Web 的界面来查看构成拉取请求的 diff 或补丁,但您仍然可以访问原始补丁文件并在命令行中使用 patch 实用程序。
开始切入正题
现在我们已经介绍了补丁和 diff 以及它们如何在流行的开源社区或工具中使用,让我们看几个示例。
第一个示例包括源代码树的两个副本,其中一个副本具有我们想要使用 diff 实用程序可视化的更改。 在我们的示例中,我们将查看“统一”diff,因为这是现代软件开发世界中大多数补丁的预期视图。 查看 diff 手册页以获取有关选项和生成差异的方法的更多信息。 原始源代码位于 sources-orig 中,我们的第二个修改后的代码库位于名为 sources-fixed 的目录中。 要在终端中以统一 diff 格式显示差异,请使用以下命令
$ diff -Naur sources-orig/ sources-fixed/
...然后显示以下 diff 命令输出
diff -Naur sources-orig/officespace/interest.go sources-fixed/officespace/interest.go
--- sources-orig/officespace/interest.go 2018-08-10 16:39:11.000000000 -0400
+++ sources-fixed/officespace/interest.go 2018-08-10 16:39:40.000000000 -0400
@@ -11,15 +11,13 @@
InterestRate float64
}
+// compute the rounded interest for a transaction
func computeInterest(acct *Account, t Transaction) float64 {
interest := t.Amount * t.InterestRate
roundedInterest := math.Floor(interest*100) / 100.0
remainingInterest := interest - roundedInterest
- // a little extra..
- remainingInterest *= 1000
-
// Save the remaining interest into an account we control:
acct.Balance = acct.Balance + remainingInterest
diff 命令输出的前几行可能需要一些解释:三个 ---
符号显示原始文件名;原始文件中存在但在比较的新文件中不存在的任何行都将以单个 -
为前缀,以表明该行已从源文件中“减去”。 +++
符号显示相反的情况:比较的新文件和在此文件中找到的添加项用单个 +
符号标记,以表明它们是在新版本的文件中添加的。 每个“hunk”(这就是以 @@
为前缀的部分的名称)差异补丁文件都具有上下文行号,这些行号有助于 patch 工具(或其他处理器)知道在哪里应用此更改。 您可以从“Office Space”电影参考函数中看到,我们已经纠正(通过删除三行)了我们一位软件开发人员的贪婪,他向四舍五入的利息计算中添加了一点,并在我们的函数中添加了注释。
如果您希望其他人测试此树中的更改,您可以将 diff 的此输出保存到补丁文件中
$ diff -Naur sources-orig/ sources-fixed/ >myfixes.patch
现在您有了一个补丁文件 myfixes.patch,可以与另一位开发人员共享,以应用和测试这组更改。 如果您的同事开发人员的当前工作目录位于源代码树的根目录中,则可以使用 patch 工具应用更改
$ patch -p1 < ../myfixes.patch
patching file officespace/interest.go
现在,您的同事开发人员的源代码树已打上补丁,可以构建和测试通过补丁应用的更改。 如果这位开发人员单独对 interest.go 进行了更改怎么办? 只要更改没有直接冲突(例如,更改完全相同的行),patch 工具应该能够解决在哪里合并更改。 例如,在以下 patch 运行示例中使用了一个包含多个其他更改的 interest.go 文件
$ patch -p1 < ../myfixes.patch
patching file officespace/interest.go
Hunk #1 succeeded at 26 (offset 15 lines).
在这种情况下,patch 警告更改未在文件中的原始位置应用,而是偏移了 15 行。 如果您有大量更改的文件,patch 可能会放弃尝试查找更改的位置,但它确实提供了选项(在文档中附带必要的警告)来提高匹配的“模糊性”(这超出了本文的范围)。
如果您使用的是 Git 和/或 GitHub,您可能不会将 diff 或 patch 工具用作独立工具。 Git 提供了许多此功能,因此您可以使用内置功能在共享源代码树上工作,合并和拉取其他开发人员的更改。 一个类似的功能是使用 git diff 在本地树中或任何两个引用(提交标识符、标签或分支的名称等等)之间提供统一的 diff 输出。 您甚至可以创建一个补丁文件,让不使用 Git 的人可能会发现它有用,只需将 git diff 输出通过管道传输到文件,因为它使用 patch 可以使用的 diff 命令的确切格式。 当然,GitHub 将这些功能带入基于 Web 的用户界面,因此您可以在拉取请求中查看文件更改。 在此视图中,您会注意到它实际上是 Web 浏览器中的统一 diff 视图,GitHub 允许您将这些更改下载为原始补丁文件。
总结
您已经了解了什么是 diff 和补丁,以及与之交互的常用 Unix/Linux 命令行工具。 除非您是仍然使用基于补丁文件的开发方法(如 Linux 内核)的项目的开发人员,否则您将主要通过像 Git 这样的源代码控制系统来使用这些功能。 但了解许多开发人员每天通过 GitHub 等更高级别的工具使用的功能的背景和基础知识很有帮助。 谁知道呢 - 当您需要在 Linux 世界中处理来自邮件列表的补丁时,它们可能会在某一天派上用场。
1 条评论