3

假设我有一个本地存储库并进行了一些更改 - 有些已添加到索引中,而有些仅被跟踪但留在工作区中而没有添加到索引中。

如果我要存储更改,请在同一个分支上继续工作(存储后),然后在添加或提交存储后所做的更改之前 git-apply。

git 如何处理“申请”?是应用=应用+尝试合并吗?

将存储应用到 repo 时,git 在内部做了什么?

在将其他隐藏的更改应用到同一分支之前,我们是否应该始终提交或隐藏我们的更改?

谢谢 !

4

2 回答 2

2

TL;博士

这很长,所以让我把结论放在首位,然后是我如何得出结论。

在将其他隐藏的更改应用到同一分支之前,我们是否应该始终提交或隐藏我们的更改?

除非您非常确定存储会干净地应用,否则我建议您这样做。考虑将特别复杂的存储情况转变为实际的分支,使用git stash branch.

请注意,git stash applygit stash pop的某些版本比其他版本更小心。(具体来说,“工作目录必须与索引匹配”的约束并非适用于每个版本的 Git!)

假设我有一个本地存储库并进行了一些更改 - 有些已添加到索引中,而有些仅被跟踪但留在工作区中而没有添加到索引中。

单词tracktracked在 Git 中有一些特定的含义,似乎都不合适。在这种情况下,单词tracked mean的适当定义存在于索引中。我将假设您的工作树中的所有文件都在此定义下“跟踪”,并且您的意思是您这样做了:

git check something
<edit several files>
git add <some of those files, but not all>

这样git status会说,在这一点上:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   wt-status.c

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:   wt-status.h

(这是git status命令的实际输出,减去前三行)。

如果我要隐藏更改,

在这一点上,重要的是要知道是什么git stash

它的作用是提交两个,或者如果你git stash要求它,三个提交。这两个(或三个)提交代表一个 stash 并且出于 stash 的目的被视为一个单元,并且被称为stash。这两个提交的内容——我们将忽略可选的第三个并假设你没有使用这些形式git stash——是:

  • 索引的快照,以及
  • 工作树的快照。

(因为它们是提交,所以它们也有作者和提交者——当然是你——以及日期和时间戳,以及日志消息等等。这些都是自动生成的,除了你可以将提交消息指定为隐藏消息。大多数情况下它们不是很有趣。)

请记住,索引包含建议的下一次提交。1 也就是说,索引永远不会是2 empty。只是在git checkout master您开始使用的任何命令之后或之后,索引通常与当前提交匹配。当它匹配时,git status表示没有要提交的更改。也就是说,索引中文件每个副本都与提交中相同文件的副本相匹配HEAD

即使每个文件像这样匹配,两个提交中的git stash一个仍然会有每个文件的副本。3 所以索引提交肯定不为。只是它的内容可能与HEAD提交的内容相匹配。


1在冲突合并期间,索引会扩展,并且不再作为建议的下一次提交工作。但是,在此期间,您也不能跑步git stash

2好吧,几乎从来没有:例如,当您根本没有文件时,您可以有一个空索引。

3在某个较早的提交中重用文件的每个提交实际上与该较早的提交共享该文件。因此,如果您进行任何与某个现有提交的快照完全匹配的新提交,则新提交占用的磁盘空间几乎为零——它只需要空间来保存元数据。同样,元数据是包含您的姓名、日期和时间以及日志消息等内容的部分。存储它通常只需要一个磁盘“块”,无论您的系统的块大小是多少。这可能小到 512 字节或 4KiB,具体取决于您的最小磁盘块大小——尽管还有很多其他因素也会影响这一点。

回到你的问题

如果我要存储更改,[然后]继续在同一个分支上工作(存储后)......

现在重要的是要知道在进行两次提交git stash立即执行的操作。存储文档调用这些(用于索引)和(用于工作树);我喜欢将它们称为相同的名称,但使用小写,并像这样绘制它们:IW

...--F--G--H   <-- branch (HEAD)
           |\
           i-w   <-- refs/stash

提交F,GH是分支的最后三个,是分支H的尖端。该名称 branch标识 commit H

i提交包含您index中的任何内容,这意味着它包含提交中的所有文件,被您编辑HEAD的任何文件覆盖。git add在上面的示例git status输出中,我拥有构成 Git 源的所有文件,除了我wt-status.c用新版本覆盖之外。w提交包含运行到该索引获得的内容。git add -u在这种情况下,这将保留wt-status.c我已经复制到索引的更新(并且此时在我的工作树中),并用我在工作树中的副本覆盖H-commit 副本。wt-status.h

因此,在 之后git stash,对i提交进行比较H将表明这wt-status.c是不同的,而其他一切都是相同的。区分w提交H将表明两者wt-status.c都是wt-status.h不同的。

在这一点上,最后一幕git stash是奔跑git reset --hard。这使得索引匹配H再次提交,并使我的工作树匹配提交H。所有这些文件都被跟踪(它们都在索引中),但所有三个副本都匹配:H索引中的匹配索引中的匹配,索引中的匹配我的工作树中的匹配。

... [然后] 在同一个分支上继续工作(隐藏后)...

所以现在,在你的工作树中,你有一些修改过的文件。这些文件仍然全部被跟踪——在(单一的)索引中有它们的副本——但是工作树版本没有被复制到索引中。索引与 commit 匹配H,但与工作树不匹配。

...然后 git-apply 在添加或提交存储后所做的更改之前。

git apply是一个命令,但它不使用存储。 git stash apply是一个不同的命令,尽管有些相关。我想你的意思是:如果我git stash apply现在运行,不添加或提交怎么办?

假设你意思是git stash apply,我们现在必须处理更多的复杂问题。要应用的存储是refs/stash指向的存储。这就是这i-w对提交。4可以告诉git stash apply使用两个提交,但默认情况下,它只使用w提交5

