Reset 命令的作用
假设目前我们处于下图所示的工作场景中。我们正在 master 分支上进行工作。目前工作目录是干净的,这意味着暂存索引区中的文件内容与工作目录是一致的。并且,HEAD 指向的 master 分支指向的提交对象,也和目前工作空间中的文件内容保持一致。

现在,让我们来一步步看看 reset 命令究竟做了些什么。
第一步:移动 HEAD
reset 做的第一件事是移动 HEAD 的指向。 这与改变 HEAD 自身不同(checkout 所做的);reset 移动 HEAD 指向的分支。 这意味着如果 HEAD 设置为 master 分支(例如,你正在 master 分支上),运行 git reset 9e5e64a 将会使 master 指向 9e5e64a。
使用 rest 命令将 HEAD 指向的 master 重置到其父提交对象。
git reset --soft HEAD~

再强调一次,checkout 命令修改的是 HEAD 分支变量保存的地址内容,而 reset 命令是修改的 HEAD 分支所指向的分支变量中保存的地址内容。到目前为止,reset 命令实际上撤销了最近一次的 gitcommit 命令。
第二步:更新暂存区
如果你现在执行 git status
你会看到 git 提示你当前暂存区的变动有差异,需要进行提交。现在,reset 要做的下一件事是使用 HEAD 当前所指向的快照的内容来更新索引。

第三步:更新工作目录
reset 要做的的第三件事情就是让工作目录看起来像索引。 如果使用 --hard 选项,它将会继续这一步。

现在让我们回想一下刚才发生的事情。你撤销了最后的提交、git add 和 git commit 命令以及工作目录中的所有工作。如果现在使用 git log
查看 git 的提交记录,就会发现 reset 目标分支之后的提交记录全都消失了。
当客户端尝试将 reset 过后的版本内容推送到远端时,会被服务器拒绝并提示本地的版本落后于远端的版本。这很容易理解,因为你本地的分支指针指向的提交对象是远程仓库中的对应指针指向的提交对象的父提交对象。为了覆盖掉远端信息,你需要在命令的后面加上 --force 参数。
git push origin <分支名> --force
Revert 命令的作用。
revert 命令可以生成一个新的提交,撤销已有提交的所有变更。这样的操作被称为还原。
git revert -m 1 HEAD
-m 1 标记指出 “mainline” 需要被保留下来的父结点。 当你引入一个合并到 HEAD(git merge topic),新提交有两个父结点:第一个是 HEAD(C6),第二个是将要合并入分支的最新提交(C4)。 在本例中,我们想要撤消所有由父结点 #2(C4)合并引入的修改,同时保留从父结点 #1(C4)开始的所有内容。
有还原提交的历史看起来像这样:

新的提交 ^M 与 C6 有完全一样的内容,所以从这儿开始就像合并从未发生过,除了“现在还没合并”的提交依然在 HEAD 的历史中。 如果你尝试再次合并 topic 到 master Git 会感到困惑:
git merge topic
Already up-to-date.
topic 中并没有东西不能从 master 中追踪到达。 更糟的是,如果你在 topic 中增加工作然后再次合并,Git 只会引入被还原的合并 之后 的修改。

结语
git reset
命令是一次对版本信息的清洗和回退。通过移动指针指向的内容达成回退版本,并且覆盖掉相应的历史信息的目的。这种重写历史记录的特性,可能会在使用远程仓库时造成问题。假设在合并之后,远程仓库又创建了其他提交,那么你的强制提交可能就会洗掉别人的提交记录。所以如果你重写的他人的提交,那就应该避免使用 reset 。
git revert
命令是通过使用老的内容,产生一次新的提交以达到覆盖老的提交内容的目的。revert 命令不会覆盖历史记录,相对 reset 命令更加的安全。唯一需要注意的是,revert 过后的提交对象由于在分支历史上处于时间线的前列。这就造成了凡是属于此提交对象的父对象的历史信息,都无法被合并到 revert 过后的提交对象上。
虽然根据不同的业务情况选择不同的命令往往是正确做法。但是鉴于 reset 命令会重写历史记录的特性,当你想要撤销某次提交时,请慎重使用 reset 命令。
参考书籍
《精通 Git pro 第二版》
网友评论