gpt4 book ai didi

git - 如何从“git stash save --all”中恢复?

转载 作者:行者123 更新时间:2023-12-05 01:13:35 25 4
gpt4 key购买 nike

我想隐藏未跟踪的文件,但我一直传递错误的选项。对我来说,这听起来不错:

git stash save [-a|--all]


但这实际上也会隐藏忽略的文件。正确的是:

git stash save [-u|--include-untracked]


当我运行 git stash save -a并尝试 git stash pop时,对于所有被忽略的文件,我都会收到无数错误:

path/to/file1.ext already exists, no checkout
path/to/file1.ext already exists, no checkout
path/to/file1.ext already exists, no checkout
...
Could not restore untracked files from stash


因此命令失败。

如何找回已追踪和未追踪的隐藏变更? git reflog不存储隐藏命令。

最佳答案

TL; DR版本:

您需要清洁目录(用git clean表示),以便正确应用存储。这意味着运行git clean -f或什至git clean -fdx这是一件很丑的事情,因为某些未跟踪或未被跟踪且忽略的文件/目录可能是您要保留的项目,而不是删除完全。 (如果是这样,则应将它们移到工作树之外,而不要git clean-将其移开。请记住,git clean删除的文件正是您无法从Git取回的文件!)

若要查看原因,请查看“应用”描述中的步骤3。请注意,没有选项可以跳过存储中未跟踪和/或忽略的文件。

关于隐藏本身的基本事实

当将git stash save-u-a一起使用时,隐藏脚本会将其"stash bag"作为三父提交而不是通常的双父提交写入。

用提交图的形式,“存储袋”通常看起来像这样:

o--o--C     <-- HEAD (typically, a branch)
|\
i-w <-- stash


o是任何旧的普通提交节点, C也是如此。节点 C(用于Commit)有一个字母,因此我们可以为其命名:这是“存储袋”的悬挂位置。

储物袋本身是悬挂在 C上的三角形小袋,它包含两个提交: w是工作树提交,而 i是索引提交。 (未显示,因为很难绘制,是 w的第一个父级是 C而第二个父级是 i的事实。)

使用 --untracked--all时, w有第三个父级,因此该图看起来更像这样:

o--o--C     <-- HEAD
|\
i-w <-- stash
/
u


(这些图实际上必须是图像,以便它们可以具有箭头,而不是难以包含箭头的ASCII艺术)。在这种情况下, stash是提交 wstash^是提交 C(还是 HEAD), stash^2是提交 i,而 stash^3是提交 u,其中包含“未跟踪”甚至“未跟踪和忽略”的文件。 (据我所知,这实际上并不重要,但是我要在这里添加 iC作为父提交,而 u是无父母或根提交。似乎没有这样做的特殊原因,只是脚本的工作方式,但它解释了为什么“箭头”(线)与图中的一样。)

save时的各种选项

在保存时,您可以指定以下任何或所有选项:


-p--patch
-k--keep-index--no-keep-index
-q--quiet
-u--include-untracked
-a--all


其中一些暗示,替代或禁用其他。例如,使用 -p完全更改了脚本用于构建存储的算法,并且还打开了 --keep-index,如果不希望使用 --no-keep-index则将其关闭。它与 -a-u不兼容,如果给出任何一个,将出错。

否则,将保留 -a-u之间的最后一个。

此时,脚本将创建一个或两个提交:


一个用于当前索引(即使它不包含任何更改),其父提交为 C
-u-a一起,是一个无父母的提交,其中包含(仅)未跟踪的文件或所有(未跟踪和忽略的)文件。


然后, stash脚本保存您当前的工作树。它使用一个临时索引文件(基本上是一个新的暂存区)来执行此操作。使用 -p,脚本将 HEAD提交读出到新的暂存区域中,然后有效地运行 git add -i --patch,这样该索引就会随着您选择的补丁而结束。如果没有 -p,它只会将工作目录与隐藏的索引进行比较,以查找更改的文件。2无论哪种情况,它都会从临时索引中写入树对象。该树将成为提交 w的树。

