显然我是一个有git的菜鸟。我一直在做一些重大的改变。我还没有在当前分支上进行任何提交,因为我认为这也会将这些更改提交到我的主分支。所以我读了"git stash save",我知道这会保存对当前分支的更改而不会影响主分支。当我尝试此操作时,我收到以下消息:
Saved working directory and index state WIP on assignment-join: 92cc8f1 Wrong arguments error
HEAD is now at 92cc8f1 Wrong arguments error.
令我惊恐的是,我在当前分支上的所有工作都已重置为分支的创建。
有什么方法可以撤消此操作吗?
另外,在不影响主节点的情况下保存当前分支的正确方法是什么?
谢谢你的时间。
显然我是一个有 git 的菜鸟。
每个人都有一段时间。 (我想,除非他们根本不使用它。
我一直在做一些重大的改变。我还没有在当前分支上进行任何提交,因为我认为这也会将这些更改提交到我的主分支。
我希望你现在已经看到,这是错误的——但我怀疑你仍然有一些关于 Git 不了解的东西,这是很自然的,因为 Git 非常奇特。
所以我读了"git stash save",我知道这会保存对当前分支的更改而不会影响主分支。
不幸的是,这不是它所做的。 要准确理解它的作用需要大量的 Git 知识,但你现在需要的是五分钟(或者 15 分钟)的快速介绍。
正如其他人已经回答的那样,要在您做完之后立即取消它,您可以git stash pop
. 但是有一些微妙之处,值得轻描淡写。
git stash save
所做的是提交。 然后,在进行这些提交后,它会运行git reset --hard
,以抛弃您的更改(现在安全地保存在提交中)。 (您可以使用标志将其中一些行为更改为git stash save
。
不过,关于提交git stash save
保存有两个特殊之处:
-
他们不在任何分支上。
-
其中不止一个。 事实上,至少有两个(如果你在
git stash save
命令中添加某些标志,还有第三个;但请不要:-))。虽然我们将远离所有血腥的细节,但值得考虑这两个是什么,因为当您进行自己的提交时,它们很重要。
这又回到了:
另外,在不影响主节点的情况下保存当前分支的正确方法是什么?
答案是:"提交"。
提交前需要了解的内容
首先,如果你习惯了任何其他版本控制系统,Git 的分支方法非常奇怪。
其次,Git 不是从工作树进行提交,而是从 Git 的索引进行提交,Git 也称之为暂存区。 我们稍后将定义这些术语。
第三,每个提交都有一个"真实名称",这是你毫无疑问见过的那些丑陋的大哈希ID之一,92cc8f1...
和badbead...
等等。 这些是不可能记住的(尽管如果需要,你可以剪切和粘贴它们),所以 Git 让我们以各种名称保存某些哈希 ID。 在很大程度上,这就是分支名称,如master
或featureX
:它是提交的名称。
最后,这是提交是什么/包含:
- 它有一个作者和提交者(你的姓名和电子邮件地址,两次;额外的一个是你提交别人的工作的情况,例如,如果他们通过电子邮件向您发送补丁)。
- 它具有该哈希 ID 真实名称。
- 它存储当前在索引中的每个文件的完整,完整版本。 (但它通过花哨的压缩和秘密重用其他提交的现有存储文件来实现,因此这几乎不占用任何空间。
- 它存储您的提交日志消息。
- 也许最棘手的是,它存储父提交的哈希 ID。 (事实上,它可以存储多个父哈希,但我们在这里忽略这一点——它适用于合并提交。
正是这些父提交 ID 构成了 Git 中的历史记录。 事实上,每个提交都"指向"其父级(保存其父级哈希 ID),这使得 Git 可以向你显示分支中的所有提交。 分支名称本身只是最近提交的名称。
可视化(部分)提交图
让我们绘制一个只有master
分支的小存储库:
A <-B <-C <--master
在这里,名称master
记住了第三次提交的 ID(我称之为C
而不是写出一些难以理解的哈希 ID)。 我们说这个名字master
指向提交C
。
但请注意,是 commitC
记住了 commitB
的 ID:B
是C
的父级;我们说C
指向B
。 同样,B
点回到A
。
A
是第一次提交。 它没有什么可以指向的,所以它有点特别。 它只是不指向任何地方。 我们称之为根提交,而无处可去的事实让 Git 停止倒退。
换句话说,Git 基本上总是向后工作。 我们从分支的最新提交或提示开始,我们使用分支名称找到它。 该提交为我们提供了其父级的 ID,从而为我们提供了另一个父级,依此类推。
绘制所有这些内部箭头是一种痛苦,所以我们不要打扰。 我们知道提交只记得他们的父母,而不是他们的孩子。 我们只能向后移动,在这些图纸中,我们总是从右边开始,向左走:
A--B--C <-- master
不过,我将分支箭头保留在绘图中,因为这些分支名称的关键在于,当我们添加新提交时,它们会移动。 让我们添加一个新的提交D
:
A--B--C--D
新的提交D
指向C
。 但是什么指向D
? 好吧,如果我们在master
分支上,它是名称master
:
A--B--C--D <-- master
但是我们可以在不同的分支上。 让我们创建一个新的分支test
,并让master
和test
最初指向C
:
A--B--C <-- master, test (HEAD)
现在让我们像git status
所说的那样"进行分支测试",并使我们的新提交D
。 以下是绘制方法:
A--B--C <-- master
D <-- test (HEAD)
和以前一样,D
点回到C
。 但这一次改变的不是master
,而是test
. 这一次,test
现在指向新的提交。
这就是分支在 Git 中的增长方式:我们进行一个新的提交,它指向当前的分支提示。 然后,我们使当前分支指向新提交。 但是我们怎么知道哪个分支是我们当前的分支呢? 嗯,这就是图纸中HEAD
的东西:提醒,"这是我们当前的分支"。 (如果我们只绘制一个分支,我们不需要额外的注释,尽管它并没有真正受到伤害。
这也是为什么在某个分支上进行新提交会使其他分支不受影响的原因:所有其他分支名称仍然指向其原始提交。 一旦提交完成,在 Git 中,它就永远无法更改。
(您最终会看到命令git commit --amend
,这似乎改变了当前的提交。 它没有,真的:它只是进行一个新的提交,就像我们D
一样。 只是它把当前的提交推开了,而不是在最后添加新的提交:
...--E--F--G <-- branch [before --amend]
成为:
G [old branch tip, now abandoned]
/
...--E--F--H <-- branch [after --amend]
Git 中的所有提交都是如此:一旦进入,它们就永远无法更改——但你可以通过撕掉它们的所有分支名称来放弃它们。 这最终允许 Git "垃圾收集"不需要的提交。 即使这样,也有一堆秘密保护,以确保它们持续一段时间。
关于工作树和索引/暂存区域的知识
存储在 Git 提交中的文件是"安全的":每个提交都有自己的该文件保存版本的副本(尽管 Git 根据需要在所有提交之间神奇而安全地共享它们)。 但是,它们的形式只对 Git 本身有用:zlib 放气,用于压缩,甚至可能与其他文件"打包"以进行更多压缩。 如果 Git 有任何用途,我们需要一个地方,我们可以以正常形式拥有文件,以便我们可以处理它们。
工作树是 Git 以正常格式写入文件的地方。 因此,工作树是(并且不出所料)您工作的地方。 这部分非常简单,但当然有一个转折点:很多时候,我们希望在我们的工作树中提交我们不想提交的文件——我们不想永远保存每个版本的文件。
在任何其他非 Git 版本控制系统中,我们会将这些文件标记为"不提交",我们就会完成,但 Git 不同。 Git 给了我们这个额外的东西,暂存区域,也称为索引。
索引的简短描述是,它是您构建下一次提交的地方。在运行git commit
之前,您必须将文件从工作树复制到索引中。 一旦索引中有文件的一个版本,该版本将进入下一次提交。 如果再次更改工作树中的文件,则必须再次将其复制回索引。
这似乎——坦率地说,经常是——只是屁股上的一个大痛。 为什么当我们转到索引时,Git 不能自动将文件复制到索引中git commit
? 实际上,它可以,但让我们推迟一下。 您必须手动将每个更新的文件git add
索引中,以便在提交之前暂存它,这一事实让我们避免添加我们不想保存的工作树文件。
已在索引中的文件,我们称之为跟踪。 因此,尚未在索引中但在工作树中的文件将取消跟踪。
git status
命令会将索引/暂存区域与工作树进行比较,并告诉您哪些文件已暂存(在索引中并准备提交)、未暂存(在索引中,但与工作树不匹配)和未跟踪(不在索引中,但在工作树中)。 不过,首先,git status
将当前提交与索引进行比较:索引中的文件,但与当前提交中的文件匹配,它就会关闭。 他们仍然被跟踪;它们将在下一次提交中保存;但它们与已经提交的内容相同。
(当然,所有关于构建工件文件被"取消跟踪"的不断抱怨都很烦人。 所以这就是.gitignore
的用武之地:你可以在那里列出这样的文件,告诉 Git:"是的,我知道这些文件没有被追踪,不要再抱怨它们了。 请注意,在.gitignore
中列出文件并不会让它无法跟踪,它只会让 Git 闭嘴。 正是等式中的"不在索引中"部分实际上使文件无法跟踪。
这就是为什么git stash save
进行两次提交的原因
所以,我们看到 Git 有这个暴露的"索引"/"暂存区域"的东西,并让你在那里建立你的下一个提交。 它还有您的工作树,您可以在其中处理文件。 您可以对某些已暂存的文件进行一些更改,并对尚未暂存的其他文件进行更多更改。
git stash save
操作旨在保存所有这些挂起的更改,然后从索引和工作树中删除它们。 所以它需要进行两次提交:一次用于索引,一次用于工作树。 如果您已仔细暂存文件README.txt
但尚未暂存pending
并希望 Git 记住这一点,git stash save
会记住这一点。
但是,通常您不会这样做。 所以git stash apply
默认情况下,不会在索引和工作树之间保持这种仔细的分离。 有一种方法可以保留它,当您执行apply
步骤时,您应该做出此决定(是否仔细暂存索引)。
请注意,git stash pop
只是意味着git stash apply && git stash drop
:立即应用存储的提交,如果成功(此步骤可能会失败),则还要删除存储。 幸运的是,你的git stash apply
会成功,这里不会有失败的混乱后果。
何时以及为何应该使用提交而不是存储
如果你想在分支上保存东西,你应该提交它们。 这做了一个普通的提交,这是一个普通的分支。 再次找到它并使用它很容易 - 好吧,无论如何,就像 Git 中的任何内容一样简单。
如果你使用git stash save
,Git 会进行两次(有时是三次!)不在任何分支上的提交。(特殊名称stash
记住其中一个提交的哈希 ID,git stash
代码巧妙地安排一个提交以记住其他提交。 这些提交不在分支上的事实意味着您可以移动到另一个分支,然后尝试应用它们。 但是,"移动到另一个分支"(实际上是移动到另一个提交)可能会使它们无法应用;当他们未能申请时,你必须清理一些混乱。
git stash save
会将您当前的工作保存在"藏匿处">中,然后从工作副本中删除该作品。然后,您可以使用git stash pop
将存储应用回您的工作副本(甚至可以是不同的分支)。
Git 存储文档
save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] []
将本地修改保存到新的存储中,并将它们回滚到 HEAD(在工作树和索引中)。pop[--index] [-q|--quiet] []
从存储列表中删除单个存储状态并将其应用于当前工作树状态之上,即执行 git 存储保存的反向操作。工作目录必须与索引匹配。