【问题标题】:Git patch from several (not all) unstaged files来自几个(不是全部)未暂存文件的 Git 补丁
【发布时间】:2020-08-10 12:43:00
【问题描述】:

我阅读了有关git patch 命令的信息,但所有示例都显示了如何为所有未暂存或缓存的文件,甚至是某个提交包含的所有文件(从一个提交到另一个提交)获取补丁。但假设我有 10 个未暂存的文件,我需要为其中的 6 个 创建 一个适当的补丁。我怎样才能做到这一点?如果有办法创建这样的补丁,我该如何应用它?
很明显很抱歉。

【问题讨论】:

  • 你的意思是git format-patch
  • git diff -- file1 ... file6 将产生一个输出,该输出可以馈送到git apply
  • @Adam 我的意思是一些补丁,我想申请同一个目录没有任何错误。

标签: git version-control diff git-diff git-patch


【解决方案1】:

您必须首先定义短语正确的补丁。什么使补丁正确?是什么让一个人不合适?就此而言,patchdiff 之间有什么区别?没有一个固定的答案,但请参阅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-treegit diff-filesgit 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 可以变成提交,那么您将需要进行新的提交。这就是了解什么是 stagednot 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 会在其中添加一个标头,给出提交者的姓名和电子邮件地址、适当的日期和时间戳,以及提交者的日志消息提交解释了他们做出该提交的原因。补丁本身位于此标头之后。

【讨论】:

    猜你喜欢
    • 2011-04-14
    • 2015-05-21
    • 1970-01-01
    • 1970-01-01
    • 2020-01-27
    • 1970-01-01
    • 2023-04-03
    • 2018-03-18
    • 2011-06-13
    相关资源
    最近更新 更多