在脚本的最后一个存储创建步骤中,该脚本使用刚刚保存的树,父提交 C,索引提交以及未跟踪文件的根提交(如果存在)创建最终的存储提交 w。但是,脚本随后会根据影响您的工作目录的位置执行其他几个步骤,具体取决于您使用的是 -a-u-p和/或 --keep-index(并且请记住, -p表示 ):


使用 --keep-index


“反向修补”工作目录以除去 -p和存储之间的差异。从本质上讲,这将使工作目录仅保留未更改的更改(特别是那些不在提交 HEAD中的更改;此处忽略提交 w中的所有内容)。
仅当您指定 i时:运行 --no-keep-index(根本没有任何选项,即 git reset)。这将清除所有内容的“待提交”状态,而不更改其他任何内容。 (当然,在运行 git reset --mixed之前使用 git stash save -pgit add进行的任何部分更改都保存在提交 git add -p中。)

没有 i


运行 -p(如果也指定了 git reset --hard,请运行 -q)。这会将工作树设置回 HEAD提交中的状态。
仅当指定 -a-u时:运行 git clean --force --quiet -d(如果 -x,则使用 -a;如果 -u,则不使用)。这将删除所有未跟踪的文件,包括未跟踪的目录;使用 -x(即在 -a模式下),它还会删除所有忽略的文件。
仅当您指定 -k / --keep-index时:使用 git read-tree --reset -u $i_tree将隐藏的索引“带回”为也将出现在工作树中的“要提交的更改”。 (自第1步清除工作树以来, --reset应该无效。)



apply时的各种选项

恢复隐藏的两个主要子命令是 applypoppop代码仅运行 apply,然后,如果 apply成功,则运行 drop,因此实际上实际上只有 apply。 (嗯,还有 branch,它稍微复杂一些,但最后它也使用 apply。)

实际上,当您应用存储时,任何“类似于存储物的对象”,即存储脚本可以将其视为存储袋的任何内容,只有两个特定于存储的选项:


-q--quiet
--index(不是 --keep-index!)


其他标志已累积,但是无论如何都会被立即忽略。 ( show使用相同的解析代码,此处其他标志传递给 git diff。)

其他所有内容都由存储袋的内容以及工作树和索引的状态控制。如上所述,我将使用标签 wiu表示存储中的各种提交,并使用 C表示存储袋挂起的提交。

apply序列是这样的,假设一切顺利(如果某件事很早就失败了,例如,我们处于合并的中间,或者 git apply --cached失败了,脚本将在此时出错):


将当前索引写入树中,确保我们不在合并中间
仅当 --index:与commit i进行diff commit C,通过管道传输到 git apply --cached,保存生成的树,并使用 git reset取消登台时
仅当 u存在时:将 git read-treegit checkout-index --all与临时索引一起使用以恢复 u
使用 git merge-recursiveC的树(“基础”)与步骤1中写的树(“更新的上游”)和 w中的树合并(“隐藏的更改”)


在这之后,它变得有点复杂:-),这取决于步骤4中的合并是否顺利。但是首先让我们将以上内容扩展一下。

步骤1非常简单:脚本仅运行 git write-tree,如果索引中存在未合并的条目,则脚本将失败。如果写树有效,则结果为树ID(脚本中的 $c_tree)。

步骤2更加复杂,因为它不仅检查 --index选项,而且还会检查 $b_tree != $i_tree(即, C的树与 i的树之间存在差异)和 != $c_tree(即,在步骤1中写出的树与 $i_tree的树之间存在差异)。 i的测试很有意义:它正在检查是否有任何要应用的更改。如果没有任何变化(如果 $b_tree != $i_tree的树与 i的树匹配),则没有要还原的索引,并且根本不需要 C。但是,如果 --index$i_tree匹配,则仅意味着当前索引已包含要通过 $c_tree恢复的更改。的确,在这种情况下,我们不想 --index进行这些更改。但我们确实希望他们保持“恢复”状态。 (也许这就是我在下面不太了解的代码的重点。不过,这里似乎更可能有一个小错误。)

无论如何,如果第2步需要运行 git apply,它也将运行 git apply --cached编写树,并将其保存在脚本的 git write-tree变量中。否则, $unstashed_index_tree将留空。

