在变基和意外切换到不同分支时丢失的变化

时间:2018-05-08 14:50:23

标签: git git-rebase

在分支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

是否有可能恢复这些变化?

2 个答案:

答案 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/HEADHEAD包含类似.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进入的地方

每个分支名称都有一个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”