首页 > 解决方案 > 如何在 Git stashes 中查看未跟踪的文件

问题描述

我经常将我所做的工作存储在新的(未跟踪的)文件中,我希望以后能够找到这项工作。找到它的明显方法似乎是git show

我刚刚发现 Git 在我使用时完全忽略了这些文件git show(但幸运的是在弹出隐藏代码时没有忽略它们),这使得可靠地找到隐藏代码似乎是不可能的。

为了使这个更具体,假设我有

On branch feature/request-reappointment
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   app/models/job.rb
    modified:   config/initializers/environmental_email_interceptor.rb

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    app/models/receipt.rb
    db/migrate/20200130091050_create_receipts.rb

no changes added to commit (use "git add" and/or "git commit -a")

如果我表演

git stash save -u 'miscellaneous improvements'
git show stash@{0}

Git 将忽略未跟踪的文件。这不仅是 Git 从命令行的行为,而且像 Fork 和 GitKraken 这样的 GUI 工具也有同样的问题,大概是继承自 Git。

如何显示存储的实际内容,包括未跟踪的文件?

更新:

尽管 GitKraken 和 Fork 未能在任何存储中显示未跟踪的文件,但 Tower 2(一个旧版本,因为我拒绝切换到他们基于订阅的定价模型)成功了。我必须给他们一些信任,如果不是这个价格,我今天会买一本新的。

标签: git

解决方案


TL;博士

要查看文件名,请使用git ls-tree -r stash^3; 要查看它们的内容,请使用git show stash^3.

如何显示存储的实际内容,包括未跟踪的文件?

未跟踪的文件通常不在 stash 中。有一个例外——你正在使用它,所以我稍后会谈到。但是显示这些文件很棘手。使用它们更加困难。在我看来,git stash前端需要在这里做一些工作。

不过,首先,了解 Git 内部是如何工作的很重要。Git存储库主要是一对数据库。一个——几乎总是最大的一个——包含提交和其他 Git 对象。一个较小的数据库包含名称:分支名称 like master、标签名称 like v2.1、远程跟踪名称 likeorigin/master等等。分支名称和标签名称等是通用refreference的特定形式。一个 ref 持有一个 Git 哈希 ID,最常见的是一个提交哈希 ID。1

每个提交本身也可以保存一个或多个先前提交的哈希 ID。这些是提交的父母。对于普通(非存储)提交,​​这些通常形成一个很好的简单链:最后一个提交记住,作为它的父级,倒数第二个。倒数第二个提交会记住它的父节点——最后一个提交的祖父节点——而祖父节点会记住另一个父节点,依此类推。根据定义,具有两个父级的提交是合并提交。

因此,分支名称仅包含提交的哈希 ID,我们想说的是该分支 / on / 中的最后一次提交。多个名称可以选择相同的哈希 ID,和/或可以从某个分支通过从其尖端提交开始并向后工作来访问哈希 ID。因此,在 Git 中,提交通常位于多个分支上。

提交本身包含所有(跟踪的)文件的快照。Git 通过将跟踪的文件写入 Git 的索引,然后使用git write-tree将文件写入内部树对象,然后git commit-tree使用 写入提交对象,使用git write-tree. 所以所有的提交都起源于索引。而且,根据定义,索引中的任何文件都会被跟踪。所以这让我们有点困惑。


1要求分支名称和远程跟踪名称仅包含提交哈希ID;标签名称更灵活。


藏匿处

特殊的 ref refs/stash(如果存在)指向一个提交。那就是stash@{0}提交。(它在 on up 中的 reflog 条目stash@{1}也都指向一个提交。)因此,当存储存在时,它由提交组成。这些提交不在分支上:2它们是通过refs/stash而不是找到的。

普通存储具有合并提交的形式,但实质不同。所做的是git stash使用上述非常低级别的方法进行两次git write-tree提交git commit-tree。第一次这样的提交,基于你运行git stash saveor时索引中的任何内容git stash push,很容易:git write-tree已经只是写了索引中的任何内容,所以这两个命令放在一起,使这个索引提交,我称之为i(并且git stash文档调用I)。

第二次提交比较棘手,但本质上,git stash运行的是什么git add -u(尽管没有实际使用git add -u并因此在各种版本中引入错误git stash,其中一些在某些情况下会产生错误的工作树提交)。这会更新索引,以便它保存所有跟踪的文件,因为它们在工作树中的状态。然后对你git write-treegit commit-tree工作树做一个很好的快照,当然减去任何未跟踪的文件。

因为git stash使用的是低级命令,它可以使工作树提交——我称之为w——它选择的任何父集。i它与双方以及HEAD作为其两个父母一起做出此承诺:

...--o--o   <-- branch (HEAD)
        |\
        i-w

i提交看起来像任何普通提交,并且提交w类似于合并。它实际上不是合并——它只是添加到索引中的工作树的快照——但它的第一个父级是当前分支的提示提交,第二个父级是 commit i

进行此存储后,git stash save执行git reset --hard, 以使您的索引和工作树匹配HEAD。它HEAD本身永远不会移动,未跟踪的文件不会被保存并且不受影响。

但是,当您执行git stash save -uorgit stash save -a时,Git 会进行第三次提交,我称之为u. 第三次提交使用了一个索引,该索引清除了所有跟踪的文件,然后加载了部分或全部未跟踪的文件,就像git add --force在特定文件名上一样。3 进入第三次提交的文件集取决于您是否使用-u-a-u枚举未被忽略的未跟踪文件,并-a枚举所有未跟踪文件,即使它们忽略。Git 将这些文件复制到(嗯,一个)索引中并进行此u提交,根本没有父级,它做出最终w提交之前。然后它作为它的第三个父项进行w提交:u

...--o--o   <-- branch (HEAD)
        |\
        i-w
         /
        u

因此,w^1是分支顶端的提交,w^2是索引提交iw^3u提交。 w继续类似于合并提交——这次是章鱼合并——但它的快照与任何两次提交的存储相同。

u提交,git stash savegit stash push现在从您的工作树中删除提交中的所有文件u。如果其中的某些文件u也被忽略,它们仍会被删除。

如果 Git 无法将u提交提取到您当前的工作树中,则应用三提交存储会失败。因此,了解第三次u提交中的内容绝对有用。但是没有git stash ____(用动词填空)来显示提交是否u存在,更不用说其中的内容了。因此,我们必须依靠较低级别的 Git 命令。

特别是,因为u根提交git show所以会将它与空树进行比较。如果您不想要完整的差异,您可以使用git show --name-only或获取文件列表。git ls-tree -r命名commit u,我们可以命名任何 stash 提交——由它w指向的任何提交对象refs/stash或它的 reflog 条目之一——并添加^3后缀以表示第三父级。如果存储只有wand i^3则将失败:没有第三个父母,因此没有什么可显示的。


2如果你愿意,你可以把它们放到一个树枝上,但结果是......充其量是丑陋的。

3在内部,git stash使用临时索引而不是真实/主索引,使这更容易。它也为w提交做到这一点。虽然有一个特殊索引,即跟踪您的工作树的索引,但您可以随时创建一个临时索引,将其路径名放入GIT_INDEX_FILE中,然后将 Git 命令与该临时索引一起使用,而不是使用区分索引。这对于需要创建提交的任何命令(需要使用索引)都非常方便,并且不想在过程中干扰索引。


推荐阅读