如何在Git中恢复丢失的存储库?

2467

我经常使用git stashgit stash pop来保存和恢复我的工作树中的更改。昨天,我在我的工作树中有一些已经被存储和弹出的更改,然后我又对我的工作树进行了更多的更改。我想回去查看昨天存储的更改,但git stash pop似乎删除了所有相关提交的引用。

我知道,如果我使用git stash,那么.git/refs/stash包含用于创建存储的提交的引用。而.git/logs/refs/stash包含整个存储。但是,在git stash pop之后,这些引用都没有了。我知道提交仍然在我的仓库中,但是我不知道它是什么。

是否有一种简单的方法来恢复昨天的存储提交引用?


105
未来注意事项:如果你不想在每次使用git stash pop时丢失你的储藏,你可以使用git stash apply代替。它做的与pop相同,但不会删除已应用储藏的引用。 - Kevin
16
在这里尝试了一切,都找不到已经弹出的藏匿点。非常感激 IntelliJ 的 https://www.jetbrains.com/help/idea/local-history.html - Ruan Mendes
另请参阅如何恢复隐藏的未提交更改 - mfaani
5
建议:对于任何你不愿意丢失的内容,避免使用 git stash。如果值得保存,那么就值得进行完整的提交(可能在单独的临时分支上)。使用 git commit,你的“stash”将更容易跟踪。首先,你可以包括提交信息。但更重要的是,即使你重置/删除了分支,你的更改也会在本地 reflog 中访问。详情请见 even if you reset/delete the branch - Brent Bradburn
我同意Brent的观点,不过你可以使用git stash push -m“我的存储消息…”为你的git存储条目添加消息,以更好地组织它们。 - James Hooper
非常感谢您提出这个问题,以及回答它的人。虽然这种情况并不经常发生在我身上,但在过去两周内,我的肌肉记忆竟然让我输入了 git stash clear 命令来清除杂乱的存储区。这个帖子帮助我恢复了存储区的提交。 - Aman
25个回答

3989

一旦你知道你弹出的暂存提交的哈希值,你可以将其应用为一个暂存:

git stash apply $stash_hash

或者,您可以使用以下方式为其创建一个单独的分支:

git branch recovered $stash_hash

完成后,您可以使用所有常规工具进行任何操作。完成后,只需删除该分支。

查找哈希值

如果您刚刚弹出它并且终端仍然打开,则会在屏幕上打印出由 git stash pop 命令输出的哈希值(感谢 Dolda)

否则,您可以使用以下命令在 Linux、Unix 或 Windows 上的 Git Bash 中找到它:

git fsck --no-reflog | awk '/dangling commit/ {print $3}'

...或者在 Windows 上使用 PowerShell:

git fsck --no-reflog | select-string 'dangling commit' | foreach { $_.ToString().Split(" ")[2] }

这将向您显示提交图顶部中不再从任何分支或标记引用的所有提交 - 所有丢失的提交,包括您曾经创建的每个stash提交,都将在该图中的某个位置。

找到所需的存储提交最简单的方法可能是将该列表传递给gitk

gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )

如果在Windows上使用PowerShell,请参见emragins的答案

这将启动一个存储库浏览器,显示存储库中的每个提交记录,无论它们是否可达。

如果您喜欢控制台上漂亮的图形而不是单独的GUI应用程序,可以将其中的gitk替换为git log --graph --oneline --decorate之类的内容。

要查找stash提交,请查找以下形式的提交消息:

        WIP on somebranch: commithash Some old commit message

注意:如果您在执行git stash时没有提供消息,则提交消息仅以此形式(以“WIP on”开头)出现。


