Pulpcode

捕获,搅碎,拼接,吞咽

0%

git reset 中的三个参数到底有啥区别?

git 的文档中会介绍到git reset 有几个参数

git reset [–soft | –mixed [-N] | –hard | –merge | –keep] [-q] []

其中 soft, mixed, hard 比较常用。之所以写这样一篇博客是想借此来总结git的一些底层知识。

首先你要知道git reset 用来让代码回退到某个commit,而且–mixed 是默认选项。接下来你要知道git reset,无论使用哪个参数,你的那些回退之前的commit都“找不到”了。(用git reflog可以找回来)

git-reset

当你reset 回到2版本的时候,再次新的开发肯定是在2的基础上,如第三幅图所示,所以问题就在于你在回退版本的时候,是否还想要commit3和commit4这部分修改的代码了?

如果你想要的话,可以使用soft参数,这会让这部分变化的代码(commit3 到commit4)都放在当前工作目录中,也就是说你用 git status 将会发现一堆可以被git add 然后commit的“变化”。
或者使用mixed参数,来让这部分变化放在暂存区stage中,你可以选择将这部分变化的代码(commit3到commit4) commit。

所以如果你再把这些变化“补充”回去,那么将得到的代码会和commit4是一样的,只不过不是当时的commit4了。

当然如果你使用hard选项就是不打算要commit3到commit4的代码了。

能够实现将变化放到当前工作目录的方式,类似于 diff和patch的原理。就是a 和 b 比较,总能得到一个 diff c,那么这个c就相当于一个patch,对a打一个c patch,就可以得到b了。

同样的commit2 和commit4比较,是可以得到一个diff的,然后把当前分支回退到commit2,然后在把当前目录“运行”diff。

注意我是说commit2和commit4之间的比较,所以如果在commit3中给某个文件加入一行,在commit 4中又把它删了,那实际上我们reset之后是发不现这种变化的。提到这点为了引出git的设计思路,每个commit都是独立的版本快照,而非什么存储变化,所以你用git diff只是看到当前目录与git库的区别,git并非把这些区别存储于git中。(会在后面讲到)

顺便说一下,因为reset 回到了以前的版本,如果这部分被回退的代码在之前已经提交到服务器上,你是不能把这次commit,push到远程服务器上的,因为git是不容许旧版本覆盖新版本的,这就是所谓的“只做加法,不做减法。”
你可能知道git push -f,但这是个很不好的习惯,很可能把别人的代码都影响了。所以你想修改远程分支,最好的方式是使用revert。

所以要记住如果你的commit已经发布到远程,就不要使用reset的方式了。

git的一部分底层知识

我之前一直认为,git commit 之间是存储的是变化,也就是说commit2不会存一份新的代码,而是只存储对于commit1的diff,后来我阅读了一部分git的底层原理知识,发现这个想法基本可以不去考虑了(即使git 为了优化空间,会以“diff”的方式,来压缩,从而减小空间。但是这部分底层优化对于你理解这个模型没什么意义,本质上你还是要认为每个版本都是独立的单元。)

git-reset

git基本上是一个数据库,所有的数据都可以用key value的方式去引用到,那个key基本上就是一段哈希序,数据库的数据类型有上图几种。

红色的是 blob 对象 ,基本上一个代码文件就存储为一个 blob 对象,除非代码文件被修改了,否则新的commit还会引用到之前版本的blob对象。(如上图,commit3 也在引用 test.txt version2)

紫色的是tree对象,它可以说类似于文件夹目录,所以tree可以指向多个其它的tree或者blob对象。

绿色的被称为commit (提交) 对象,可以说一个commit对象,指向一个tree对象(项目的根目录),这样一个commit对象就代表一次完整的提交,表示当前项目的状态。同时commit对象也会指向它的父commit。

灰色的被称为引用对象,也就是分支对象了。 基本上 Git 中的一个分支其实就是一个指向某个工作版本一条 HEAD 记录的指针或引用。
而HEAD 是一个指向你当前所在分支的引用标识符。
这个数据库可以说就是在你运行git init的时候创建的,init后,你会看到各种各样的文件夹和文件,如下就是git的数据库的样子:

1
2
3
4
5
6
7
8
9
10
$ ls
HEAD
branches/
config
description
hooks/
index
info/
objects/
refs/

我们所做的git操作,就是把工作目录修改的内容,存储到这个git数据库,或者选择某个版本的仓库,把它载入到当前工作目录中来。

所以有了上面的知识,你就不难理解,git是如何比较commit之间的不同,然后进行回退的了,因为它根本没有“存差异”(试想如果是存差异 ,是不是还要合并或者重放这些差异),存的就是每个版本的快照。
这个在pro git这本书一开始就提到了:

其他系统在每个版本中记录着各个文件的具体差异

git-reset

Git 保存每次更新时的文件快照

git-reset