我阅读了有关git patch
命令的内容,但所有示例都显示了如何为所有未暂存或缓存的文件,甚至是某些提交包含的所有文件(从一个提交到另一个提交)获取补丁。但是假设我有10 个未暂存的文件,我需要为其中的 6 个创建一个适当的补丁。我怎样才能做到这一点?如果有办法创建这样的补丁,我该如何应用它?
很抱歉,如果很明显。
1 回答
您必须首先定义短语适当的补丁。什么使补丁正确?是什么让一个人不合适?就此而言,补丁和差异有什么区别? 没有一个固定的答案,但请参阅补丁和差异文件之间的区别。
也就是说,git diff
会产生差异,并且非常灵活。它是供人类使用的,而不是供机器使用的,因此它的输出可能是也可能不是您想要的(特别是因为您没有定义任何术语)。该git format-patch
程序不太灵活,更适合机器使用:它产生的输出很容易被 消化git apply
,这意味着应用单个补丁而不提交它,或者git am
意味着应用存储在您的整个系列的补丁以“邮箱格式”的计算机,提交每一个。(am
这里或多或少是应用邮箱的缩写。)
因为你还没有定义你的术语,所以你的问题没有一个正确的答案。如果我们假设您的意思是生成一个git apply
可以应用的文件,我们会得到一个可能的答案。如果我们假设您的意思是生成一个git am
可以应用和提交的邮箱格式补丁,我们会得到不同的答案。
该git format-patch
命令将从一些提交或提交中生成一个邮箱格式的补丁(或一系列补丁) 。所以要使用它,你必须做出一个 commit。如果您愿意,您可以简单地在新分支上提交您希望在补丁中包含的那些特定文件。(请参阅下面的详细信息。)
该git diff
程序或其任何更面向机器的相关命令(git diff-tree
、、、git diff-files
)git diff-index
将产生人类可读的差异。如果它没有上色,它将适合与git apply
. 要使用这些,您无需提交。
正如LeGEC 所指出的,您可以git diff
在特定文件上使用。请注意,默认情况下,将每个指定路径的索引副本与同一路径的工作树副本进行比较。这可能就是你想要的。如果您已配置为始终生成彩色输出,请在一个操作期间将其关闭。将输出保存在某处:git diff -- paths
git diff
git diff
git diff -- file1 ... fileN > /tmp/patch
或者:
git diff --color=never -- file1 ... fileN > /tmp/patch
如果您需要禁用着色。
长:关于提交
如果以上内容足够,则无需阅读其余部分。
如果您对正确补丁的定义意味着git am
可以变成一个提交,那么您将需要进行新的提交。这就是了解staged与not staged以及分支名称和提交如何工作变得非常重要的地方。
Git 本质上是关于提交的。分支——或者更具体地说,分支名称——很有用,特别是如果你是一个人,但它们并不是 Git 的真正意义所在。Git 是关于提交的。
每个提交都有编号,但这些数字不是简单的顺序计数。我们找不到提交#1,然后提交#2,依此类推。相反,每个提交都会获得一个唯一的哈希 ID。这个东西看起来是随机的,但实际上完全是非随机的,并且是经过仔细计算的,因此每个Git 都会以相同的方式对 100% 相同的、逐位相同的提交进行编号。这样,每个地方的每个 Git 都会同意这个提交获得这个哈希 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
该名称 master
包含最后一次提交的实际哈希 ID,此时为哈希 ID H
。该提交保存了所有文件的快照,还保存了之前提交的哈希 ID G
。
Git 可以通过其哈希 ID 查找任何提交(实际上是任何内部对象),因此 Git 可以查找 commit H
,提取其所有文件,然后让您处理它。或者,Git 可以查找 commit H
,找到其父哈希 IDG
并查找 commit G
。然后 Git 可以提取G
的文件,或查找其父哈希 ID F
。这是 Git 的第一个大秘密。
提交、你的工作树和索引
鉴于上述情况——只读提交和读/写文件——我们已经看到 Git 必须将提交提取到您可以查看和处理文件的区域中。该区域是您的工作树或工作树。因此,每个感兴趣的文件都有两个副本:一个在当前提交中,它一直被冻结,另一个在您的工作树中,您可以使用它。这非常简单,但有一个转折点:您可以在这个区域创建 Git不知道的文件。
不过,这里真正棘手的部分是 Git 如何进行新的提交,以及 Git知道哪些文件。例如,您可能认为 Git 只会保留一个文件名列表,并使用您的工作树文件进行新的提交……但事实并非如此。
取而代之的是,Git 保留了第三份副本——好吧,一份去重的副本,采用冻干格式——在中间位置。在当前提交和您的工作树之间,Git 拥有Git 在结帐时从提交中取出的每个文件的另一个“副本”(已经去重,所以不完全是副本)。
每个文件的这些中间“副本”位于 Git 所称的不同位置,即index或staging area,或(这些天很少)cache。请注意,这些副本已准备好进入新的提交,因为它们已经是仅 Git 的冻干格式。与提交本身的副本不同,它们可以被替换。
这就是git add
全部。该git add
命令意味着使某些文件的索引副本与工作树副本匹配。 如果您更改了工作树中的文件,则必须告诉 Git 将更新后的文件复制回 Git 的索引中。
这就是暂存文件。在任何时候,Git 的索引都有 Git 知道的每个文件的副本。如果它在索引中,它就可以提交了——但它可能与当前提交中的文件相同!如果是这样,它已经被删除了,Git 可以很容易地判断它是相同的。
如果文件的索引副本与当前提交副本不同,或者是全新的,则进入下一次提交的内容与当前提交中的内容不同。Git 调用了暂存的 . 但是,如果索引副本与提交的副本相同,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
6 个然后运行git commit
,您将获得一个新的提交,其中:
- 六个文件与上一个提交(运行之前的当前提交)不匹配
git commit
,但是 - 所有其他文件都与之前的提交匹配。
现在您已经做出了新的提交,新的提交就是当前的提交。您是从索引中的文件创建的,因此新的当前提交中的所有文件都与索引中的所有文件匹配。没有文件被“暂存以进行提交”,但是您没有 git add
的四个文件仍然存在于您的工作树中,与索引中相应的四个文件和当前提交中的这四个文件仍然不同。所以这四个文件仍然是“未暂存提交”。
如果您愿意,您现在可以git add
将这四个文件复制回 Git 的索引,然后复制git commit
结果。您现在有两个以前没有的新提交。最后一个与您的工作树匹配,因此当前提交、Git 的索引和您的工作树都匹配:没有文件被暂存,也没有文件被取消暂存。
更多关于分支名称
请注意,每次您进行新提交时,Git 都必须更新当前分支名称。假设您master
最初在您的分支上,如下所示:
...--F--G--H <-- master
现在您创建一个新的分支名称,例如feature
. 这个新名称还标识了 commit H
。我们将 name HEAD
(全部大写)添加到分支名称之一,以显示我们正在使用的分支名称:
...--F--G--H <-- feature (HEAD), master
现在我们将处理git add
一些文件并进行新的提交。它将获得一个看起来随机的新哈希 ID;我们就这样称呼它I
:
...--F--G--H <-- master
\
I
诀窍在于,Git 现在将I
的哈希 ID 写入名称 feature
(附加到的名称)中HEAD
,以便名称指向I
现在:
...--F--G--H <-- master
\
I <-- feature (HEAD)
如果我们再次添加更多文件git commit
,我们会得到另一个新的提交J
:
...--F--G--H <-- master
\
I--J <-- feature (HEAD)
请注意,每个提交都有每个文件的完整快照,因为它在您运行时出现在 Git 的索引中git commit
。当您使用git format-patch
将提交转换为补丁时,Git:
- 从提交的父级中提取文件(例如
H
forI
); - 从提交 ( ) 中提取文件
I
; - 比较提取的文件;和
- 告诉您哪些文件不同,为您提供将旧版本更改为新版本的秘诀。
由于这是一个可提交的补丁,Git 添加了一个标头,给出了提交者的姓名和电子邮件地址、适当的日期和时间戳,以及提交者解释原因的日志消息他们做出了承诺。补丁本身位于此标头之后。