无论如何,假设没有第三次提交,git stash apply现在继续w提交。它直接运行git merge-recursive,绕过了顶级git merge命令使用的所有健全性检查(有充分的理由:例如,我们不想提交结果)。这执行了我喜欢称之为merge-as-a-verb 的操作。因此答案是:

[是否git stash apply]尝试合并?

是的,确实如此

在 Git 中,每次合并都需要三个输入。通常所有三个输入都是commits6但在这种情况下,其中一个是您的活动工作树。这有点危险,因为 merge-as-a-verb 过程会写入您的 work-tree。前端git merge命令总是坚持你有一个“干净”的工作树,所以在上面乱涂乱画不会破坏任何正在进行的工作。git merge-recursive直接运行git stash apply会绕过此安全检查。(幸运的是,有一个备用安全检查,但很难描述。)

无论如何,这正是现在发生的事情。运行的git merge-recursive那个git stash apply被告知使用w提交的父级作为合并基础。在我绘制的示例中,父提交是 commit H。此提交的文件进入索引暂存槽 1。它使用当前索引内容作为“我们的”提交,即,这些文件(从当前索引的暂存槽零)进入索引暂存槽 2。最后,它使用w提交本身作为“他们的”提交,即将这些文件放入暂存槽 3。

此时,真正复杂的合并规则开始起作用,因为 Git 现在每个文件最多有三个索引条目。如果三个都匹配,或者即使两个匹配,合并结果很容易,Git 不会在工作树副本上乱涂乱画。只有当这三个都不同时,Git 才会覆盖工作树文件。在最后一种情况下,备份安全检查开始:如果 Git 必须覆盖工作树文件,它必须匹配索引副本。如果不是,git merge-recursive它本身会中止并且什么也不做。7git stash apply因此,与您修改但从未git-add编辑过的工作树进行这种复杂的舞蹈这一 事实可能是安全的。

这个合并为动词的过程具有通常的结果:如果 Git 可以将基础版本(作为 的父级的已提交副本w)的更改合并到具有从基础版本w到当前工作树的更改的提交,您得到一个漂亮干净的合并。如果没有,您将获得写入当前工作树中的合并冲突。


4从技术上讲,名称stash指向w提交。代码从git stash提交中找到另一个提交或其他两个提交,具体取决于存储的类型w

5如果你有一个三存储提交,git stash apply无论你做什么,都坚持应用第三个提交。您可以git stash apply忽略i提交,或使用它,但如果您有第三次u提交,git stash apply 尝试应用它。这可能很烦人。我们不会在这里讨论这个。

6这很重要,因为所有提交始终是完全只读的。Git 所做的任何事情都无法覆盖这些提交的快照:它们可以永远安全地恢复,或者至少只要提交本身存在。但是请注意,这git stash drop会将提交iw存储提交扔到垃圾箱中,之后git gc可能会删除它们。

7这里有很多带有重命名检测的角和边缘情况。可能有一些遗漏的案例。经过十多年的 Git 使用,它的可能性并不大,但如果你确实遇到了一个,那么它不太可能的事实将无济于事。:-)


在将其他隐藏的更改应用到同一分支之前,我们是否应该始终提交或隐藏我们的更改?

理论上,它不应该是必需的。但由于上面显示的所有并发症,我建议这样做。

一般来说,我建议避免 git stash. 它对于偶尔的快速工作很有用,但如果事实证明您需要做的事情毕竟很复杂,那么git stash所做的快速工作可能是不合适的。

如果您确实有一些想要快速和简单的东西,但事实证明它很复杂,那么有一个很好的解决方案。首先,提交或隐藏你在中间的任何东西。(如果您使用git stash,请记住它可能会变得复杂!)然后,而不是git stash apply使用git stash branch

git stash branch命令采用分支名称。它将使用此名称创建一个分支。记住我们如何绘制上面的 stash,然后考虑这张图片:

         L--M   <-- branch2
        /
       /           N--O   <-- branch3 (HEAD)
      /           /
...--F--G--H--J--K--P   <-- branch1
           |\
           i-w   <-- refs/stash

在这里,有人(可能是我们自己)做了一个 stash,但随后忘记了它并进行了更多的提交。不能保证 commit w(与其 parent 相比H)可以干净地应用于任何commit MOP. 但我们可以将其转换为自己的分支,而不是尝试将其w作为stashgit stash branch处理,使用:

git stash branch xyzzy

这会找到i-wstash(通过refs/stash),找到提交H——这很容易,因为w直接指向H——并让新的分支名称到那里。所以现在分支名称xyzzy标识了 commit H

我们(或git stash)现在签出 commit H。然后我们应用索引提交i和工作树提交w,使用git stash apply --index. 由于iw由 制成 H这些都顺利进行。现在git stash branch放下藏匿处;一切准备就绪,我们可以运行git commit以在适当的情况下从索引进行提交,然后git add -u再次运行并提交(或第一次在新分支上)。结果是两次提交,如果我们先提交索引,或者一次提交,我们可以这样绘制:

         L--M   <-- branch2
        /
       /           N--O   <-- branch3
      /           /
...--F--G--H--J--K--P   <-- branch1
            \
             I   <-- xyzzy (HEAD)

我们有一个普通的提交(或者可能两个,但我只画了一个),而不是一个难以处理的存储。我们所有的普通工具都适用于这个提交(或这些提交)。

于 2020-02-08T21:28:54.777 回答
0

有些仅被跟踪但留在工作区中而不被添加到索引中

在这种情况下你不能使用git stash apply,因为

工作目录必须与索引匹配。

https://git-scm.com/docs/git-stash

于 2020-02-08T19:55:39.160 回答