首页 > 解决方案 > Git diff 所有本地更改,无论哪个提交或暂存/未暂存

问题描述

我不喜欢 Git 的一件事是 diff 命令的变化取决于它是未暂存、暂存、已提交等。

我有一个分支,我只git push编辑审查。没有任何东西被推送到主分支。但是,我想对我更改的每一行进行最后一次检查,即使它目前没有上演。

我有这个命令:

git difftool @{upstream}

但它似乎只显示了自上次提交以来的差异。

我如何才能将所有内容(分阶段、未分阶段、在先前提交期间分阶段)与主分支进行比较?

标签: git

解决方案


TL;博士

您现有的git diffgit difftool命令可能应该为您提供您想知道的一切。如果不是……好吧,正如j6t 评论的那样,我们无法读懂您的想法。也许您想要的另一件事是git diff origin/master @{upstream}git difftool origin/master @{upstream}这里。

这里有两个问题,但很难准确描述。令人困惑的是,问题通常也不是问题。尽可能简单地说:git diff只比较两件事(嗯,有点:细节有点粘,稍后)。有时我们想要比较两个以上的东西,为此,我们需要多个git diff命令。

那么,诀窍就是要知道Git 比较的是什么,即差异中有哪两件事。您确实需要了解所有这些内容才能了解 Git 在做什么以及为什么这样做,以便有效地使用 Git 提供的工具。所以我要做的是描述什么git diffgit difftool实际做什么。

关键背景(长)

请记住,Git 是关于提交的:

  • 提交具有哈希 ID,这些是它们的“真实名称”,实际上。Git 实际上是通过它们的哈希 ID 来查找提交的。

  • 每个提交都有所有文件的完整快照。这个快照是它的主要数据。每个提交还有一些元数据,例如提交人的姓名、提交时间(日期和时间戳)以及提交原因(他们的日志消息)。在没有其他选项的情况下使用git log,您只会看到哈希 ID 和元数据。

  • 每次提交中的文件都是完全、完全、100% 只读的。它们以一种特殊的仅 Git 冻结格式存储,经过压缩和重复数据删除(因此每次提交都存储每个文件并不重要:每个版本都只有一个副本)。

  • 事实上,关于提交的所有内容——包括它的所有元数据——都是只读的,并且一直被冻结。并且,在该元数据中,每个提交都列出了其父提交的哈希 ID。通常这里只有一个哈希 ID。(合并提交至少有两个,通常正好两个。拥有多个提交是它们合并提交的原因。)

  • 分支名称、标签名称、诸如 之类的花哨的东西@{upstream}大多只是命名特定提交的方法。分支名称具有您可以“使用”它们的特殊属性(git statuson branch master),当您这样做时,提交会使分支名称识别新提交,而不是识别您使用的提交git checkout

  • 因此,根据定义,分支名称标识分支中的最后一次提交。较早的提交也包含在该分支中,该分支通过遵循每个提交元数据中的父哈希 ID 来工作。

但是,如果每次提交中的所有文件都只能由 Git 使用,并且无法更改,那么我们怎么能完成任何工作呢?答案是从归档的提交版本中git checkout 提取文件。这些文件成为普通的日常文件,我们可以查看和编辑等等。

这些普通的日常文件存储在 Git 所谓的工作树工作树中。但它们实际上根本不在存储库中!这很快就变得非常重要。

(我们稍后会回到“上演”。)

我有一个分支,我只git push编辑审查。

使用 时git push,您的 Git 会调用其他 Git。然后,您的 Git 会向其他 Git 发送您拥有但他们没有的任何提交。所有这些都通过哈希 ID 起作用,因为任何提交的哈希 ID 在每个Git 中都是相同的。哈希 ID 的唯一性是实现这一切的原因。两个 Git 可以共享一个提交,一个 Git 可以通过哈希 ID 判断它是否有另一个 Git 的提交。你的 Git 说我提供给你,其他 Git,哈希 ID ______(填空),他们会看这个,看看他们是否有它。如果没有,他们会要求它,你的 Git 会提供它的提交,这一切都会再次发生;或者如果他们有,你的 Git 已经完成了提供部分。

所以这就是你的 Git 知道要发送哪些提交的方式:那些他们没有的提交,你所做的并且现在用你的 发送git push,是那些被发送的。然后,最后,您的 Git 要求他们的 Git 设置他们的分支名称之一,以识别您刚刚发送的链中的最后一个提交。1

