首页 > 解决方案 > 检查另一个分支 x 中未提交的更改,而不检查分支 x

问题描述

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

这可能吗?从我目前了解到的情况来看,我认为您不能git status在除您当前签出的分支之外的任何分支上使用。但是呢git diff

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

标签: gitgit-diffgit-status

解决方案


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


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

答案是没有。

事实上,没有任何分支包含未提交的更改。这实际上是不可能的,问这个问题意味着你认为 Git 以其他方式工作,而不是 Git 实际工作。

基本上我想知道另一个分支是否有“脏索引”。

只有一个索引。好吧,这并不完全正确,但从那开始吧!三也只是一棵工作树(这也不完全正确,但同样,让我们​​从它开始)。

... [更新]所以也许这个问题更好地表述为 - 我如何检查是否有任何分支已隐藏/未提交的更改?

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

Git 的实际工作原理

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

同时,分支名称之类的master只是简单地保存哈希 ID。每个分支名称都包含一个哈希 ID。所以一个名字 likemaster持有一个 ID like b7bd9486b055c3f967a870311e704e3bb0654e4f。Git 将此称为该分支的提示提交。name-to-hash-ID 映射是第二个数据库,位于主 hash-ID-to-object 数据库旁边。

提交本身包含另一个哈希 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此处列出的包含所有文件的完整快照。我们可以直接查看树,再次使用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行,我们最终会收集整个快照以1fd4a47af4942cbdee0bdcb4375612ab521a4a51在 Git 的 Git 存储库中提交。每一blob行都为我们提供了与该提交相关的文件的哈希 ID,而文件的名称位于行尾。所以 Git 中的这个提交有一个zlib.c带有 hash 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 就知道对象已经以某种方式损坏,例如,由于磁盘故障或类似的原因。

名称查找提示提交;提交找到他们的父母,做一个反向链

如上所述,like 名称包含该分支的提示master提交的提交哈希 ID 。如果我们使用哈希 ID 来检索提交本身,我们会得到一行。父级在较早的时间告诉我们作为分支尖端的提交的哈希 ID 我们说这些东西中的每一个——分支名称和提交本身——<em>指向一个提交,我们可以将这些指针绘制为箭头:parent

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

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

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

内部链接总是向后指向,从提交到父级。由于对象本身是只读的,因此没有提交记住它的孩子:那些创建得太晚了。但是所有的孩子总是记得他们所有的父母。当我们进行合并提交时,例如合并GD中,合并会记住它的两个父节点:

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

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

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

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

我们在上面看到的blob对象,即包含文件的东西,由哈希 ID 标识,是一种特殊的、仅限 Git 的压缩格式。(提交和树也被压缩了,虽然这不太重要,因为只有 Git 真正直接使用它们。)但是,这对我们自己的使用没有好处,所以我们需要一个地方,让 Git 可以将文件提取为普通的未压缩格式. 这些文件也需要是可更改的,而仅 Git 的冻结 blob 对象则不是。

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

当然,Git 确实需要一种方法来进行的提交。有一个类似的版本控制系统(Mercurial)使用工作树进行新的提交,Git 可以做到这一点,但 Git 没有这样做。相反,Git 有这个东西,不同地称为index,或staging area,或cache。索引的作用可能会变得相当复杂——例如,在有冲突的合并操作期间,Git 会大量扩展它——但最好将它描述为构建下一次提交的地方。这是它作为集结地的作用。

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

我们现在可以观察检查提交和进行新提交的过程

让我们从一个六提交存储库开始。在此存储库中,让我们也添加远程跟踪名称 ( origin/*),并将名称附加HEADmaster

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

现在让我们这样做:

$ git checkout develop

(让我们假设存储库刚刚被克隆并且所有内容都“干净”)。分支名称develop还不存在!与其失败,不如git checkout立即使用哈希 ID创建origin/develop它,这样名称就会develop出现,指向 commit F

下一步可能非常复杂(请参阅Checkout another branch when there are uncommitted changes on the current branch)但我们假设一切都是干净的,因此我们可以进一步简化它:Git 将树的内容F放入到索引中,并通过删除其中不应该存在的任何文件并使所有其他文件与索引中的文件匹配,但已解压缩,从而使工作树匹配。Git 将名称附加HEADdevelop. 我们的提交图绘图没有改变,除了HEAD名称 develop附加的位置:

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

我们现在可以愉快地修改工作树中的文件。在我们完成之后,我们运行git add我们改变的任何一个。这会将工作树文件复制到索引中,压缩它们并准备好提交:

$ [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

Git 将索引的内容打包(作为带有适当子树的树),将所有预压缩文件冻结到新树中,并进行新的提交。新提交的父级是F, 因为HEAD附加到developdevelop包含F的哈希 ID。新提交的树就是Git刚刚打包的树,作者是“我们”,以此类推。写出提交会为我们的新提交生成一个新的、唯一的哈希 ID G。Git 将该哈希 ID 写入 name develop,现在我们有了:

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

并且我们的索引和工作树相互匹配并匹配 commit G,因为G它只是由我们的索引创建的。

所有其他项目都从这里构建

在编辑或评论中,您提到使用git stash. 做git stash的是提交。提交的特别之处git stash在于它们不在分支上。

它实际上至少进行了两次提交,一次用于当前索引内容,一次用于工作树内容,以防您已暂存(使用git add)一些内容而未暂存(通过不添加)更多内容。这两个提交都是完整的快照!我喜欢把它们画成iw

A--B--C--D   <-- master, origin/master
       \
        E--F--G   <-- develop (HEAD), origin/develop
              |\
              i-w   <-- refs/stash

Git通过特殊名称(不是分支名称——分支位于、例如和中)找到w提交。提交找到提交,以及进行存储的提交();该提交还链接回您进行存储时的当前提交。refs/stashrefs/heads/refs/heads/masterrefs/heads/developwiGi

进行了两次(普通存储)或三个(--include-untracked--all)存储提交后,git stash运行git reset --hard以清除索引和工作树,使它们与当前提交匹配。这里还有更多选项,但涵盖了git stash.

git worktree add, 和其他特殊情况

使用git worktree,我们可以创建与当前存储库一起使用的新的附加工作树。每个添加的工作树都有自己的索引。每个工作树的索引缓存(因此名称为cache )关于该工作树的大量数据,Git 使用它来快速扫描甚至避免扫描工作树:这就是索引的方式和原因索引工作树,因此名称index

除了所有这些,您还可以创建自己的临时索引文件!例如,这就是git stash工作原理。首先,它对当前i索引进行大多数普通的提交,这很容易,因为 Git 已经知道如何做到这一点,但随后它必须提交工作树。Git 只能从索引构建新的提交,所以要做的是创建一个临时索引,将所有工作树文件塞入其中,并使用它来进行提交。git stashw

许多其他花哨的 Git 技巧可以利用临时或备用索引文件。必须小心使用这些,因为 Git 通常假定索引索引/缓存工作树——或者添加的每个工作树索引缓存特定添加的工作树——如果你让事情不同步,发生了一些有趣的微妙故障。


推荐阅读