[英]Git: went to old commit, now I can't come back
我使用以下代码移至旧的提交,其中包含已删除的一些代码:
git checkout xxx .
但是现在我想回到工作所在,尝试了以下方法: 我没有保存或提交最新代码
git checkout xxx/xxxx .
xxx / xxx是我一直在工作的分支的名称。
但是我的文件没有更改,它们仍然包含所有旧代码,而没有包含我的新代码!
任何帮助将不胜感激。
恐怕par的答案可能是正确的答案 。 但是,如果您对文件进行git add
(即使不进行git commit
,结果也可能会很幸运。 如果全部为“ TL; DR”,则可以跳到下面的最后一部分。
为了清楚起见,让我们首先注意到git checkout
本质上有三种或四种形式: 1
git checkout branchname
git checkout commit-ish
git checkout commit-ish -- path [ path ... ]
git checkout -- path [ path ... ]
第一种形式,您为git checkout
了一个分支名称, 并且没有路径参数 ,试图将您带入branch 。 例如, git checkout master
会将您置于分支master
(或者失败并且什么也不做)。
第二种形式,您给git checkout
一些可以解析为特定提交ID的内容,但由于第一种形式,它不是普通的本地分支名称 , 而是检出指定的提交并为您提供“分离的HEAD”。 (或者再次,它可能会失败并且什么都不做。)内部工作的方式是,您离开之前的分支,而现在位于新的临时匿名(未命名)分支上,该分支在提交时结束您刚刚退房。
第二种形式是查看旧提交或重建旧提交或签出标签并进行构建(或类似操作)的常用方法。 例如,您可以git checkout c0ffee1
(哈希ID)或git checkout v2.7.2
(标签)。 但这不是你所做的。
第三和第四种形式的动作非常不同。 如果我负责Git的世界,那么它们根本不会被拼写为git checkout
,因为它们不会带您进行另一次提交。 它们对HEAD
没有影响。 如果您在分支上,则留在该分支上。 如果您处于具有特定提交的分离HEAD模式,那么您将处于具有相同特定提交的分离HEAD模式。 但是现在我们进入了第一个复杂性,因为现在我们必须谈论索引和工作树。
git checkout
的前两种形式相对安全,因为它们确保您不会丢失索引和工作树中的文件。 您可以使用-f
/ --force
标志来要求它们覆盖文件。 git checkout
的第三和第四种形式并不安全。 Git认为每个path
参数都是覆盖给定文件或目录的请求。
commit-ish
参数是Git可以用来查找提交的任何内容。 这可以是原始哈希ID(例如c0ffee1
)或标签(例如v2.7.2
,甚至是分支名称(例如master
。 请注意,我们将分支名称拆分为git checkout
一种特殊(第一种)形式,但这仅在没有path
参数的情况下适用 。 当您提供一些path
参数时,像master
这样的分支名称就不再特殊了。
顺便说一句, --
之前的path
是可选的。 存在它的原因是允许您使用类似于选项的path
。 例如,如果您有一个名为-f
或--force
的文件,则需要能够告诉Git引用该文件 -f
或--force
,而不是使用-f
或--force
选项。 如果您有一个名为master
的文件,则需要能够告诉Git您所引用的是名为master
的文件 ,而不是名为master
的分支。 如果省略--
,Git会最好地猜测您是指分支名称,提交ID,选项还是文件名。 在您的情况下,您使用.
,绝对是文件名,所以git checkout xxx .
成为第三种形式。 当然, .
表示“整个当前目录,包括其所有文件和子目录”。
1表格的确切数量取决于您决定如何计算这些表格。 例如,您可以将前两个折叠为一种形式,然后将第二个折叠为另一种形式,或者将前两个分开并合并第三和第四种形式。 还有git checkout -m
, git checkout -p
, git checkout --ours
和git checkout --theirs
,它们与这四个主要方法略有不同。 但是在开始使用它们之前,请不要担心它们。
承诺是Git存在的理由。 他们记录了您一直以来所做的(或至少已承诺的)所有事情,以便您可以取回它们。 每个提交都以树状结构(包含文件和子目录的目录等)的形式保存一些文件的完整快照。 它还保存先前(父)提交的ID,以及一些元数据,例如您的姓名和电子邮件地址,时间戳以及提交日志消息。 就是这样:提交就是保存的树加上一些元数据。
工作树也很明显:Git使您可以工作。 Git内部的提交格式只有Git本身可以使用,但是您需要使用计算机来处理普通文件。 因此,Git可以从提交中填充工作树-尽管实际上它必须使用其索引(稍后我们将看到)-然后,您将拥有常规文件,常规程序,Web服务器或所有可以使用的常规文件。 由于许多原因,工作树还可以保存您不会提交的文件以及您从未打算提交的文件。 您将这些文件保留为“未跟踪”文件(Git会对它们发牢骚,并且您希望将其关闭)。
Git的索引 (也称为暂存区) (如git diff --staged
)或有时称为缓存 (如git diff --cached
:与--staged
)具有多个角色,但有趣的是,它是您构建下一个提交的地方 。
当从某个现有存储库的克隆开始并签出某个分支名称时,Git将从快照中的索引中填充该分支上最新提交的索引。 (此最新提交称为尖端提交。)因此,现在索引与提交匹配。 因此,如果您现在要进行新的提交, 2您的新提交将具有与当前提交相同的树,因为您尚未更改索引。
当您运行git add
来更新现有文件时,Git只是将索引副本替换为工作树中的版本。 现在,您进行的下一次提交将具有新版本。 当您运行git add
以添加一个全新的文件时,Git将该文件从工作树复制到索引中,现在下一次提交将具有新文件。 如果在文件名上使用git rm
,则会从工作树和索引中删除该文件,现在下一次提交将没有该文件。
(顺便说一句,这使我们能够精确地说出这是什么意思了工作树文件是“未跟踪”。文件是未跟踪 ,当且仅当它是不是在索引中。这就是它-这一切就是这么简单!现在,当Git对未跟踪的文件发牢骚时,可以将文件名添加到名为.gitignore
的文件中,这将使Git 关闭它。实际上不会使该文件处于未跟踪状态:这取决于文件是否未被跟踪。它只是使Git关闭,并且在您使用Git的“一次添加多个文件”快捷方式之一时也不会自动添加它。)
2 Git将尝试阻止您进行与HEAD
提交完全匹配的新提交。 但是,您可以使用--allow-empty
强制其允许提交。 “ Empty”是一种有趣的拼写方式,因为新提交根本不为空,至少在保存的工作树方面完全相同 。 (即使它们与HEAD
匹配,也始终允许新的合并提交。)
git checkout
何时覆盖索引和/或工作树? 如果我们回到我所谓的git checkout
的第三种形式和第四种形式,我们会看到其中一种具有commit-ish
参数,而另一种则没有。 这可能应该是更多隐藏的实现细节,但是Git习惯于让实现细节直接显示给用户。
为了git checkout
将文件从提交复制到工作树,它必须首先将文件写入索引。 因此,第三种形式git checkout commit-ish -- path
,找到与给定commit-ish
关联的文件path
的版本,将其复制到索引,然后将索引版本复制到工作树。
但是,第四种形式没有commit-ish
参数: git checkout -- path
。 在这种情况下,Git将文件的版本从索引复制到工作树中。 在大多数情况下,索引上的版本与工作树中的版本相同 ,因此在大多数情况下,这没有任何作用。 但是,如果您已经修改了工作树版本,然后又决定要放弃所做的修改,则可以提取索引版本。
索引版本可能与当前提交( HEAD
)版本相同。 在这种情况下, git checkout -- path
和git checkout HEAD -- path
都将HEAD
版本复制到工作树中,但是具有显式HEAD
副本首先将HEAD
版本复制到索引中,结果是相同的因为HEAD
和索引版本仍然相同。
为了完整起见,我将提到前两个git checkout
形式(“安全”形式) 也将覆盖索引和工作树,但是在此不做过多详细说明,Git尝试仅覆盖那些是“安全”的覆盖,因此您不会丢失未提交的工作。 请参阅Git-在当前分支上有未提交的更改时签出另一个分支 (更多)。
每个文件始终有多达三个有趣的版本:
HEAD
)提交中的一个; 使用git checkout commit-ish -- path
,如git checkout xxx .
,使HEAD
版本保持不变,但将xxx
(提交)版本复制到索引和工作树中。 如果索引和工作树中还有其他版本,那么它们现在就消失了。 如果这些版本与某个提交的版本匹配,则可以将其取回。 如果没有,Git帮不了您……可能。 但是请看最后一节!
“永远消失”规则有一个不寻常的例外,尽管使用起来很痛苦。 当您在索引中git add
文件时,Git实际上将文件的副本放入存储库中 。 索引仅包含文件的40个字符的SHA-1 哈希 。 这意味着,如果您在git add
-ed文件中git add
了该文件,则该文件将保存在存储库中。 如果用另一个版本覆盖索引版本,则实际上会将“另一个”版本复制到存储库中,并将新的哈希值放入索引中。 中间版本不会被删除! 好吧, 还没有 。
这些散列但从未提交的文件可以恢复,直到Git“垃圾收集”它们为止。 默认情况下,从git add
-ed到它们至少有14天的时间。 恢复它们的命令是git fsck --lost-found
。
这种恢复方法的问题在于文件名不见了。 git fsck --lost-found
所做的是找到Git对象(提交,树,标签和“ blob”,Git称为存储的文件),这些对象没有引用它们。 当您对文件进行git add
-ed时,Git将文件内容存储为新的blob,然后将blob对象的哈希ID写入索引,并使用索引来保存文件名 。 然后,当您重写path
的索引条目时,您会丢失名称,并且存储库blob对象变为未引用 。 --lost-found
选项使git fsck
将原始文件的内容复制到.git/lost-found/other/
,并将其存储在哈希ID下,因为名称消失了。 然后,您可以浏览每个此类文件以找到所需的文件,并将其移出失物招领处以将其取回。
如果您在未预先存放或提交代码的情况下签出分支,则代码将丢失并且无法检索。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.