"TL;DR"部分:小心盲目执行 git stash save && git checkout ... && git stash pop
。
特别是在使用git的命令行界面时,你可以将其拆分成几个部分。
特别是,“当前分支”,如果有的话,只是一个记录在文件中的项目(包含HEAD
引用的文件,.git/HEAD
文件)。查看该文件的原始内容,通常会看到ref: refs/heads/master
等。(在“分离的HEAD”模式下,您将看到原始SHA-1。)有一些低级git命令可以更新HEAD
而不进行任何其他操作。
然而,大多数人使用git checkout
来切换分支,除了是正确的方法 :-)之外,还内置了许多保护功能。主要的是,如果进行这种切换时,您有工作树或“索引”(也称为缓存)修改将被丢失,它将拒绝切换分支。假设您在分支A
上,并要求切换到分支B
。检出过程必须:
- 获取在分支
A
的末尾提交中存在的所有文件列表。
- 获取在分支
B
的末尾提交中存在的所有文件列表。
- 对于第一个列表中不存在于第二个列表中的文件,从工作目录中删除这些文件。
- 对于第二个列表中不存在于第一个列表中的文件,将这些文件添加到工作目录中。
- 对于两者都存在的文件,仅当它们不同时才替换工作目录内容。
此外,检出是通过“写入”缓存完成的:如果文件F
在A
和B
中不同,则首先将B
版本的内容复制到索引/缓存中,然后写入工作目录。
如果您已经使用git add
将某个修改提交到文件F
中,或者您在工作目录中有一些未提交的修改,这个检出过程会覆盖这些已提交或未提交的更改,因此git checkout
会停止并显示错误信息。如果要删除文件F
,那么也会覆盖(或者更准确地说,删除)您的更改,因此checkout
也会停止。
另一方面,如果文件F
在两个提交中是相同的,checkout
就可以进行:它只会保留未提交的更改,不管它们是已经提交或未提交的。这就是为什么有时候,但并不总是,你可以简单地使用git checkout
切换到你想要工作的分支。
Git的“stash”(如
git stash
)与分支无关。这里的关键概念是,每个stash - 你可以同时有多个活动的 - 实际上都是一个提交(或更准确地说,一组提交:根据你stash的内容而定,可能是两个或三个)。虽然提交是在分支上进行的,但在这里,我们必须再做一些区分。具体来说,“branch”这个词在Git中有两种或三种不同的含义。
提交总是(必须)进入“提交图”,因为该图形只是由所有提交及其边缘形成的东西。就“分支”的含义而言,这些stash提交位于提交图的某个分支上。但是,“branch”这个词也指标识分支“尖端”提交的名称,在这里,这些stash提交不会推进分支尖端。 (有关“branch”多重含义的更多详细信息,请参见Jubobs的
此帖子。)
git stash
命令查看当前索引/缓存和工作树状态,并从它们创建新的提交,
如果 - 这个“如果”非常重要 -
如果 它们具有未保存的暂存或未暂存更改。其中一个新提交(可以从其他提交中找到)保存在特殊的引用名称
stash
下。然而,这些新提交都没有添加到当前分支上,因此它们不属于任何命名分支。从这个意义上说,它们与当前分支无关 - 但是因为它们是提交,所以它们具有父提交ID,并且从这个特定意义上讲,它们直接附加到
save
时生效的提交。
对于终端用户来说,这意味着什么通常是“无所谓”的:你可能不关心,也不需要关心。但是,如果您曾经使用
git stash branch
将stash转换为分支,则意味着新分支将从stash附加到的提交处分叉。(实际上,这通常正是您想要的。)
定义一个别名或宏,执行
git stash save && git checkout ... && git stash pop
的一个风险是,第一步
git stash save
可能什么也不做。
如果它什么也没做 - 如果它没有将新的stash推送到“stash堆栈”上 - 那么它仍会成功,并且您的别名或宏将继续检出其他分支,然后(尝试)从stash堆栈中弹出一个stash。
如果在该堆栈上有另一个(不同的)stash,您打算在其他地方使用它,则刚刚尝试将其弹出到您刚刚切换到的分支中。
请注意,这里有两个嵌套的“if”条件,只有这两个条件都满足时,才会出现特定的错误:
- 您需要没有暂存内容,
- 您需要有一些现有的暂存内容,您不想将其弹出。
解决此问题的一种方法是使用脚本,而不仅仅是简单的 git 别名来完成分支切换和暂存序列。在脚本中,您按照惯例运行 git stash,但在暂存之前和之后,检查 stash 引用解析为的 SHA-1,如果有的话。如果更改了此值,则
git stash save
已经保存了某些内容,因此有东西可以弹出,并且可以继续执行 checkout-and-pop 序列。如果它没有更改——如果既没有暂存内容,也没有栈顶上的暂存内容——那么就没有要弹出的内容,您应该只进行 checkout 操作。
这里还有另一个可能出现问题的 bug; 请参阅
此答案,针对一个略有不同的问题,其中包括一些表达上述“仅在 save 实际推送内容时才弹出”的 shell 代码。