步骤3是“不干净”目录中出问题的地方。如果 $unstashed_index_tree提交存在于存储中,脚本将坚持将其提取,但是如果其中任何一个文件被覆盖,则 u将失败。 (请注意,这是通过一个临时索引文件完成的,此文件随后将被删除:步骤3完全不使用常规暂存区。)

(第4步使用了三个我没有见过的“魔术”环境变量: git checkout-index --all提供了要合并的树的“名称”。要运行 $GITHEAD_t,该脚本提供了四个参数: git merge-recursive $b_tree < cc> --。如前所述,这些是基本提交 $c_tree,开始索引的 $w_tree和隐藏的工作提交 C的树。在这些树中, apply在环境中查找通过在每个树的原始SHA-1前面加上 w形成的名称,该脚本不会将任何策略参数传递给 git merge-recursive,也不允许您选择除以下以外的任何策略 GITHEAD_。也许应该。)

如果合并有冲突,则隐藏脚本将运行 git merge-recursive(q.v.),如果 recursive,则将告诉您索引尚未还原,并以合并冲突状态退出。 (与其他早期出口一样,这防止 git rerere放下藏匿处。)

但是,如果合并成功,则:


如果我们有一个 --index,即我们正在做 pop,并且步骤2中的所有其他测试也都通过了,那么我们需要恢复在步骤2中创建的索引状态。在这种情况下,一个简单的 $unstashed_index_tree (没有选项)可以解决问题。
如果 --index中没有东西,脚本将使用 git read-tree $unstashed_index_tree查找要添加的文件,运行 $unstashed_index_tree对原始保存的索引进行单树合并,然后使用文件名进行 git diff-index --cached --name-only --diff-filter=A $c_tree来自早期的 git read-tree --reset $c_tree。我不太确定为什么要达到这些长度( git update-index --add手册页中有一个提示,即避免对修改后的文件造成错误命中,这也许可以解释这一点),但这就是它的作用。


最后,脚本运行 diff-index(对于 git-read-tree模式,输出发送到 git status;不确定为什么在 /dev/null下运行)。

关于 -q的几句话

如果您在应用存储时遇到麻烦,可以将其转换为“真实分支”,以确保可恢复(除非像往常一样,否则包含提交 -q的存储的问题不适用,除非您清除未暂存的文件,甚至可能先忽略文件)。

这里的技巧是从检出提交 git stash branch(例如, u)开始。当然,这会导致“分离的HEAD”,因此您需要创建一个新分支,可以将其与检出commit C的步骤结合使用:

git checkout -b new_branch stash^


现在,即使使用 git checkout stash^,您也可以应用存储,并且它应该工作,因为它将应用于存储袋挂起的相同提交:

git stash apply --index


此时,任何较早已暂存的更改都应再次暂存,并且任何较早未暂存(但已跟踪)的文件将在工作目录中具有未暂存但未跟踪的更改。现在放下藏书是安全的:

git stash drop


使用:

git stash branch new_branch


只需为您完成上述顺序即可。它从字面上运行 C,如果成功,则应用存储(带有 --index),然后将其删除。

完成此操作后,您可以提交索引(如果需要),然后添加并提交其余文件,以使两个(如果要删除第一个索引,则提交一个)“常规”在“常规”上提交”分支:

o-o-C-o-...   <-- some_branch
\
I-W <-- new_branch


并且您已将存储袋 git checkout -b--index提交转换为普通的分支提交 iw



1更正确地,它运行 I,它直接调用perl脚本进行交互式添加,并为存储设置了特殊的魔术。还有其他一些魔术 W模式;参见脚本。

2这里有一个非常小的错误:git将已提交索引的树 git add-interactive --patch=stash --读取到临时索引中,然后将工作目录与 --patch进行比较。这意味着,如果您更改了索引中的某些文件 $i_tree,然后又将其更改回以匹配 HEAD版本,则存储在存储袋中 f下的工作树将包含索引版本 HEADw的工作树版本。

关于git - 如何从“git stash save --all”中恢复?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20586009/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com