是否可以撤消由以下命令引起的更改?如果是,怎么办?

git reset --hard HEAD~1

当前回答

我知道这是一条古老的线索。。。但随着许多人在寻找如何在Git中撤销这些东西,我仍然认为在这里继续提供提示可能是一个好主意。

当您在gitgui中执行“gitadd”或将任何内容从左上角移动到左下角时,文件的内容存储在一个blob中,文件内容可以从该blob中恢复。

因此,即使文件未提交但必须已添加,也可以恢复该文件。

git init  
echo hello >> test.txt  
git add test.txt  

现在创建了blob,但它被索引引用,因此在重置之前,它不会与git fsck一起列出。所以我们重置。。。

git reset --hard  
git fsck  

您将得到一个悬空blob ce013625030ba8dba906f756967f9e9ca394464a

git show ce01362  

将向您返回文件内容“hello”

为了查找未引用的提交,我在某处找到了一个提示。

gitk --all $(git log -g --pretty=format:%h)  

我把它作为gitgui中的工具,它非常方便。

其他回答

如果您还没有对存储库进行垃圾收集(例如,使用git重新打包-d或git gc,但请注意垃圾收集也可以自动进行),那么您的提交仍然存在,只是无法通过HEAD访问。

您可以尝试通过查看git fsck的输出来查找提交。

较新版本的Git有一种叫做“reflog”的东西,它是对ref所做的所有更改的日志(与对存储库内容所做的更改相反)。因此,例如,每次您切换HEAD时(即每次执行git签出以切换分支时),都会被记录。当然,你的git重置也操纵了HEAD,所以它也被记录了下来。您可以以类似于访问存储库的旧状态的方式访问ref的旧状态,方法是使用@符号而不是~,如git-resetHEAD@{1}。

我花了一段时间才明白HEAD@{1}和HEAD~1之间的区别,所以这里有一个小解释:

git init
git commit --allow-empty -mOne
git commit --allow-empty -mTwo
git checkout -b anotherbranch
git commit --allow-empty -mThree
git checkout master # This changes the HEAD, but not the repository contents
git show HEAD~1 # => One
git show HEAD@{1} # => Three
git reflog

因此,HEAD~1表示“在HEAD当前指向的提交之前进行提交”,而HEAD@{1}表示“在其当前指向的位置之前进行HEAD指向的提交”。

这将很容易让您找到丢失的提交并恢复它。

如果Git尚未垃圾收集,则可以恢复它。

使用fsck获取悬空提交的概述:

$ git fsck --lost-found
dangling commit b72e67a9bb3f1fc1b64528bcce031af4f0d6fcbf

使用rebase恢复悬空提交:

$ git rebase b72e67a9bb3f1fc1b64528bcce031af4f0d6fcbf

如果您使用的是JetBrains IDE(任何基于IntelliJ的),您甚至可以通过其“本地历史记录”功能恢复未提交的更改。

右键单击文件树中的顶级目录,在上下文菜单中找到“本地历史记录”,然后选择“显示历史记录”。这将打开一个视图,在该视图中可以找到您最近所做的编辑,一旦找到要返回的修订,请右键单击该修订并单击“还原”。

据我所知,--hard将丢弃未提交的更改。因为git不跟踪这些。但您可以撤消放弃的提交。

$ git reflog

将列出:

b0d059c HEAD@{0}: reset: moving to HEAD~1
4bac331 HEAD@{1}: commit: added level introduction....
....

其中4bac331是丢弃的提交。

现在只需将头部移至该提交:

$ git reset --hard 4bac331

在回答之前,让我们添加一些背景,解释一下这个HEAD是什么。

首先,什么是头部?

HEAD只是对当前分支上当前提交(最新)的引用。在任何给定时间只能有一个HEAD。(不包括git工作树)

HEAD的内容存储在.git/HEAD中,它包含当前提交的40字节SHA-1。


分离式封头

如果您不在最近一次提交中,这意味着HEAD指向历史上的先前提交,它称为分离的HEAD。

在命令行上,它看起来像-SHA-1而不是分支名称,因为HEAD没有指向当前分支的尖端


关于如何从分离的HEAD恢复的几个选项:


git校验

git checkout <commit_id>
git checkout -b <new branch> <commit_id>
git checkout HEAD~X // x is the number of commits t go back

这将签出指向所需提交的新分支。此命令将签出到给定的提交。此时,您可以创建一个分支并从此开始工作。

# Checkout a given commit. 
# Doing so will result in a `detached HEAD` which mean that the `HEAD`
# is not pointing to the latest so you will need to checkout branch
# in order to be able to update the code.
git checkout <commit-id>

# create a new branch forked to the given commit
git checkout -b <branch name>

数字刷新

您也可以始终使用reflog。git reflog将显示更新HEAD的任何更改,检查所需的reflog条目将将HEAD设置回该提交。

每次修改HEAD时,reflog中都会有一个新条目

git reflog
git checkout HEAD@{...}

这会让你回到你想要的承诺


git reset HEAD--hard<commit_id>

将你的头“移”回所需的位置。

# This will destroy any local modifications.
# Don't do it if you have uncommitted work you want to keep.
git reset --hard 0d1d7fc32

# Alternatively, if there's work to keep:
git stash
git reset --hard 0d1d7fc32
git stash pop
# This saves the modifications, then reapplies that patch after resetting.
# You could get merge conflicts, if you've modified things which were
# changed since the commit you reset to.

注:(自Git 2.7以来)您也可以使用git rebase--也可以不使用autostash。



git还原<sha-1>

“撤消”给定的提交或提交范围。重置命令将“撤消”给定提交中所做的任何更改。将提交带有撤销补丁的新提交,而原始提交也将保留在历史记录中。

# add new commit with the undo of the original one.
# the <sha-1> can be any commit(s) or commit range
git revert <sha-1>

此模式说明了哪个命令执行什么操作。正如您可以看到的那样,重置和签出修改HEAD。