在分支foo
上,我开始使用这样的rebase:git rebase --interactive HEAD~1
,我想在文件A
中为最后一次提交添加更改。
我做了我的更改,git add
他们,然后是git commit --amend
他们。 (请注意,我尚未发布git rebase --continue
命令)
然后我通过bar
切换到分支git checkout bar
;在那里没有做任何事情,并通过foo
切换回git checkout foo
。检查文件A
时,我发现我在rebase期间所做的所有更改都已消失,即使git status
说:
Last command done (1 command done):
e deadbee Nice commit message
是否有可能恢复这些变化?
答案 0 :(得分:4)
当您启动交互式rebase时,Git会让您进入"分离的HEAD"模式。当您通过名称签出分支名称时,Git会将您带入"附加HEAD"模式,即回到分支上。这相当严重地破坏了正在进行的变革,因为你现在很难找到任何新的承诺。
Lemuel Nabong's answer包含密钥(但是错误):您必须重新检出相应的detached-HEAD提交,您可以使用git reflog
找到该提交。在git reset或git checkout hash
执行此操作。然后你应该能够继续你的变基。
分离的HEAD 意味着特殊文件git checkout HEAD@{number}
(始终存在)不再包含分支的名称。通常.git/HEAD
或HEAD
包含类似.git/HEAD
的字符串,表示当前分支是名为ref: refs/heads/master
的分支。然后,当前分支确定当前提交。
对于某些类型的工作,尽管 - 包括交互式rebase-Git更改master
,以便它包含原始提交哈希ID。这种模式的有趣之处在于,您可以进行新的提交,从而获得与每个现有提交不同的新哈希ID。当你这样做时,那些新的提交'通过阅读.git/HEAD
本身找到ID 。
我想,一张照片让这一点更加清晰。如果我们从一个只有三个提交的小型存储库开始,我们可以像这样绘制它们,使用单个大写字母代表那些可怕的哈希ID字符串,如.git/HEAD
。我们会调用我们的第一个提交ccdcbd54c4475c2238b310f7113ab3075b5abc9c
,我们的第二个A
和第三个B
:
C
提交A <-B <-C <--master
,我们的最新提交,其哈希ID存储在名称C
下。我们说名称master
指向 master
。提交C
本身将提交C
的哈希ID存储为其父,因此我们说B
指向C
。依次提交B
存储B
个哈希ID,因此A
指向B
。提交A
是有史以来第一次提交,因此它根本没有父级。 Git将此称为 root commit ,例如,如果我们运行A
,则操作将停止,因为之前没有提交的提交。< / p>
因此,Git始终向后运行:分支名称指向分支上的 last 提交。提交本身会记住先前的提交,依此类推。如果我们要将{em> new 提交添加到git log
,我们会运行:
master
提交步骤打包最新的快照(来自索引又名临时区域,其中git checkout master # if needed
... do things to modify files ...
git add file1 file2 ...
git commit
复制了它们,但我们会留下对于另一个主题),然后写出一个新的提交git add
,其父项是当前提交D
:
C
最后,在写完新提交后,A <-B <-C <--master
\
D
会写入新提交的哈希ID - 无论结果如何;它不容易预测到名称git commit
,以便master
现在指向master
:
D
并完成提交。
如果您有多个分支名称,Git知道哪个分支名称要更新的方式是将A <-B <-C
\
D <--master
附加到其中。假设我们不是在HEAD
上提交D
,而是执行此操作:
master
现在图纸看起来像这样(我放弃了内部箭头,我们知道它们总是向后倾斜而且很难画出来):
git checkout master
git checkout -b develop # create new develop branch
我们开展工作A--B--C <-- master, develop (HEAD)
和git add
,并且由于git commit
附加到HEAD
而不是develop
,Git会写新的提交{{} 1}}将哈希ID转换为master
而不是D
,并给出:
develop
分离的HEAD 只是意味着master
不是将A--B--C <-- master
\
D <-- develop (HEAD)
附加到某个分支名称,而是直接指向某个提交。如果我们现在分离HEAD
并将其指向提交HEAD
,我们可以将其绘制为:
HEAD
如果我们现在制作新提交D
,我们就会知道:
A--B--C <-- master
\
D <-- develop, HEAD
如果我们现在说E
,就会发生这种情况:
A--B--C <-- master
\
D <-- develop
\
E <-- HEAD
回到原来的方法是为commit git checkout master
找一些名字(记住,它的真名是一些很难看的哈希ID)。
rebase和A--B--C <-- master (HEAD)
\
D <-- develop
\
E <-- ???
都可以通过 new 提交来完成。 E
所做的特殊事情是使新的提交及其父级是当前提交的父级。如果我们从:
git commit --amend
并运行--amend
,Git进行新的提交A--B--C <-- master
\
D <-- develop (HEAD)
,其父级为git commit --amend
的父级E
,而不是D
本身。然后Git将其写入适当的名称 - C
,在这种情况下给予:
D
每个分支名称都有一个reflog,记录分支名称用于指向的提交ID。也就是说,如果develop
一次指向 E <-- develop (HEAD)
/
A--B--C <-- master
\
D <-- ??? [abandoned?]
- 它必须具有 - 那么master
的reflog包含提交A
的哈希ID。此reflog还包括commit master
的哈希ID。在A
不再直接指向B
后,master
reflog也将包含哈希ID C
,依此类推。
还有master
本身的reflog,记录C
指向的哈希ID,直接(分离)或间接(通过附加到分支名称) 。因此HEAD
向您显示那些reflog条目,它允许您查找您正在寻找的提交的实际哈希ID。
reflog条目的一个缺点是它们最终到期: 30到90天后,Git假设您不再关心。由于您正在寻找的提交是新鲜的,因此这个特定的下方不会适用于此。另一个(另一个?)的缺点是,在reflog中发现的提交往往看起来都很相似,而且可能有很多,所以很难在噪声中找到它们。有一点有用的是要注意它们保持有序:HEAD
条目是刚才的旧值,git reflog HEAD
条目是之前的条目,依此类推。因此,如果您最近才切换,那么您想要的那个将位于前几个。
答案 1 :(得分:1)
git reflog
git checkout HEAD@{X}
其中 X 是索引提交之前“o3820h HEAD @ {Y}:checkout:从foo移动到bar”