这一切意味着您只能推送提交。您无法从工作树中推送任何文件,这些文件甚至根本不在存储库中。当我们稍后介绍暂存某些文件时,您也不能推送暂存文件。您只能推送提交


1实际上,您可以要求他们在一个中设置多个名称git push。这里唯一的初始要求是要求他们将某个名称N设置为哈希 ID H,他们现在必须拥有哈希 ID H的对象。对于提交,这意味着您的 Git 应该首先向他们发送提交H,然后如果他们说是,请实际发送,或者如果他们说不,则不发送,谢谢,我已经有了

一旦他们请求将 _____(名称)设置为 _____(哈希 ID),他们可以检查是否希望允许以这种方式设置该名称。! rejected ...然后,根据他们对此请求的回复,您的推送要么成功,要么得到错误 ( )。


不同的提交

提交包含一个快照——所有文件的完整副本。

大多数时候,我们不想将快照视为一个整体。那东西太多了。如果我们确实想查看整个快照,我们可以git checkout将其文件提取到我们的工作树中,然后我们可以在这些文件上使用所有文件浏览/编辑工具。该提交成为当前提交。Git 总是知道你有哪个提交作为当前提交——最后一个你git checkout-ed(或git switch-ed to,在 Git 2.23 及更高版本中)。

不过,我们希望一张快照与另一张快照进行比较。

git diffgit difftool命令可以做到这一点:

git diff <left-side-commit> <right-side-commit>

