Git - 在版本之间切换自如

Git 的使用说难也不难,对不少人而言,git 无外乎那么几种命令。使用 git 开发就如同一条流水线式的做业:pull 下来、checkout 分支、add 一下、commit 一下、push 完事。Git 用起来很是温馨,由于靠着这么一顿操做,大多数状况下都能知足须要。git

可是一旦有一天中午,昏昏欲睡的你猛然发现本身好几回 commit 错了东西,或是一直工做在一个错误的分支上,那么 git 对于你来讲,忽然间就变成了洪水猛兽,日常使用的命令没有一个派的上用场。json

痛定思痛,除了知足平常开发,仍是得掌握更多 git 的使用,才能让在下一次犯错的时候吃上一瓶后悔药。app

基础知识

学习 git 的一个重要的技巧就是结合类弹珠图的 git 提交历史来看,推荐使用一些图形化界面好比 Sourcetree 来观察每次执行命令以后 history 的变化。学习

提交对象

使用 git 做为版本控制系统,首先要理解什么是一个版本:每当进行一次 commit 操做时,Git 会保存一个提交对象(commit object),能够理解这个提交对象就包含了此次改动的内容快照,根据不一样提交对象的 commit id,能够随时访问不一样版本的内容。3d

举例来说:版本控制

1)添加 README 文件,add -> commit -> git 生成 commit 98c27 2)更新 README 为 v二、添加 app.js、package.json 文件,add -> commit -> git 生成 commit 2c9be 3)更新 README 为 v三、更新 app.js 为 v2,add -> commit -> git 生成 commit 1a35c指针

示意图以下:code

时间轴是从左往右,可是 commit 的指向正好相反,由于每一个新建立的 commit,都之前一个 commit 为父节点,因此指向前一个 commit。cdn

每个 commit 都包含了 当时版本的文件快照,也就是说,切换到某一个 commit,就能回到对应的版本上去。对象

分支

因此,咱们如何知道本身在哪一个 commit 上?通常来讲,咱们都是工做在分支上的,好比当一个项目初始化时,都默认有一个 master 分支,分支本质上仅仅是指向提交对象的可变指针。好比以下的 master 分支上,README 已是 v3 版本了:

当在 master 分支上 checkout 一个分支出来时,仅仅是建立了一个新的指针,指向了当前的 commit。

因此此时 master 分支上和 feature 分支上的内容是同样的。

咱们接着 commit,而后 master 的指针就会指向新的 commit:

为何不是 feature 的指针改变?换句话说,git 怎么知道咱们处在哪一个分支?这是由于有一个特殊的指针 HEAD,它指向当前所在的本地分支:

刚才建立分支使用的是 git branch 命令,它只是建立一个分支,并不会自动切换过去,使用 git checkout 命令使得 HEAD 指向特定的分支:

继续 commit,feature 将往前移动:

这时能够发现两个分支开始分叉了,也就是说这两个分支基于同一个版本分别作了不一样的改动。

三个集合

要理解如何操纵 commit,还须要理解 git 中的三个集合。在 git 中,文件有三种状态:已修改(modified)、已暂存(staged)和已提交(committed),它们分别对应于三个区域:工做区(working directory)、暂存区(staging area) 和 版本库(repository)。

git 经过比较三个区域的内容,来提示用户须要作的操做。好比工做区与暂存区不一样,意味着你须要 add;暂存区与版本库不一样,意味着你已经 add 但还没有 commit。

咱们知道,基本的 Git 工做流程以下:

  1. 工做目录 中修改文件。
  2. 暂存文件(add),将文件的快照放入 暂存区域
  3. 提交更新(commit),找到暂存区域的文件,将快照永久性存储到 Git 仓库目录

假设咱们处在 feature 分支,当咱们进行以上操做后,commit 与三个区域的内容变化分别以下:

从左到右分别是工做区、暂存区和版本库,版本库上面标注了 HEAD,这表示咱们展现的是 HEAD 指针指向的内容,也就是当前分支所在的 commit。

接下来对工做区内的文件进行修改,换句话说就是修改磁盘上的文件:

修改磁盘上的文件只是改变了工做区的内容,接下来使用 git add 命令将修改提交到暂存区:

将修改提交到暂存区并不会新增一个 commit,接下来经过 git commit 来提交:

此时新的 commit 被建立,HEAD 指向的 commit 相应发生改变。

有了上面这些 git 操做的印象后,解释如何切换版本就容易多了。

Git 中的撤销

撤销多是使用过程当中最须要的操做,你可能在任什么时候候都须要撤销。根据状况不一样,撤销的命令也是不一样的。

撤销最近几回 commit

要撤销最近几回的提交,可使用 git reset,下面介绍它的三种模式:softmixedhard

假设目前分支状况以下,咱们须要撤销到 98c27 commit 上去。

1)soft 模式

执行 git reset --soft 98c27,git 会首先修改 HEAD 的指向,它会连带修改 HEAD 所在分支的指向:

如上图所示,如今的暂存区和 HEAD 是不一样的,这个操做本质上撤销了 2c9be 这个 commit,如同回到了上次准备 commit 的时候。(git 中的时光机!)

此时你能够进行后悔操做,继续修改文件再 add,而后从新 commit,这时会提交一个新的 commit。

2)mixed 模式

回到一开始的时候,假如咱们执行的是 git reset --mixed 98c27,它也会首先修改 HEAD 的指向,使得 HEAD 上的 commit 为 98c27

但还不够,git 还会接着更新你的暂存区,如同回到了你准备 add 的时候。(时光机再向前!)

对你来讲可能更方便了,继续改就行,而后从新 add、commit。这其实是 reset 的默认模式,等同于 git reset 98c27

3)hard 模式

不用我多说你可能已经意识到 hard 是干什么用的了。这一次 git 摧枯拉朽,把你的 HEAD、暂存区、工做区全给干掉了:

一会儿回到了你开始写需求的时候。因此这个命令是 危险 的,除非你真的打算不要这些修改了,不然最好不要用。

不过即便你真的用了又后悔,那也是有办法的,在 git 里面,既然能回到过去,也能在过去穿越到将来。使用 git reflog 能够查看你最近的修改,找到最前面的 commit id,能够继续使用 reset 穿回去。

合并 commit

有时候你可能发现本身刚才提交的好几个 commit 其实都是中间状态,还不如把它们合并成一个。根据上面的 reset,实际上就能完成这件事情。

好比下面的场景,咱们多提交了一个 File V1.1 的中间版本,但愿将其从 commit 历史中去掉:

那其实能够直接 reset 到 v1 版本:

而后从新进行 commit,这样就会将 v1 以后的修改都提交到了新的版本,如同移除了中间的版本。

固然,这个场景也能用 rebase 解决,以后会提到。

挪动 commit

在多个分支上切换开发的时候,有时候会忘记切换分支就开始开发。当发现本身提交的 commit 放错分支怎么办呢?在 git 中,这也不算个事,经过 git cherry-pick 就能解决。

cherry-pick 能够将指定的 commit “摘到”当前的分支上面,git 会为你从新生成一个 commit,但内容与 pick 的 commit 一致。

若是你要 pick 好几个 commit,它们之间有依赖关系,那须要根据前后顺序依次进行 cherry pick。

当发生冲突时,此时须要修改文件解决冲突,可使用 git cherry-pick --abort 放弃这次 pick,或者解决完 add 进暂存区,而后使用 git cherry-pick --continue。注意这里并非使用 git commit,若是你须要改变 commit 的信息,可使用 commit,不然 git 会默认使用 pick 的 commit 的信息。

相关文章
相关标签/搜索