我想隐藏未跟踪的文件,但我继续传递错误的选项.对我来说这听起来是对的:
git stash save [-a|--all]
Run Code Online (Sandbox Code Playgroud)
但实际上这也隐藏了文件.正确的是:
git stash save [-u|--include-untracked]
Run Code Online (Sandbox Code Playgroud)
当我运行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
Run Code Online (Sandbox Code Playgroud)
所以命令失败了.
如何恢复跟踪和未跟踪的更改?git reflog不存储存储命令.
tor*_*rek 25
您需要清理目录(以git clean术语表示)才能正确应用存储.这意味着运行git clean -f,甚至git clean -fdx,这是一件非常丑陋的事情,因为一些未跟踪或未跟踪和忽略的文件/目录可能是您要保留的项目,而不是完全删除.(如果是这样,你应该将它们移到工作树之外,而不是将git clean它们移走.请记住,git clean删除的文件正是那些你无法从Git中获取的文件!)
要了解原因,请查看"应用"说明中的第3步.请注意,没有选项可以跳过存储中未跟踪和/或忽略的文件.
当您使用git stash save其中任何一个-u或时-a,存储脚本将其"存储包"写为三个提交而不是通常的双父提交.
从图形来看,就提交图而言,"存储包"通常看起来像这样:
o--o--C <-- HEAD (typically, a branch)
|\
i-w <-- stash
Run Code Online (Sandbox Code Playgroud)
该os为任何旧的普通提交节点,是C.节点C(用于提交)有一个字母,所以我们可以命名它:它是"藏匿袋"悬挂的地方.
存储袋本身是悬挂的小三角形包C,它包含两个提交:w是工作树提交,i是索引提交.(未显示,因为它很难绘制,是w第一个父亲是C第二个父母的事实i.)
有--untracked或者--all有第三个父级w,所以图表看起来更像这样:
o--o--C <-- HEAD
|\
i-w <-- stash
/
u
Run Code Online (Sandbox Code Playgroud)
(这些图确实需要是图像,以便它们可以有箭头,而不是ASCII技术,其中箭头很难包含).在这种情况下,stash是提交w,stash^提交C(仍然是HEAD),stash^2提交i和stash^3提交u,其中包含"未跟踪"或甚至"未跟踪和忽略"的文件.(这不是真正重要的是,据我所知道的,但我会在这里补充一点,i有C作为父提交,同时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提交读出到新的临时区域,然后有效地运行1git add -i --patch,以便此索引随您选择的修补程序而结束.如果没有-p,它只是将工作目录与stashed索引区分开以查找已更改的文件.2 在任何一种情况下,它都会从临时索引中写入一个树对象.该树将是提交树w.
作为最后一个存储创建步骤,脚本使用刚保存的树,父提交C,索引提交和未跟踪文件的根提交(如果存在),以创建最终的存储提交w.然而,脚本则需要几个会影响您的更多步骤的工作目录,这取决于您是否使用-a,-u,-p,和/或--keep-index(和记住-p暗示--keep-index):
用-p:
"反向修补"工作目录以删除HEAD存储和存储之间的区别.从本质上讲,这会使工作目录只保留那些没有被隐藏的更改(特别是那些不在提交中的更改w; i这里忽略了提交中的所有内容).
只有你指定--no-keep-index:run git reset(根本没有选项,即git reset --mixed).这样可以清除所有内容的"待提交"状态,而无需更改任何其他内容.(当然,您在运行之前git stash save -p使用git addor进行的任何部分更改都会git add -p保存在提交中i.)
没有-p:
运行git reset --hard(-q如果你也指定了).这会将工作树设置回提交中的状态HEAD.
只有在您指定-a或-u:运行时git clean --force --quiet -d(使用-xif -a或不使用if -u).这将删除所有未跟踪的文件,包括未跟踪的目录; 使用-x(即在-a模式下),它还会删除所有被忽略的文件.
仅当您指定-k/ --keep-index:用于git read-tree --reset -u $i_tree"将"存储索引"恢复"为"工作树中也将提交的更改".(--reset由于步骤1清除了工作树,因此应该没有效果.)
apply的时间恢复存储的两个主要子命令是apply和pop.该pop代码只是运行apply,然后,如果apply成功,运行drop,因此,实际上,有真的只是apply.(嗯,还有branch,这有点复杂 - 但最终,它也使用apply.)
当您应用存储 - 任何"存储类对象"时,实际上,即存储脚本可以视为存储包的任何内容 - 只有两个存储特定选项:
-q, --quiet--index(不是--keep-index!)其他标志已累积,但无论如何都会立即被忽略.(使用相同的解析代码show,此处将其他标志传递给git diff.)
其他所有内容都由存储包的内容以及工作树和索引的状态控制.如上所述,我将使用标签w,i并u表示存储中的各种提交,并C表示存储包挂起的提交.
该apply顺序是这样的,假设一切顺利的话(如果事情早发生故障,例如,我们是在合并的中间,或git apply --cached失败,脚本错误出在这一点上):
--index:diff提交i提交C,管道git apply --cached,保存结果树,并用于git reset取消它u存在时:使用git read-tree并git checkout-index --all使用临时索引来恢复u树git merge-recursive将C("基础")的树与步骤1中写入的树("更新上游")和树中的w("存储更改")合并在此之后它变得有点复杂:-)因为它取决于步骤4中的合并是否顺利.但首先让我们稍微扩展一下.
第1步非常简单:脚本刚刚运行git write-tree,如果索引中存在未合并的条目,则会失败.如果写树工作,则结果是树ID($c_tree在脚本中).
第2步是更复杂的,因为它不仅检查--index选项而且还检查$b_tree != $i_tree(即,树C和树之间存在差异i),以及$c_tree!= $i_tree(即,树之间存在差异在步骤1中,以及树的i).测试$b_tree != $i_tree是有道理的:它正在检查是否有任何更改要应用.如果没有变化 - 如果i匹配的树C没有要恢复的索引,并且--index根本不需要.但是,如果$i_tree匹配$c_tree,则仅表示当前索引已包含要通过恢复的更改--index.确实,在这种情况下,我们不希望git apply这些变化; 但我们确实希望它们保持"恢复"状态.(也许这是我在下面不太了解的代码的重点.但是,这似乎更有可能是这里有一个小错误.)
在任何情况下,如果需要运行第2步git apply --cached,它也会运行git write-tree以编写树,将其保存在脚本的$unstashed_index_tree变量中.否则$unstashed_index_tree留空.
第3步是"不干净"目录中出错的地方.如果u存储中存在提交,则脚本会坚持提取它,但git checkout-index --all如果任何这些文件被覆盖,则会失败.(请注意,这是通过临时索引文件完成的,该文件将在以后删除:步骤3根本不使用正常的暂存区域.)
(步骤4使用了三个我没有看到的"魔术"环境变量:提供要合并的树的"名称".要运行,脚本提供四个参数:.如前所述,这些是基本提交的树,该指数在启动-OF- 和藏匿的工作承诺,要为每个树得到串名字,看起来在前面加上组成的名称环境到原始SHA-1的每棵树,脚本不传递任何策略参数,也不允许你选择除了之外的任何策略.可能它应该.)$GITHEAD_tgit merge-recursive$b_tree -- $c_tree $w_treeCapplywgit merge-recursiveGITHEAD_git merge-recursiverecursive
如果合并有冲突,则存储脚本运行git rerere(qv),如果--index,则告诉您索引未恢复并以合并冲突状态退出.(与其他早期退出一样,这可以防止pop丢失藏匿物.)
如果合并成功,但是:
如果我们有$unstashed_index_tree-ie,我们正在做--index,并且第2步中的所有其他测试都已通过 - 那么我们需要恢复在步骤2中创建的索引状态.在这种情况下,一个简单的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/nullfor -q模式;不确定它为什么一直运行-q).
git stash branch如果您在应用存储时遇到问题,可以将其转换为"真正的分支",这样可以保证还原(除非您通常u清除包含提交不存在的存储问题,除非您清除unstaged甚至可能首先忽略文件).
这里的技巧是从签出提交开始C(例如,git checkout stash^).这当然会导致"分离的HEAD",因此您需要创建一个新分支,您可以将其与检出提交的步骤结合使用C:
git checkout -b new_branch stash^
Run Code Online (Sandbox Code Playgroud)
现在你可以应用存储,即使有--index,它应该工作,因为它将应用于存储袋挂起的同一个提交:
git stash apply --index
Run Code Online (Sandbox Code Playgroud)
此时,任何较早的分阶段更改都应再次暂存,并且任何早期未分阶段(但已跟踪)的文件将在工作目录中进行未分阶段但已跟踪的更改.现在放下藏匿物是安全的:
git stash drop
Run Code Online (Sandbox Code Playgroud)
使用:
git stash branch new_branch
Run Code Online (Sandbox Code Playgroud)
只需为您完成上述顺序.字面上运行git checkout -b,如果成功,则应用存储(with --index)然后删除它.
完成此操作后,您可以提交索引(如果您愿意),然后添加并提交其余文件,使两个(或者如果您省略第一个,索引,提交一个)"常规"提交"常规" "分支:
o-o-C-o-... <-- some_branch
\
I-W <-- new_branch
Run Code Online (Sandbox Code Playgroud)
你已经转换了存储袋i并w提交普通的分支机构提交I和W.
1更准确地说,它运行git add-interactive --patch=stash --,它直接调用perl脚本进行交互式添加,并使用特殊的魔术集进行存储.还有一些其他魔法--patch模式; 看脚本.
2这里有一个非常小的错误:git将$i_tree已提交索引的树读入临时索引,但随后将工作目录区分开来HEAD.这意味着,如果你改变了一些文件f索引,然后改了回来,以配合HEAD修订,下存储的工作树w中藏袋包含索引的版本f,而不是工作树版本f.