(或与 相同git difftool)。这是做什么的:

  • 将所有左侧提交提取到临时区域
  • 将所有右侧提交提取到临时区域
  • 匹配左右侧文件:
    • 对于每个相同的文件,什么都不做
    • 对于每个不同的文件显示差异(git diff)或运行另一个命令来显示差异(git difftool

当 Git 本身显示差异时,它使用自己内置的XDiff变体来计算配方:对左侧文件进行这些更改,您将获得右侧文件。 这是非常面向行的:即使您使用 word-diff 函数,Git 也会从查找更改的开始,然后对结果进行后处理以获取更改的单词。

产生的配方git diff不一定是编辑文件的人实际所做的。这只是一个产生相同结果的食谱。当然,如果您使用git difftool,则取决于其他程序(您选择的工具)向您展示发生了什么,无论该工具以何种方式完成该工作。

提交仅在提交后才存在

虽然git diff可以告诉您这是如何将提交 X 更改为提交 Y,但我们有一个工作树,其中有普通文件,也许我们已经更改了这些普通文件。如果 Git 能告诉我们我们在这里做了什么改变,那就太好了。

吉特可以。Git 可以将一个提交放在左边,而不是把一个提交放在左边,另一个提交放在右边,然后像右边提交一样使用我们充满文件的工作树。这就是:

git diff <left-side-commit>

确实:我们命名左侧提交,但我们喜欢——通过哈希 ID,或通过名称,如masterorbranch@{upstream}——并且 Git 将该提交提取到一个临时区域,然后将该提交与工作树文件进行比较。

暂存文件呢?

这就是事情变得有趣的地方,而且有点奇怪。Git 不仅有提交的文件——在提交中永久保存的文件,以只读、仅 Git、压缩和冻结的格式——以及我们工作树中根本不在存储库中的普通文件。Git 还拥有每个文件的第三个副本,其中2 个保存在 Git 具有三个名称的区域中。

这个区域被称为indexstaging area,或者——现在很少被称为cache,这取决于 Git 的谁/哪个部分正在执行调用。索引——我将在这里主要使用这个名称——有多种用途,但它的主要用途和思考方式是:索引保存提议的下一次提交

换句话说,索引中的内容是每个文件的副本,准备好进入下一次提交。这些副本采用冻结格式,但由于它们尚未提交,因此它们实际上并未冻结。您可以批发更换它们。你也可以完全删除它们——这意味着被删除的文件根本不会出现在下一次提交中——或者添加全新的文件,这意味着下一次提交将有一个不在当前提交中的文件。

索引开始匹配当前提交。也就是说,提交中的每个文件现在也在索引中,不在提交中的文件也不在索引中。

运行获取命名文件并将其复制到索引中。以前的任何副本现在都被替换了。如果以前没有副本,现在有。无论哪种方式,人们都将此暂存文件称为 commit。但是以前通常有一个文件。我们刚刚更换了它。git add path

当你运行时git status,Git 会执行两个 git diff操作。两者都带有一个标志,上面写着don't find a change recipe, just find if there are changes3

第一个git diff当前提交与索引进行比较。无论文件在哪里相同,Git 什么都不说。对于不同的文件,Git 说staged for commit

第二个git diff将索引与您的工作树进行比较。无论文件在哪里相同,Git 什么都不说。对于不同的文件,Git 说not staged for commit

当你开始运行git commit时,Git 会打包索引中的所有文件——它们已经是冻结的格式,这使得这很容易——并写出一个新的提交。新提交的父级是当前提交。新的提交获得一个新的、唯一的哈希 ID。除了父级(当前提交)和时间戳(“现在”)之外,元数据来自您提供的内容:您的姓名和电子邮件地址以及日志消息。现在,带有新哈希 ID 的新提交一直被冻结,如果git statuson branch xyz,Git 会将新提交的哈希 ID 填充到您的分支名称xyz中。


2从技术上讲,Git 索引中的内容不是文件副本,而是有关文件副本的信息。文件的复制内容保存为内部 Git blob 对象。除非您深入研究 Git 并使用git ls-files --stage, 或git update-index. 您可以将文件的索引副本视为“副本”,并且一切正常。

3你可以自己使用这个标志:git diff --name-status. 输出格式有点不同:git status尝试以更用户友好的方式呈现结果。


四种主要的跑步方式git diffgit difftool

要使暂存区本身在git diffor中发挥作用git difftool,您可能需要使用--cachedor选项--staged。两者的意思完全相同。

  • git diff没有额外的名字:如果我们想比较暂存区和工作树,这个命令是git diff没有额外的名字或提交哈希--name-status(当然,仍然允许使用诸如 之类的标志。)Git 将索引放在左侧,将工作树放在右侧,然后将两者进行比较。

  • git diff commit1 commit2:如果我们想比较两个提交,这就是工作。Git 将第一个提交(按名称或哈希 ID)放在左侧,第二个放在右侧。

  • git diff commit:将提交与当前工作树进行比较。Git 将命名的提交放在左侧,将工作树放在右侧。

  • git diff --cached commit:将提交与索引/暂存区域进行比较。命名的提交在左边,索引的文件在右边。

  • git diff --cached:将当前提交与索引/暂存区域进行比较,就像我们运行git diff --cached HEAD. (如果你计算要点,这是第五种方式,但它实际上与第四种方式完全相同。)

以上所有内容也适用git difftool:和以前一样,它把一件事放在左边,另一件事放在右边。但是,它不会让 Git 自己提出更改配方,而是调用您喜欢的工具来比较每个不同的文件。与往常一样,它只是跳过相同的文件。

几个特殊情况

git diff命令可以做更多的事情,例如比较两个指定的文件。它还有一个特殊的模式,它恰好在两种情况下使用,其中一种通常是通过调用的git show

git show命令非常方便。就像git log打开补丁模式一样,一次提交:4

git show <commit>

向您显示日志消息,然后显示差异。diff 将提交的级与提交进行比较。由于父级持有“之前”快照,而提交本身持有“之后”快照,结果是一个秘诀:对之前提交的内容执行此操作,您将获得命名提交的内容。

但是,当您git show合并提交上使用时,不只有一个父级。这以一种特殊的方式调用git diff,其中git diff运行多个差异。Git将每个父级与子级提交进行比较,一次一个。然后它将这些差异组合成它所谓的组合差异

至少对我来说,组合差异有点令人失望,因为这里的规则会丢弃任何从任何父级到子级匹配的文件。也就是说,如果您从合并的一侧获取未更改的文件,则丢弃某人在另一个分支上所做的所有工作,git show 并不会向您显示. 有时那是您正在寻找的错误,git show只是没有显示出来。您可以强制git show将其分解为多个单独git diff的 s- git show -muse-当它是问题时,这通常是找到这个问题的方法。

git diff如果您处于冲突合并的中间,将索引与工作树进行比较的简单方法也将使用组合差异代码。细节有点复杂,我不会在这里介绍它们。但是,当您处于这种冲突的合并模式时,您实际上无法提交,因此这永远不会适用于代码审查案例。


4git show也可以做更多的事情。阅读其文档了解详细信息。


结论

只要您对commits中发生的事情感兴趣,找出的方法是使用两次提交形式:

git diff <commit-1> <commit-2>

或用于git show自动比较父母与孩子:

git show <commit>

如果工作树内容与所需内容匹配,您可以使用将给定提交与工作树进行比较的表单。但是如果你想比较两个提交,你需要两个名字,或者两个哈希 ID,或者一个混合,例如。git diff commitcommit-2


推荐阅读