一、问题引入
假设这样一个场景:有一天你的老板让你整理一份报告,结果你很轻松的整理完了第一版,但是你的老板并不满意,于是老板给你不断的提出建议,你不断的修改,于是报告就迭代出了版本1、版本2、版本3……但是老板拿到最新版本的报告之后,摇了摇头说:“我还是觉得版本1比较好,你把版本1拿给我吧”。这个时候你可能想“杀”他的心都有了,因为你是在一个报告中迭代的,版本1你早就不知道长什么样了。
这次经历之后,你留了一个心眼,每次你在进行修改的时候,都创建一个副本,结果就是一份报告分散出好多的文件,可是如果过了一周,看着这些乱起八糟的文件,你想找到修改的内容,但是已经记不清保存在哪个文件中了,只好一个一个文件去找,非常麻烦。并且如果你想保留最新的文件,然后把其他文件删掉,但是你又不敢删,怕哪天还会用到,真郁闷。
针对以上问题,难道就没有一种完美的解决方案的吗?于是 Git 等版本控制系统横空出世。其中 Git 版本回退可以说是 Git 的杀手锏之一,注意是之一,后面还有之二、之三……这个我们之后在介绍。
二、前置知识
为了更好的理解 Git ,在讲 Git 之前,我们先引入一些前置知识:
git add
将文件提交到暂存区。git commit
将文件提交到本地仓库。git status
可以让我们随时掌握工作区的状态(哪些文件被修改过)。git diff
顾名思义就是查看difference,可以查看修改内容,显示的格式是Unix通用的diff格式。git log
命令显示从最近到最远的提交日志(加上--pretty=oneline
会更加可观)。
上面这些命令不作为本期重点,大家可以自行了解。
三、工作区暂存区和版本库
- 工作区:就是你在电脑里能看到的目录。
- 暂存区:英文叫
stage
或index
。一般存放在 “.git” 目录下的 index 文件(.git/index
)中,所以我们把暂存区有时也叫作索引(index)。 - 版本库:工作区有一个隐藏目录
.git
,这个不算工作区,而是Git的版本库。
下面这张图就展示了
其实细心的小伙伴就发现了,除了工作区,暂存区,版本库,还有两个就是 object
和 master
- 在Git中,所有的数据都以对象(objects)的形式存储(文件内容,提交信息,分支引用等等)。
objects
标识的区域为 Git 的对象库,实际位于 “.git/objects
” 目录下,里面包含了创建的各种对象及内容。 - 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,
master
分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
注:Git
跟踪并管理的是修改(新增、删除、更改、新建)
四、版本回退
1、版本回退命令
git reset 命令在 Git 中被用来移动 HEAD 指针和重置当前分支的位置。它有几种不同的用法,主要包括以下三种模式:soft、mixed 和 hard。
1. Soft 模式:git reset --soft <commit_id>:该命令将 HEAD 指向 <commit_id>,但不会更改暂存区和工作目录的内容。你可以重新提交更改,新的提交将建立在指定的 <commit_id> 之上。
2. Mixed 模式:git reset --mixed <commit_id>(默认行为):该命令将 HEAD 指向 <commit_id>,并重置暂存区的内容为指定 <commit_id> 的内容,但不会更改工作目录的内容。这样你可以重新选择要提交的内容。
3. Hard 模式:git reset --hard <commit_id>:该命令将 HEAD 指向 <commit_id>,同时重置暂存区和工作目录的内容为指定 <commit_id> 的内容。慎用此命令,因为会丢失工作目录中未提交的更改。
除了以上几种模式外,git reset 还可以结合使用一些选项和参数来实现不同的操作,如:
(1)git reset HEAD <file>:将指定文件从暂存区中移除,但保留在工作目录中的更改。
(2)git reset --hard HEAD:将工作目录和暂存区都重置为最近一次提交的状态,丢弃所有未提交的更改。
(3)git checkout -- [file]: 命令让⼯作区的文件回到最近⼀次 add 或 commit 时的状态。要注意 git checkout – [file] 命令中的-- 很重要,切记不要省略,⼀旦省略,就变成了“切换到另一个分支”的命令。
注:
- HEAD 表示当前版本
- HEAD^ 上⼀个版本
- HEAD^^ 上上⼀个版本
以此类推…
也可以使⽤ 〜数字表示:
- HEAD~0 表示当前版本
- HEAD~1 上⼀个版本
- HEAD^2 上上⼀个版本
以此类推…
2、四大常见场景
(1)场景一:使用 --hard 将工作区、暂存区、版本库进行了回退,结果后悔了怎么办
在Git中,总是有后悔药可以吃的。例如当你用 git reset --hard HEAD^ 回退到上一个版本时,再想恢复到之前的版本,就必须找到之前版本的 commit id。Git提供了一个命令 git reflog 用来记录你的每一次命令,你可以通过这个命令找到之前的版本 id,然后使用 git reset --hard <commit_id> 进行恢复。
(2)场景二:对于⼯作区的代码,还没有 add
直接使用 git checkout -- <file_name> 即可回退到最近一次工作区修改。
(3)场景三:已经 add ,但没有 commit
先使用 git reset --mixed HEAD <file_name> 回到场景二。
再使用 git checkout -- <file_name> 回退到最近一次工作区修改。
(4)场景四:已经 add ,并且也 commit 了
不要担心,我们可以 git reset --hard HEAD^ 回退到上⼀个版本!不过,这是有条件的,就是你还没有把⾃⼰的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后⾯会讲到远程版本库,⼀旦你推送到远程版本库,你就真的惨了……
3、删除文件
再 Git 中删除也是修改,也可以被 Git 管理起来。
一般情况下,你直接在工作区中把文件给删除了,这个时候 Git 是可以知道你删除了文件的,你可以使用 git status 命令查看哪些文件被删除了。
其实在实际的工作中,删除文件的操作是比较少见的,此时一般会存在两种情况:
情况一:确实想要从版本库中删除文件
- 使用 git rm <file_name> 删除掉
- 使用 git commit 提交删除
情况二:不小心删错了,属于误删情况
- git checkout – <file_name> 恢复即可
总结
值得注意的是,Git 的版本回退速度非常快,因为 Git 在内部有个指向当前版本的 HEAD 指针,当你回退版本的时候,Git 仅仅是把 HEAD 指针指向回退的版本即可。