112
Jaydel说出了我想说的话。这篇文章拯救了我的工作 :) 我只想补充一点 - 记住你丢失文件的日期,可以更轻松地在Gitk中查找你需要的内容。 - Sridhar Sarnobat
4
@Codey: 因为 PowerShell。我不知道 MsysGit 是否提供 AWK 二进制文件。通过谷歌搜索,我发现在 PowerShell 中,类似 %{ $_.Split(' ')[2]; } 这样的代码可以实现与 awk 命令中 {print $3} 相同的效果,但我没有 Windows 系统来测试它,并且你仍然需要一个 /dangling commit/ 部分的等效命令。无论如何,只需运行 git fsck --no-reflog 并查看输出即可。你需要从“dangling commit <commitID>”行中获取哈希值。 - Aristotle Pagaltzis
8
值得一提的是,如果您在存储时没有提供自己的消息(即通过执行 git stash save "<message>"),提交消息将仅包含字符串“WIP”。 - Samir Aguiar
42
如果你知道这次提交是何时发生的,可以使用以下一行命令获取悬空提交列表:git fsck --no-reflog | awk '/dangling commit/ {print $3}' | xargs -L 1 git --no-pager show -s --format="%ci %H" | sort 最后一个条目可能是你想要的 stash apply - ris8_allo_zen0
25
git stash apply {ref} 恢复了一个被删除的存储! git 太棒了,简直该禁止! - Tom Russell
显示剩余17条评论

831

如果您没有关闭终端,只需查看git stash pop的输出,您将获得被删除存储的对象ID。 它通常长这样:

$ git stash pop
[...]
Dropped refs/stash@{0} (2ca03e22256be97f9e40f08e6d6773c7d41dbfd1)

(请注意,git stash drop 命令也会产生同样的行为。)

若要取回该暂存区,只需运行 git branch tmp 2cae03e 命令,你将得到一个分支。如果想要将其转换成暂存区,则运行以下命令:

git stash apply tmp
git stash

将它作为一个分支还允许你自由地操纵它;例如,挑选它或合并它。


