检查另一个分支x中未提交的更改,而无需签出分支x

时间:2018-08-05 19:11:42

标签: git git-diff git-status

我想确定另一个分支是否有未提交的更改,但是我不想检查该分支。基本上我想找出另一个分支是否具有“脏索引”。这意味着未提交的已分阶段或未分阶段的更改。

这可能吗?到目前为止,据我了解,除了当前已签出的分支,我认为您无法在任何其他分支上使用git status。但是git diff呢?

更新:我刚刚意识到,唯一具有“脏索引”的分支是您当前的分支,除非您使用git-stash(我认为)。 所以也许这个问题更好地表述为-我如何检查分支是否藏有/未提交更改?

2 个答案:

答案 0 :(得分:3)

注意:这花了一些时间写,并且您同时更新了问题。这个答案比可能需要的要长得多。


  

我想确定另一个分支(即当前分支以外的分支)是否有未提交的更改...

答案是没有。

实际上, no 分支包含未提交的更改。从字面上看这是不可能的,而提出这个问题意味着您认为Git的工作方式不同于Git实际的工作方式。

  

基本上我想找出另一个分支是否具有“脏索引”。

只有一个索引。嗯,那不是相当,而是从那开始!三个也是一棵工作树(这也不是 quitet 正确的,但再次让我们开始吧)。

  

... [更新]因此,也许这个问题可以更好地表述为-如何检查分支是否藏有/未提交更改?

在这里,您可能希望使用$(git --exec-path)/git-sh-setup中的帮助程序例程。例如,在您喜欢的编辑器中查看此文件。

Git实际如何工作

Git的实际工作方式有点复杂。任何Git存储库的主体都是一个数据库,其中包含四种类型的Git对象,其中对我们而言最重要的是 commit 。我们可以认为每个Git存储库都是由一系列提交组成的。每个提交都有一个唯一的哈希ID(这些大的丑陋的十六进制数字之一,例如b7bd9486b055c3f967a870311e704e3bb0654e4f),Git用来提取提交对象的内容。对象本身很小,但是包含对其他哈希ID的引用,这些引用最终使您获得该提交快照中的所有文件

同时,诸如master之类的分支名称仅包含哈希ID。每个分支名称都具有一个精确的哈希ID。因此,诸如master之类的名称具有一个b7bd9486b055c3f967a870311e704e3bb0654e4f之类的ID。 Git将此称为该分支的 tip commit 。名称到哈希ID的映射是第二个数据库,与主要的哈希ID到对象数据库并列。

提交本身包含另一个哈希ID-嗯,不止一个,但让我们看一下此提交。 git cat-file -p命令可以漂亮地打印对象的内容。

$ git cat-file -p b7bd9486b055c3f967a870311e704e3bb0654e4f | sed 's/@/ /'
tree 1fd4a47af4942cbdee0bdcb4375612ab521a4a51
parent 5571d085b3c9c2aa9470a10bcf2b8518d3e4ec99
author Junio C Hamano <gitster pobox.com> 1531941857 -0700
committer Junio C Hamano <gitster pobox.com> 1531941857 -0700

Third batch for 2.19 cycle

Signed-off-by: Junio C Hamano <gitster pobox.com>

tree 行为我们提供了该提交的快照。 parent 行告诉我们哪个提交在 之前。其余各行包含其余的元数据,包括进行提交的人的姓名和日志消息。

因为这些东西都在提交中,所以都已提交。也没有任何更改:此处列出的tree拥有 all 文件的完整快照。我们可以再次使用git cat-file -p直接查看树,但它又长又无聊:

100644 blob 12a89f95f993546888410613458c9385b16f0108    .clang-format
100644 blob 1bdc91e282c5393c527b3902a208227c19971b84    .gitattributes
[snippage]
100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42    COPYING
040000 tree 814822a9a0a75e17294704b37950c30361401a85    Documentation
[lots more snippage]
100644 blob ec6e574e4aa07414b9a17bb99ddee26fd44497de    xdiff-interface.c
100644 blob 135fc05d72e8f066a63902785d12485a656efa97    xdiff-interface.h
040000 tree 12edaa3d770f84e31ee58826eea93ea6ca64d939    xdiff
100644 blob d594cba3fc9d82d94b9277e886f2bee265e552f6    zlib.c

