如何查看Git暂存区中未被跟踪的文件

7

我经常将我的工作存储在新的(未跟踪)文件中,我希望以后能够找到这些工作。 显然的方法似乎是使用git show查找。

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

为了更具体地说明,假设我有:

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会省略未跟踪的文件。不仅是在命令行中,像Fork和GitKraken这样的GUI工具也有相同的问题,可能是从Git继承而来。

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

更新:

尽管GitKraken和Fork无法显示任何暂存区中未跟踪的文件,但是Tower 2(一个旧版本,因为我拒绝切换到他们的订阅定价模式)成功了。 我必须给他们一些信用,如果不是价格的问题,我今天就会购买一个新副本。


如果您查看存储的提交,它应该看起来像三个父合并提交。未跟踪文件的差异应在第三个父级中。 - miqh
1个回答

4

TL;DR

To see file names: git ls-tree -r stash^3, to see contents: git show stash^3.

Long

如何展示存储的内容,包括未跟踪的文件?大部分情况下,未跟踪的文件通常不在存储中。但有一些例外,您正在使用其中一个例外的方法,稍后我将讲到它。但要展示这些文件比较麻烦。使用这些文件更加困难。我的意见是,git stash前端需要在这方面进行一些改进。首先,了解Git内部工作原理非常重要。Git仓库主要是一对数据库。一个(通常是最大的)保存提交和其他Git对象。较小的数据库保存名称:分支名称、标签名称、远程跟踪名称等。分支名称和标签名称等都是通用引用或参考的特定形式。引用保存单个Git哈希ID,最常见的是提交哈希ID。每个提交本身还可以保存一个或多个先前提交的哈希ID。这些是提交的父项。对于普通(非存储)提交,这些通常形成一个简单的链:最后一个提交作为其父项,记住倒数第二个提交。那个倒数第二个提交记住它的父项——最后提交的祖父,并且祖父记住另一个父项,依此类推。具有两个父项的提交是合并提交,这是其定义。因此,分支名称只是保存我们希望说是该分支上/中/的最后一个提交的哈希ID。多个名称可以选择相同的哈希ID,哈希ID可以通过从其尖端提交开始向后工作而从某个分支可达。因此,在Git中,提交通常位于多个分支上。提交本身包含所有已跟踪文件的快照。Git通过将跟踪的文件写入Git的索引,然后使用 git write-tree 将它们写入内部树对象,然后使用 git commit-tree 编写提交对象,以使用由git write-tree编写的树(s)。因此,所有提交都源自索引。任何索引中的文件都是跟踪的。所以这让我们有点困惑。

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


暂存区

特殊的引用refs/stash(如果存在)指向一个提交。这就是stash@{0}提交。(它在stash@{1}及以上的reflog条目也指向每个提交。)因此,当存在时,暂存区由提交组成。这些提交不在任何分支上,而是通过refs/stash找到。

正常的暂存区形式类似于合并提交,但并不完全相同。 git stash所做的是创建两个提交,使用上面描述的非常低级别的git write-treegit commit-tree方法。根据你在运行git stash savegit stash push时暂存区中的内容,第一个提交很容易:当你执行git write-tree时,它已经将暂存区的内容写入,因此这两个命令放在一起,将创建这个暂存区提交,我称之为igit stash文档称之为I)。

第二个提交要棘手一些,但基本上,git stash所做的是运行git add -u(尽管没有实际使用git add -u,从而在各种版本的git stash中引入bug,其中一些在某些情况下会产生错误的工作树提交)。这将更新暂存区,以保存所有被跟踪文件到它们在工作树中的状态。然后,git write-tree紧随其后,再加上git commit-tree,可以为你的工作树创建一个良好的快照,当然不包括任何未被跟踪的文件。

因为git stash正在使用这些底层命令,所以它可以使工作树提交(我称之为w)具有其选择的任何父提交。它将使该提交同时拥有iHEAD作为其两个父提交:

...--o--o   <-- branch (HEAD)
        |\
        i-w
i 提交看起来像普通提交一样,w 提交类似于合并。实际上它不是一个合并 - 它只是你的工作树的快照,作为添加到索引中的内容,但它的第一个父提交是您当前分支的尖端提交,第二个父提交是提交i
进行此存储后,git stash save 执行 git reset --hard,使您的索引和工作树与 HEAD 匹配。 HEAD 本身从未移动,并且未跟踪的文件未被保存并且不受影响。
但是,当您执行 git stash save -ugit stash save -a 时,Git 会创建一个我称之为 u 的第三个提交。 这个第三个提交使用已清除所有已跟踪文件的索引,并使用您的一些或所有未跟踪文件加载,就好像在特定文件名上使用了 git add --force 一样。进入这个第三个提交的文件集取决于您是否使用了 -u-a-u 枚举未被忽略的未跟踪文件,而 -a 枚举所有未跟踪文件,即使它们被忽略。Git 将这些文件复制到(好吧,并且是)索引中,并创建此 u 提交,没有任何父提交,在最终的 w 提交之前。然后它使 w 提交,u 作为它的第三个父提交。
...--o--o   <-- branch (HEAD)
        |\
        i-w
         /
        u

因此,w^1是分支尖端的提交记录,w^2是索引提交记录iw^3是提交记录u。这个w继续类似于合并提交记录——这次是多爪合并提交记录——但它的快照与任何双提交储藏记录相同。
进行了u提交记录后,git stash savegit stash push现在从您的工作树中移除所有在u提交记录中的文件。如果某些在u提交记录中的文件也被忽略,则仍会被删除。
如果Git无法将u提交记录提取到您当前的工作树中,则应用三个提交储藏记录将失败。因此,知道第三个u提交记录中有什么肯定是有用的。但是没有git stash ____可以显示提交记录u是否存在,更不用说其中有什么了。因此,我们必须退回到较低级别的Git命令。
特别地,由于u是一个根提交,因此git show将其与空树进行差异比较。如果不想进行完全差异比较,则可以使用git show --name-onlygit ls-tree -r获取文件列表。要为提交记录u命名,我们可以为任何储藏提交记录指定名称——指向refs/stash的任何w提交对象或其之一reflog条目——并添加^3后缀以表示第三个父项。如果储藏只有wi,则^3将失败:没有第三个父项,因此没有可显示的内容。
2如果愿意,实际上可以将它们放在一个分支上,但结果最多是丑陋的。 3在内部,git stash使用临时索引而不是真正的/主索引,以使事情变得更容易。它也用于w提交记录。虽然有一个特殊的索引theindex,用于跟踪您的工作树,但您随时可以创建一个临时索引,将其路径名放入GIT_INDEX_FILE中,并使用该临时索引而不是使用区分索引的Git命令。对于需要创建提交记录(需要使用索引)但不想在此过程中干扰索引的任何命令,这非常方便。

git show stash^3 显示文件的名称和内容,但 git ls-files stash^3 没有显示任何内容。 - iconoclast
@iconoclast:哎呀,应该是 git ls-tree(带有 -r 以访问子树);我会修复的。 - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接