Git的reset与checkout的区别

Git的reset与checkout的区别

Head,Index,Working Director

  1. Git里面的三个重要区域

    • HEAD指向最近一次commit里的所以snapshot
    • Index 缓存区域,只有Index区域里的东西才可以被commit
    • Working Directory 用户操作区域

初始状态

当你checkout分支的时候,git做了这么三件事情

> 将HEAD指向那个分支的最后一次commit
> 将HEAD指向的commit里所有的文件的snapshot替换掉Index区域里面的内容
> 将Index区域里面的内容填充到Working Directory里

所以你可以发现,HEAD,Index,Working Directory这个时候里的内容都是一摸一样的。
注意:一般会误解为,Index中的内容是空的,只有git add后才会有东西。实际上不是,Index里一直是有东西的。 ,所以,git里的所有操作就是对这三个区域的状态的操作。

Changed

如果你在Working Directory里修改了文件,git会发现Working Directory里面的内容与Index区域里面的内容不一致了。这个时候git status的结果就是:
Changes not staged for commit

Staged

一个文件仅仅changed是不能够被commit的,git要求只能提交Index里的东西。所以需要git add,这个命令的意思是,把changed的文件的内容同步到Index区域里。这样Working Directory和Index区域的内容就一致了,这个过程称之为stage,这个时候git status的结果是:Changes to be commited

Commited

最后,就可以提交了git commit,这样,就把HEAD的状态和Index以及Working Directory就形成了一致了。

reset

reset是用来修改提交历史的,想象这种情况,如果你在2天前提交了一个东西,突然发现这个提交是有问题的。这个时候你有两个选择,要么使用git revert(推荐),要么使用git reset

上图可以看到git reset是会修改版本历史的,他会丢弃掉一些版本历史。而git revert是根据那个commit逆向生成一个新的commit,版本历史是不会破会的。

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

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

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

不带文件参数的reset

前面章节已经说到Git有三个区域,Git的所有操作实际上是在操作这三个区域的状态(或内容)。
git reset配合不同的参数,对这三个区域会产生不同的影响。reset实际上有三个步骤,根据不同的参数可以决定执行哪个步骤(–soft,–mixed,–hard)。

1. 改变HEAD所指向的commit(--soft)
2. 执行第一步,将Index区域更新为HEAD所指向的commit里包含的内容(--mixed)
3. 执行第1,2步,将Working Directory区域更新为HEAD所指向的commit里包含的内容(--hard)

带文件参数的reset

上面讲到的git reset实际上不带参数的,那带上文件参数呢?

  1. HEAD不会动
  2. 将那个commit的snapshot里的文件放到Index区域中

需要注意的是带文件参数的git reset没有–hard, –soft这两个参数。只有–mixed参数。

unstage

1
2
git reset file.txt
git reset --mixed HEAD file.txt

上面这两条命令是一样的,都是reset到HEAD上,这个例子的意义在于,unstage file,当你把一个文件stage到Index区域后悔了,那么只需把Index区域里的这个文件恢复到最近一次commit的状态(也就是HEAD),那就相当于unstage。

恢复到历史版本

下面这个命令就是将某个文件恢复到历史版本上。
git reset eb23er file.txt
这个例子的意思在于,把某个文件恢复到Index区域里,然后直接commit,这样就等于把这个文件恢复到历史版本了,这样依赖你都不需要改动working Directory了。

checkout

前面讲到的checkout是会修改HEAD的指向,变更Index区域的内容,修改Working Directory里的内容。
这看上去很像reset –hard,但两者有两个重要的差别

  1. reset会把working directory里面所有的内容都更新掉,checkout不会去修改你在working Directory里修改过的文件。
  2. reset把branch移动到HEAD指向的地方,checkout则把HEAD移动到另一个分支。

第二个区别可能有点那以理解,举例来说:假若你有两个分支master和develop,这两个分支指向不一样的commit,我们现在在develop分支上(HEAD指向的地方)

如果我们git reset master,那么develop就会指向master所指向的那个commit。
如果我们git checkout master,那么develop不会动,只有HEAD会移动。HEAD会指向master。看图:

带文件参数

当执行git checkout [branch] file,checkout干了这件事情:

  1. 更新了index区域里面的file文件的内容
  2. 更新了working directory里file文件的内容