您必须首先定义短语正确的补丁。什么使补丁正确?是什么让一个人不合适?就此而言,patch 和 diff 之间有什么区别?没有一个固定的答案,但请参阅Difference between patch and diff files。
也就是说,git diff 产生差异,并且非常灵活。它是供人类使用的,而不是供机器使用的,因此它的输出可能是也可能不是您想要的(特别是因为您没有定义任何术语)。 git format-patch 程序不太灵活,更适合机器使用:它产生的输出可以被git apply 轻松消化,这意味着应用单个补丁而不提交它,或者git am,意味着应用以“邮箱格式”存储在您计算机上的一系列补丁,并提交每个补丁。 (am 这里是 Apply Mailbox 的缩写,或多或少。)
因为您尚未定义您的术语,所以您的问题没有一个正确的答案。如果我们假设您的意思是生成一个git apply 可以应用的文件,我们会得到一个可能的答案。如果我们假设您的意思是生成git am 可以应用和提交的邮箱格式补丁,我们会得到不同的答案。
git format-patch 命令将从一些提交或提交中生成一个邮箱格式的补丁(或一系列补丁)。因此,要使用它,您必须提交。如果您愿意,您可以简单地在新分支上提交您希望在补丁中包含的那些特定文件。 (请参阅下面的详细信息。)
git diff 程序或其任何更面向机器的相关命令(git diff-tree、git diff-files、git diff-index)将产生人类可读的差异。如果没有上色,则适合与git apply一起使用。要使用这些,您无需提交。
作为LeGEC noted,您可以在特定文件上使用git diff。请注意,默认情况下,git diff -- <em>paths</em> 会将每个指定路径的 index 副本 与同一路径的 work-tree 副本 进行比较。这可能就是你想要的。如果您已将git diff 配置为始终生成彩色输出,请在git diff 操作期间将其关闭。将输出保存在某处:
git diff -- file1 ... fileN > /tmp/patch
或:
git diff --color=never -- file1 ... fileN > /tmp/patch
如果您需要禁用着色。
Long:关于提交
如果以上内容足够,则无需阅读其余部分。
如果您对正确补丁 的定义意味着git am 可以变成提交,那么您将需要进行新的提交。这就是了解什么是 staged 与 not staged,以及分支名称和提交如何工作,变得非常重要。
Git 的核心其实就是提交。分支——或者更具体地说,分支names——很有用,特别是如果你是人类,但它们并不是 Git 的真正意义所在。 Git 就是关于提交。
每个提交都有编号,但数字不是简单的顺序计数。我们找不到提交#1,然后提交#2,依此类推。相反,每个提交都会获得一个唯一的哈希 ID。这个东西看起来是随机的,但实际上完全是非随机的,并且经过仔细计算,以便 每个 Git 将 100% 相同的、逐位的编号相同的提交以相同的方式。这样,所有地方的每个 Git 都会同意 this 提交获得 this 哈希 ID,并且没有其他提交获得这个哈希 ID。
提交的内容分为两部分:数据和元数据:
-
提交中的数据非常简单:它是Git 知道的每个文件的完整快照。
这显然使 Git 存储库变得非常庞大,因为每次提交都会存储每个文件。但他们不会(变得非常肥胖)。原因是文件以特殊的、只读的、仅限 Git 的、压缩的和去重复的格式存储。如果您进行一千次提交,每个提交有 1000 个文件,但每次重复使用 1000 个文件中的 999 个,那么所有一千次提交与其他提交共享 999 个文件。
(除此之外还有很多,但重复数据删除是第一步,而且是非常大的一步。)
-
提交中的元数据是关于提交的信息。例如,这是 Git 存储提交人的姓名和电子邮件地址的地方。不过,在这个元数据中,Git 存储了 Git 自己需要的一组特定信息。每个提交都存储其 父项 提交的 提交编号(哈希 ID)。这就是历史在存储库中的存在方式。
由于提交中的文件是只读的——而且只有 Git,永远冻结,压缩成这种特殊的冻干格式,只有 Git 本身可以使用——所以您使用的文件必须从提交中提取。这就是 git checkout(或者,从 Git 2.23 开始,git switch)所做的。您选择一个提交并告诉 Git:从该提交中提取所有文件,以便我可以看到它们并使用 / 处理它们。
因为每个提交都会记住它的上一次提交,所以你需要做的,使用 Git,就是让 Git 为你记住 的唯一哈希 ID最后 提交。这就是分支名称的来源。分支名称仅包含要被视为该分支一部分的最后提交的哈希 ID。
这意味着我们可以像这样画一个分支:
... <-F <-G <-H <--master
namemaster 保存 last 提交的实际哈希 ID,此时为哈希 ID H。该提交保存了所有文件的快照,还保存了之前提交的哈希 ID G。
Git 可以通过哈希 ID 查找任何提交(实际上是任何内部对象),因此 Git 可以查找提交 H,提取其所有文件,然后让您处理它。或者,Git 可以查找提交 H,找到其父哈希 ID G,并查找提交 G。然后 Git 可以提取 G 的文件,或查找其父哈希 ID F。这是 Git 的第一个大秘密。
提交、工作树和索引
鉴于上述情况(只读提交和读/写文件),我们已经看到 Git 必须将提交提取到您可以查看和处理文件的区域中。该区域是您的工作树或工作树。因此,每个感兴趣的文件都有两个副本:一个在当前提交中,它一直被冻结,另一个在您的工作树中,您可以使用它。这非常简单,但有一个转折点:您可以在这个区域创建 Git 不知道的文件。
不过,这里真正棘手的部分是 Git 如何进行新提交,以及 Git 确实知道哪些文件。例如,您可能认为 Git 只会保留一个文件名列表,并使用您的工作树文件进行新的提交……但事实并非如此。
相反,Git 保留了 第三个 副本——嗯,去重复 副本,冻干格式——在中间位置。在当前提交和您的工作树之间,Git 拥有 Git 从提交中取出的每个文件的 other“副本”(已经去重,所以不完全是副本)结帐时间。
每个文件的这些中间“副本”位于 Git 所称的位置,不同的是,index,或 暂存区,或(现在很少) 缓存。请注意,这些副本已准备好进入新的提交,因为它们已经是仅 Git 的冻干格式。与提交本身的副本不同,它们可以被替换。
这就是git add 的全部意义所在。 git add 命令意味着 使某些文件的索引副本与工作树副本匹配。 如果您更改了工作树中的文件,则必须告诉 Git 复制更新后的文件文件回到 Git 的索引中。
这就是暂存文件。在任何时候,Git 的索引都有 Git 知道的每个文件的副本。如果它在索引中,它就可以提交了——但它可能与当前提交中的文件相同!如果是这样,它已经被重复数据删除了,Git 可以很容易地判断它是相同的。
如果文件的索引副本与当前提交副本不同,或者是全新的,那么进入下一次提交的内容不同在当前提交中。 Git 调用了 staged。但是,如果索引副本与提交的副本相同,Git 什么也不说。
同时,文件的索引副本可能与工作树副本匹配,也可能不匹配。如果索引副本确实 匹配工作树文件,Git 不会说明任何内容。如果不是,Git 说该文件是unstaged。
这意味着一个文件可以同时暂存以进行提交和不暂存以进行提交!如果提交的副本(无法更改)与索引副本不匹配,并且索引副本与工作树副本不匹配,则您有一个文件既是 为提交暂存和未为提交暂存。您可以通过以下方式获得此状态:
git checkout somebranch
edit file.ext # change something in a file
git add file.ext # copy the updated file back into Git's index
edit file.ext # change something else in the same file
当您运行git commit 时,Git 所做的是从当时 Git 索引中的任何内容进行新的提交。因此,如果您现在有 10 个未暂存的文件,并且您 git add 其中六个然后运行 git commit,您将获得一个新的提交,其中:
- 六个文件与上一次提交(当前之前你运行
git commit的那个)不匹配,但是
- 所有其他文件做匹配上一次提交。
现在您已经做出了新的提交,新的提交就是当前的提交。您来自索引中的文件,因此新的当前提交中的所有文件匹配索引中的所有文件。没有文件被“暂存以供提交”,但是您 没有 git add 的四个文件仍然存在于您的工作树中,与索引中相应的四个文件和这四个文件仍然不同在当前提交中。所以这四个文件仍然“未暂存以进行提交”。
如果您愿意,您现在可以git add 这四个文件将工作树版本复制回 Git 的索引,然后git commit 结果。您现在有 两个 以前没有的新提交。最后一个匹配您的工作树,因此当前提交、Git 的索引和您的工作树都匹配:没有文件暂存,也没有文件未暂存。
更多关于分支名称
请注意,每次进行新提交时,Git 都必须更新 当前分支名称。假设您最初位于 master 分支上,如下所示:
...--F--G--H <-- master
现在您创建一个新 分支名称,例如feature。这个新名称也标识了提交H。我们将在其中一个分支名称中添加名称HEAD(全部大写),以显示我们正在使用的名称:
...--F--G--H <-- feature (HEAD), master
现在我们将git add 一些文件并进行新的提交。它将获得一个看起来随机的新哈希 ID;我们就叫它I:
...--F--G--H <-- master
\
I
诀窍在于 Git 现在将 I 的哈希 ID 写入 name feature——HEAD 是附加到的——所以名称指向 I现在:
...--F--G--H <-- master
\
I <-- feature (HEAD)
如果我们再添加一些文件和git commit,我们会得到另一个新的提交J:
...--F--G--H <-- master
\
I--J <-- feature (HEAD)
请注意,每个提交都有每个文件的完整快照,因为它在您运行git commit 时出现在Git 的索引中。当你使用git format-patch 将提交变成补丁时,Git:
- 从提交的父级中提取文件(例如,
H for I);
- 从提交中提取文件 (
I);
- 比较提取的文件;和
- 告诉您哪些文件不同,为您提供将旧版本更改为新版本的秘诀。
由于这是一个可提交的补丁,Git 会在其中添加一个标头,给出提交者的姓名和电子邮件地址、适当的日期和时间戳,以及提交者的日志消息提交解释了他们做出该提交的原因。补丁本身位于此标头之后。