如果遵循每个子tree行,我们最终将在Git的Git存储库中收集提交1fd4a47af4942cbdee0bdcb4375612ab521a4a51的整个快照。每行blob都会为我们提供该提交附带的文件的哈希ID,而文件的 name 在该行的末尾。因此,Git中的提交具有zlib.c版本,该版本具有哈希ID d594cba3fc9d82d94b9277e886f2bee265e552f6,并且如果需要,我们也可以使用git cat-file -p查看该文件:

$ git cat-file -p d594cba3fc9d82d94b9277e886f2bee265e552f6 | head
/*
 * zlib wrappers to make sure we don't silently miss errors
 * at init time.
 */
#include "cache.h"
[rest snipped]

这些都是冻结的/只读的

在这些哈希ID键下,存储在主存储库主体中的所有内容完全是100%只读的。原因很简单:哈希ID密钥实际上是数据的加密校验和! (好吧,其中的数据加上一个很小的标头提供了对象的类型和大小。)当给Git一个哈希ID作为键,并且Git使用它从存储库数据库中提取对象时,Git会进行一致性检查:重新校验检索到的数据,以确保它与最初使用的哈希ID匹配。这两个必须匹配:如果不匹配,则Git知道该对象已经以某种方式损坏,例如由于磁盘故障或类似原因。

名称找到提示提交;承诺找到他们的父母,以建立一个向后的链条

如上所述,类似master的名称包含该分支的 tip 提交的提交哈希ID。如果我们使用哈希ID来检索提交本身,则会得到parent行。父级告诉我们 是分支尖端的提交的哈希ID。我们说这些东西(分支名称和提交本身)都指向 提交,我们可以将这些指针绘制为箭头:

... <-parent  <-tip   <--master

Git可以从最先提交开始,然后对该提交执行一些操作。然后,Git可以使用父ID来查找先前的提交,并对其进行处理。父级是它自己的父级,而Git可以查看该提交,依此类推。最终,整个链返回到动作停止的位置,通常是在有史以来第一次提交时。如果在一个很小的存储库中有两个分支名称,它们的分支提示处有不同的提交,我们可以像这样绘制整个内容:

A--B--C--D   <-- master
       \
        E--F--G   <-- develop

内部链接始终从提交到父级指向后方。由于对象本身是只读的,因此没有提交会记住其子对象:创建得太晚了。但是所有的孩子总是记得所有的父母。当我们进行合并提交时,例如将G合并到D中,合并会记住其父母的两者

A--B--C--D------H   <-- master
       \       /
        E--F--G   <-- develop

并且,每当我们进行新提交时,都会发生相同的事情:当前分支名称发生更改,因此master指向H而不是{{1 }}。合并提交的 first 父级,以及普通非合并提交的唯一(也是第一个)父级,是 刚才的提示

换句话说:分支名称移动;提交保持不变。

所有提交的内容都采用特殊的仅Git格式

blob 对象(包含上面我们看到的包含文件的内容)由哈希ID标识,采用特殊的,仅Git的压缩格式。 (提交和树也被压缩了,尽管这并不重要,因为只有Git才真正直接使用它们。)但是,这对我们自己的使用没有好处,因此我们需要一个Git可以将文件提取为普通的未压缩格式的地方。 。这些文件还需要可更改,而仅Git冻结的blob对象则不能。

因此,每个存储库通常都带有一(1)个 work-tree ,Git可以在其​​中提取和解压缩已提交的文件。工作树的初始内容来自冻结的提交之一。

当然,

Git确实需要一种使 new 提交的方法。有一个类似的版本控制系统(Mercurial),它使用工作树进行新的提交,而Git可以做到这一点,但是Git却不这样做。相反,Git将此事物分别称为 index staging area cache 。索引的作用可能会变得非常复杂-例如,在有冲突的合并操作中,Git对其进行了很多扩展-但也许最好将其描述为建立下一个提交的位置。这是它作为临时区域的角色。

新提交是完整的快照,而不仅仅是更改!因此,索引/暂存区域保存当前提交中的所有文件。 Git只是将当前提交复制到索引,这使索引包含所有文件。索引中的文件仍然是特殊的仅Git格式,但是-这是与已提交副本的关键区别-现在它们可以可写

我们现在可以观察签出并进行新提交的过程