67
你也可以使用 git stash apply commitid 命令,然后再用 git stash 命令获取一个新的存储。 - Matthew Flaschen
35
请注意,如果 Git 自动合并了存储,并出现冲突,它将不会显示哈希值。 - James
34
再说,如果这些冲突是因为运行了 git stash pop 而产生的,它也不会丢弃代码储藏,所以通常不会有问题。 - Dolda2000
3
我的Git stash pop命令输出中没有SHA信息。 :( - Throw Away Account
2
@MatthewFlaschen 你也可以使用 git stash store commitid 恢复旧的存储。这样做的好处是不会影响你的工作目录,并且原始存储的日期也会保留。 - jakun
显示剩余2条评论

306

我想提一下对已接受解决方案的补充。第一次尝试这种方法时(也许应该是的),我并没有立刻意识到从哈希值应用存储的方法,只需使用“git stash apply ”:

$ git stash apply ad38abbf76e26c803b27a6079348192d32f52219

当我刚开始使用git时,这对我来说并不清楚,我尝试了各种组合,如“git show”,“git apply”,“patch”等。


5
请注意,这适用于(显然!)将stash应用到当前的工作树。如果工作树是脏的,您可能需要使用一个临时分支或先进行stash,然后从SHA-1应用stash,再次stash,然后弹出倒数第二个stash(称为stash@{1})。 - musiKk

259
获取仍在您存储库中但不再可达的stash列表:
git fsck --unreachable | grep commit | cut -d" " -f3 | xargs git log --merges --no-walk --grep=WIP

如果你为你的暂存区设置了一个标题,请将命令末尾的 -grep=WIP 中的“WIP”替换为消息的一部分,例如 -grep=Tesselation

该命令是在搜索“WIP”,因为暂存区的默认提交消息的格式为 WIP on mybranch: [previous-commit-hash] Message of the previous commit.

当你找到想要的提交后,使用 git stash apply <commit_hash> 命令来应用它。


2
echo 'git fsck --unreachable | grep commit | cut -d" " -f3 | xargs git log --merges --no-walk --grep=WIP' >/usr/local/bin/git-stashlog; chmod a+rx /usr/local/bin/git-stashlog # git stashlog - Erik Martino
1
或者你可以将其作为别名添加到你的.gitconfig文件中(在命令前加上“!”)。 - asmeurer
2
这个方法可以找到丢失的提交记录,而不是采用已接受的答案!当你找到提交记录后,使用 git stash apply <commit_hash> 应用它。 - blub
2
如果操作系统是另一种语言,则可能会失败。例如,在法语中,我们必须使用“cut -d”“-f4”来匹配“objet commit inatteignable 12f1d8d92b703b220c37403d0eb83dba5e273551”中的sha1。 - JM Lord
1
这救了我一整个上午的工作。我再也不会再弹出隐藏文件夹了。 - Xiang Wei Huang
git stash drop会输出一个数字;在终端上向上滚动,你就能找到它。 - undefined

100

我刚刚构建了一个命令,帮助我找到了丢失的存储提交:

for ref in `find .git/objects | sed -e 's#.git/objects/##' | grep / | tr -d /`; do if [ `git cat-file -t $ref` = "commit" ]; then git show --summary $ref; fi; done | less

这里列出了.git/objects树中的所有对象,找到类型为commit的对象,并显示每个对象的摘要。从这一点开始,只需查找提交记录,找到适当的“WIP on work: 6a9bb2”(“work”是我的分支,619bb2是最近的提交)。

我注意到如果我使用“git stash apply”而不是“git stash pop”,我就不会遇到这个问题,如果我使用“git stash save message”那么找到提交记录可能会更容易。

更新:根据Nathan的想法,这变得更短:

for ref in `git fsck --unreachable | grep commit | cut -d' ' -f3`; do git show --summary $ref; done | less

98

使用gitk的Windows PowerShell等价命令:

gitk --all $(git fsck --no-reflog | Select-String "(dangling commit )(.*)" | %{ $_.Line.Split(' ')[2] })

可能有更有效率的一次性处理方法,但是这个命令可以完成所需任务。


60

git fsck --unreachable | grep commit命令应该会显示SHA1,尽管它返回的列表可能很大。 git show <sha1>命令将显示是否为您想要的提交。

git cherry-pick -m 1 <sha1>命令将把该提交合并到当前分支上。


50

如果您想要恢复一个丢失的stash,首先需要找到您已丢失的stash的哈希值。

正如Aristotle Pagaltzis建议的那样,使用git fsck命令可能会有所帮助。

我个人使用我的log-all别名,它可以显示每个提交(可恢复的提交),以便更好地了解情况:

git log --graph --decorate --pretty=oneline --abbrev-commit --all $(git fsck --no-reflogs | grep commit | cut -d' ' -f3)

如果你只想查找“WIP on”消息,那么你可以进行更快的搜索。

一旦你知道了你的sha1值,你只需更改你的存储引用日志以添加旧的存储:

git update-ref refs/stash ed6721d

你可能更喜欢有一个相关的消息,因此是 -m

git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721d

你甚至会想将此用作别名:

restash = !git update-ref -m $(git log -1 --pretty=format:'%s' $1) refs/stash $1

2
然而,-d\\ 应该改为 -d\ (或者更清晰的 -d' ')。 - joeytwiddle
出现错误:"fatal: ambiguous argument 'dangling': unknown revision or path not in the working tree." - Daniel Ryan
你还需要用引号将子命令包裹起来:git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721 - Andrei Shostik

28

我最喜欢的是这一句话:

git log --oneline  $( git fsck --no-reflogs | awk '/dangling commit/ {print $3}' )

这基本上是与这个答案相同的想法,但更简洁。当然,您仍然可以添加--graph以获得树状显示。

当您在列表中找到提交时,请使用以下命令应用:

git stash apply THE_COMMIT_HASH_FOUND

对我而言,使用--no-reflogs确实揭示了丢失的暂存条目,但是--unreachable(如许多其他答案中所述)没有。

在Windows下使用git bash运行它。

来源:上述命令的详细信息取自https://gist.github.com/joseluisq/7f0f1402f05c45bac10814a9e38f81bf


这对我在Mac OSX上的git版本2.31.1上运行良好,而gitk在尝试打开窗口时产生错误。 我还尝试了给出的链接,但在撰写此评论时,该命令中存在语法错误,括号前缺少$符号。 也许值得一提的是,如果您能记住至少一部分存储名称,可以通过将输出导入到grep中来查找它。 - Glenn Lawrence

24

你可以在终端中输入这个命令来列出所有无法到达的提交 -

git fsck --unreachable

检查无法到达的提交哈希值 -

git show hash

最后,如果你找到了藏在某处的物品,请进行申请 -

git stash apply hash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接