读档操作

就像你玩 RPG 游戏很菜一样,你写代码也很菜,总会出现数不清的 bug,因此必须在遇到 boss(重量级的 bug)前存档, 这一切都是为了快速地重新挑战,如果你没有存档,那就只能从头开始了 Orz

WARNING

这个例子可能有点蠢,但是也说明了版本管理软件的优点之一。 时刻谨记,数据无价。

其实 commit 就是所谓的存档操作,接下来开始读档

日志(存档点)

你的每一次 commit 都会产生一条 log,可以使用 git log 来查看所有的日志记录

git log

#commit 3269bdf6abb5ab1e60bf73ac5f564d31135a9a33 (HEAD -> master)
#Author: mosqu1t0 <mosquito@email.cn>
#Date:   Sun Oct 23 21:13:30 2022 +0800
#
#    complete my readme
#
#commit ec1ed748d45e24b3272f355c9316d3d0905e44aa
#Author: mosqu1t0 <mosquito@email.cn>
#Date:   Sun Oct 23 20:33:15 2022 +0800
#
#    just commit a new file

查看单一分支的日志: git log branch-name,比如: git log dev

查看不同分支间的日志差异: git log branch1..branch2,列出分支 branch1 到 branch2 的 commit 记录(左开右闭)

如果不知道谁先谁后: git log branch1...branch2

你会发现一串奇怪的字母和符号 HEAD -> master

  • HEAD 表示当前仓库所处于的版本(默认当然是最新的提交)
  • master 主分支

todo

Reset

使用以下指令回退到某个版本

git reset --hard <commit sha>

# --hard 表示强制

DANGER

使用该指令的时候千万小心,它是真正的回退,是时光机那种回退,回退到的版本之后的版本都会消失!尽管还有办法恢复,但还是请小心。 推荐使用 revert

<commit sha>填写日志的 sha 值, 也可以使用 HEAD^ 表示上一个版本

TIP

理所当然,HEAD^^ 表示上上个版本,写多少个^代表多少个上
HEAD^^^可以简写成 HEAD~3 表示往上 3 个版本(倒数第 4 个版本)

回滚版本

及时的

假如你刚刚滚错,还没有关闭终端(还保存有上条 commit sha 值) 那么可以直接使用 git reset --hard <commit sha> 指令回到未来

不及时

如果你已经已经完成工作下班,第二天才悲剧地发现回滚错误 orz

没关系,git 提供了后悔药吃

git reflog

# 查看每次操作的记录

# ec1ed74 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^^
# a11a2e6 HEAD@{1}: reset: moving to a11a
# 3269bdf HEAD@{2}: reset: moving to HEAD^
# a11a2e6 HEAD@{3}: reset: moving to a11a
# ec1ed74 (HEAD -> master) HEAD@{4}: reset: moving to HEAD~2
# a11a2e6 HEAD@{5}: commit: again a commit
# 3269bdf HEAD@{6}: commit: complete my readme
# ec1ed74 (HEAD -> master) HEAD@{7}: commit (initial): just commit a new file

再使用 git reset --hard <commit sha> 回去

TIP

其实 reset 共有三种模式 git reset --[soft,mixed,hard] <commit sha>
但回退到的版本之后的版本日志都会消失, 区别只是是否会改动工作区文件和暂存区而已
这反而导致只有 hard 是我最常用的(怪喔)

回滚暂存区

让我们来看 reset 一个比较温柔的操作

git reset HEAD <file>

# 把暂存区中某文件的修改撤销

未 commit 前,可以把刚 add 的文件撤销掉

TIP

你可能会发现使用git status 时,git 会提醒你可以使用 git restore --staged <file> 达到同样的效果(unstage)

如果你不小心更改了工作区的文件,git status时,git 还会提醒你可以使用 git restore <file> 把工作区的某文件的更改取消,恢复到暂存区中的状态

Revert

上文 reset 是我一年前写的内容,现在发现问题非常大,通过学习后我发现之前使用 git 的习惯非常不好,因此想添加一部分内容来修正,防止传播错误的知识误人子弟。

image

git reset 是会修改版本历史的,他会丢弃掉一些版本历史。

而 git revert 是根据那个 commit 逆向生成一个新的 commit,版本历史是不会被破坏的。

因此大部分情况下都推荐使用rever的方式去回滚!

DANGER

已经 push 到远程仓库的 commit 不允许 reset

上面已经讲了,git reset 是会丢弃掉 commit 的。

如果 commit 已经被 push 到远程仓库上了,也就意味着其他开发人员就可能基于这个 commit 形成了新的 commit,这时你去 reset,就会造成其他开发人员的提交历史莫名其妙的丢失,或者其他灾难性的后果。

因此,一旦 commit 已经被 push 到远程仓库,那么是坚决不允许去 reset 它的。

一般来说管好自己就很难了,如果还做干扰到别人是非常不能原谅的事情!🥺

# -->1-->2-->3<--HEAD
# 使用 revert 回滚3的提交
git revert HEAD^
# -->1-->2-->3-->revert3<--HEAD
#       |           |
#       -------------
#
# 此时 3 提交已经没有了,因此工作区的内容与 2 一样

然而有时,我们也许并不想完全撤销一个提交,而是在它的基础上添加一些代码,形成全新的功能,但也许这个功能在项目的未来中不一定会采用,至少我们需要把它保存下来对吧?比如保存到一个新的ft/newshit分支

那么光使用 revert 还不够,需要更灵活的操作!checkout就是这样的指令

checkout

checkout 不仅仅常用来切换分支,其实用来移动HEAD指针也是它最厉害的作用之一。

checkoutreset不同的地方在于,它只会移动 HEAD到指定的 commitID ,而reset会移动 branch 到HEAD指向的 commitID,那之前的 commit 都会丢失。wow 感觉有点难以理解,但只要知道 checkout 不会改变当前的提交记录,而仅仅是移动了指针的位置,当然工作目录的内容也会改变,还可以随时移动回去,安全很多对吧,这才是存档的意义。

# 移动`HEAD`到上一个 commit
git checkout HEAD^
# 假如有这样的提交记录
# * 表示HEAD指向的地方
# a-->b-->c-->d* (master)
#
# 移动 HEAD 到 b 处
git checkout b_commit_id

# a-->b*-->c-->d
#
# 假如我现在有新的提交
# a-->b-->c-->d (master)
#     |
#     e*

# 发现了吗,它没在 a-d(master) 这条分支里插入提交,而是新起了一个分岔的提交
# 继续提交
# a-->b-->c-->d (master)
#     |
#     e-->f*
#
# 太让人兴奋了,现在我们的开发工作结束了,先回到 a-d 分支看看是否一切正常
# 然而当我们 checkout master后,查查日志发现一切活都白干了!
#
# a-->b-->c-->d* (master)
#
# 当然 git reflog 里记录了所有操作,已经commitID,还有后悔药可以吃,虚惊一场。
git reflog
git checkout f_commit_id

# a-->b-->c-->d (master)
#     |
#     e-->f*
#
# 原来只有新建一个branch 或者 打上 tag,新的记录才能保存下来
git checkout -b ft/newshit

# a-->b-->c-->d (master)
#     |
#     e-->f* (ft/newshit)
#
# 诶诶?这不就是新建了一个 branch 吗,原来常用的 checkout -b branch 指令只是在 HEAD 处新建了一个分支嘛
# 这种和谐统一的设计实在是太厉害了