让我们从六个提交的存储库开始。在此存储库中,我们还要添加远程跟踪名称(D),并将名称origin/*附加到HEAD

master

现在让我们这样做:

A--B--C--D   <-- master (HEAD), origin/master
       \
        E--F   <-- origin/develop

(并且假设存储库刚刚被克隆,并且一切都“干净”)。分支名称$ git checkout develop 尚不存在! develop实际上是使用git checkout作为哈希ID来立即创建,而不是失败,因此名称origin/develop应运而生,指向提交{{ 1}}。

下一步可能会非常复杂(请参见Checkout another branch when there are uncommitted changes on the current branch),但是我们假设一切都很好,因此我们可以进一步简化它:Git将develop中树的内容放入并将其添加到索引中,并通过删除其中不应该包含的所有文件并使所有其他文件与索引中的文件匹配但已解压缩,从而使工作树匹配。 Git将名称F附加到F。除了将HEAD附加到新的名称 develop之外,我们的提交图图形保持不变:

HEAD

我们现在可以愉快地修改工作树中的文件。完成此操作后,我们对更改的任何内容运行develop。这会将工作树文件复制到索引中,对其进行压缩并使其准备提交:

A--B--C--D   <-- master, origin/master
       \
        E--F   <-- develop (HEAD), origin/develop

现在索引与工作树匹配,我们运行:

git add

Git将索引的内容打包(作为带有子树的树),将所有预压缩的文件冻结到新树中,并进行新提交。新提交的父对象为$ [edit various files] $ git add -u # or -A or `.` or list the files or whatever ,因为$ git commit # with -m to avoid using the editor, or whatever 附加到F并且HEAD包含develop的哈希ID。新提交的树是刚刚打包的Git树,作者是“我们”,依此类推。写出提交会为我们的新提交develop产生一个新的唯一哈希ID。 Git将该哈希ID写入名称F中,现在我们有了:

G

与我们的索引和工作树相互匹配并匹配提交develop,因为A--B--C--D <-- master, origin/master \ E--F--G <-- develop (HEAD), origin/develop 刚从我们的索引中制成

所有其他物品都从这里建造

在编辑或注释中,您提到使用GG所做的是使 commits git stash进行的提交的特殊之处在于它们位于 no 分支上。

实际上,至少进行了两次提交,一次提交用于当前索引内容,另一次提交用于工作树内容,以防万一您已暂存(使用git stash)某些内容而未暂存(未添加) ) 更多东西。这些提交都是完整的快照!我喜欢将它们绘制为git stashgit add

i

Git通过特殊名称w(不是分支名称)找到A--B--C--D <-- master, origin/master \ E--F--G <-- develop (HEAD), origin/develop |\ i-w <-- refs/stash 提交,分支位于w中,例如refs/stash和{{1} }。 refs/heads/提交会找到refs/heads/master提交,以及进行隐藏的提交(refs/heads/develop); w提交还会链接回到存储时的最新提交。

提交了两个(常规存储)或三个(iG)存储,i运行--include-untracked来清理索引和工作树,使它们与当前提交匹配。这里也有更多选项,但涵盖了--all的基本操作。

git stash和其他特殊情况

使用git reset --hard,我们可以创建与当前存储库一起使用的新的其他工作树。每个添加的工作树都有其自己的索引。每个工作树的索引都会缓存(因此命名为 cache )很多工作树的数据 about ,Git会使用它来快速浏览甚至避免扫描工作树:这是为什么以及为什么索引 indexes 工作树,因此命名为 index

除了所有这些,您还可以创建自己的临时索引文件!例如,git stash就是这样工作的。首先,它对 current 索引进行一个最普通的提交git worktree add,这很容易,因为Git已经知道如何做到这一点,但是随后必须提交工作树。 Git只能根据索引建立新的提交,因此git worktree要做的是创建一个临时索引,将所有工作树文件放入其中,然后使用它来制作{{ 1}}提交。

许多其他奇特的Git技巧都可以利用临时或备用索引文件。人们必须格外小心,因为Git通常假设 the 索引索引/缓存 the 工作树,或者添加的每个工作树索引缓存特别添加的工作树-如果您使事情不同步,则会发生一些有趣的细微故障。

答案 1 :(得分:1)

工作树和索引并不特定于任何分支。因此,具有“未暂存”或“未提交”的更改不是可以直接查询的分支的属性。

但是,您可以将工作树与特定分支进行比较:

git diff <branch name>

或者将索引与特定分支进行比较,

git diff --cached <branch name>

在两种情况下,省略<branch name>基本上都默认为当前签出的分支。