git 基本原理 与 常见case 处理方法
现在的项目开发中,基本都是多人参与,这就需要版本控制系统来管理我们的项目了。和大多数事物一样,git 诞生于一个极富纷争和大举创新的年代。
0. 简史
Linux 内核开源项目有着为数众广的参与者。 绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。 到 2002 年,整个项目组开始启用一个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。
到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统。 他们对新的系统制订了若干目标:
- 速度
- 简单的设计
- 对非线性开发模式的强力支持(允许成千上万个并行开发的分支)
- 完全分布式
- 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。 它的速度飞快,极其适合管理大项目,有着令人难以置信的非线性分支管理系统(令人窒息的分支管理)。
1. 基本原理
1.1 直接文件快照概念
Git 和其它版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其它系统则只关心文件内容的具体差异。
Git 通过指纹字符串来时刻保持数据的完整性。指纹字符串是 Git 使用 SHA-1 算法,通过对文件的内容和目录的结构计算出的一个 SHA-1 哈希值。这个字符串是由 40 个十六进制字符串组成,如下,我们经常看到: 696503be5f0630b43fce06cca01a9426c683bfb9
,就是我们的 commitId。
1.2 文件的三种状态
git 在管理项目时,项目中的文件存在如下三种状态:
- 已提交(committed)
- 已修改(modified)
- 已暂存(staged)
我们项目的文件都处于 working directory
,当文件进行了修改,文件的状态就会变为 modified
。通过 git add .
的命令,文件就会被提交到 staging area
,变为 staged
状态。最后通过 git commit
命令,文件就会被提交到 git directory
,文件状态就更新为 committed
。以上,就是 git 操作一个最基本的流程,以及这个过程中的状态变化。
1.3 提交
例如,项目的根目录下有三个文件,进行第一次 commit ,git 仓库中会有这样的结构产生:
我们会看到上图这样一个对象的链式结构:它们各自都代表什么呢?三个 blob
对象是三个文件的快照块,tree
对象记录目录树及其内容,最后有一个包含了指向 tree
对象的索引和其他提交信息元数据的 commit
对象。
我们提交几次代码后,就会出现这样的链:
由 commit
对象构成的链式结构,每个 commit 对象指向了项目文件的快照。那么,千呼万唤始出来,这个链其实就是我们 《1.4》要分析的分支了。
1.4 分支
分支到底是什么呢?其实,本质上,Git 中的分支仅仅是个指向 commit 对象的可变指针。默认使用 master 作为分支的默认名字。每一次提交,这个指针都会向前移动,指向最后一次提交对象,如下图:
创建分支
那么,到现在我们就应该知道,git 创建一个新的分支,其实就是创建了一个新的分支指针。例如,我们新建 testing,如下图:
切换分支
如上图,我们会看到除了 master
和 testing
还有另外一个指针,那就是 HEAD
。HEAD
指针的指向,代表了我们当前所在的分支。例如上图,HEAD 现在指向 master,那么当前的分支就是 master 分支。切换分支,就是改变 HEAD 指针的指向,例如下图:
现在,我们就把分支切换到 testing
分支了。
切换分支之后,我们可以继续提交更新文件,就会出现下图这样结果:
看到这个图,大家就应该知道我们后面会讲什么了,那就是:—–
分支合并
我们来看一下图这样的情况:
如图,我们项目的 git 管理情况,存在三个分支,分别是:master
、hotfix
和 iss53
。这个对应怎样的一种场景呢?例如:master 是我们项目的主分支,我们现在接到一个新的需要要开发,就切了分支 iss53,并且进行了一次 commit 。突然,发现一个紧急的线上问题,我们要修复,所以我们切回了 master 分支,然后又切出了一个 hotfix 分支,切到 hotfix ,修复完问题之后,我们进行了一次 commit。要提交上线了,那么我们就要合并分支了~~
操作:
结果:
hotfix 与 mater 进行了合并了。这是最简单的分支合并情况,下面来个复杂一些的,我们回到 iss53 继续开发。。。。。。。。项目变成了下面这样:
现在分支的合并就不会像上次 hotfix 那样直接合并移动指针那么简单了。
因为你的开发历史在更早地方就开始分叉了。看下图,master 分支的当前提交对象(C4)并不是 iss53 分支的直接祖先,git 需要做额外的处理。git 会用到两个分支的末端(C4 和 C5)以及它们的共同祖先 (C2)进行一次简单的三方合并计算。如下图红框标出的要进行合并的三个提交对象:
合并后的结果是重新生成一个新的快照,并自动创建一个新的提交对象 C6 指向这个快照。这个提交对象会有两个祖先(C4 和 C5)。
这样,我们的分支就算合并完成了。分支的改动到合并到了 master,iss53 如果不在使用,就可以删除掉了。
以上是 git 的一些基本原理,下面我们会讲一个好用但有时又让人害怕的命令git reset
。 git reset
命令充分利用到了上述的 git 基本原理,下面我们通过学习分析git reset
的用法与原理,来加深一下对 git 基本原理的理解~~
2. git reset 的原理
2.1 名词解释
working directory(git 的工作目录)
staging area(暂存区域)
git directory(repository)(本地仓库)
- HEAD (当前分支头指针位置)
2.2 git reset 常用来干什么呢
- git reset 之前
- git reset 之后
可以修改当前 Head 指针的位置。
我们常用 git reset commitId 回退到之前的 commitId 的状态,就是将当前分支的 HEAD
指针进行了移动,但是,git reset 一般常用的参数会有三种,这三种参数产生的效果是不同的:
(1)--mixed
(default)
--mixed
的效果。图示,使用
--mixed
回到某个 commit(例如CommitId2)的时候,
HEAD Repository
与
Staging Area
的状态是一致,与当前这个 CommitId2 的状态保持一致,但是,
Working Direcotry
还是之前 CommitId3 的状态。
(2)--soft
上图是 --soft
的效果。图示,使用 --soft
回到某个 commit(例如CommitId2) 的时候,只有 HEAD Repository
回到了CommitId2的状态, Staging Area
和 Working Direcotry
会与 CommitId3 的状态一致。
(3)--hard
上图是 --hard
的效果。图示,使用 --hard
回到某个 commit(例如CommitId2) 的时候,HEAD Repository
、 Staging Area
和 Working Direcotry
的状态是一致,都与 CommitId2 的状态一样。也就是说,刚刚的一些修改操作,一些保存到 Staging Area
的修改,都会随着你的 --hard
而消失的。
3. 常见 case 处理
3.1 删除远程仓库的某次错误提交
case one:
假设你有 3 个 commit 如下:
其中最后一次 commit 3
是错误的,那么可以执行:
此时,HEAD is now at commit 2
然后可以使用 git push --force
将本次变更强行推送至服务器。这样,在服务器上的最后一次错误提价也彻底消失了。
Pay Attention:此类操作存在风险因素,例如:在你的
commit 3
之后,其它小伙伴又提交了新的commit 4
,那么你强制推送之后,小伙伴的commit 4
也会跟着一起消失了。
3.2 修改上次提交的代码,做一次更完美的 commit
case two:
假如你上次提交的代码有些问题,不够完美,希望做一次更完美的 commit,且此时有很多代码你是出于修改状态的,你可以按如下流程做:
(1) git reset commitId
(注: 不要带 –hard) 到上一个版本
(2) git stash
,暂存修改
(3) git push --force
,强制 push,远程的最新的一次 commit 被删除
(4) git stash pop
,释放暂存的修改,开始继续修改代码
(5) git add . -> git commit -> git push
完成新的commit提交
4. 常用 git 技巧
(1)修改最后一次提交
有时候我们刚提交(git commit
)完,会发现漏掉了几个文件没有添加,又或者是 commit 的信息填写有误。想要撤销刚才的提交操作,可以使用下面的命令: git commit --amend
完整的例子:
(2)别名的使用
习惯使用 git 命令后,往往会有 “偷懒” 的想法,别名的使用可以让你少敲几个字母,提高效率。(当然,前提是你记得自己的别名,并且熟练使用它~~),如下:
这样配置之后,以后的 git co
就是 git checkout
了,以此类推,等等。。。
别名的使用不限于上面的命令,你可以根据自己的习惯,去配置更多的别名~~
End
饮水不忘挖井人~~膜拜一下大神 Linus
ps:推荐大家看一下他的公开课,网易应该有,大神的思想还是非常与众不同的~~