Git基础概念和原理

Git简介

Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理。Git的clone和SVN的checkout有着非常重要的区别。Git的clone是把服务器上的Git仓库的所有数据都取下来了(任一的一个文件的更新历史记录)。而SVN的checkout只是把SVN服务器上面的当前最新的数据给取下来,像某个文件的更改记录是不会拿下来的。

Git内部原理

首先要弄明白一点,从根本上来讲Git是一个内容寻址(content-addressable)文件系统,并在此之上提供了一个版本控制系统的用户界面。所谓内容寻址文件系统,Git的核心部分是一个简单的键值对数据库(key-value data store),你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索(retrieve)该内容。

当在一个新目录或已有目录执行git init时,Git会创建一个.git目录, 这个目录包含了几乎所有Git存储和操作的对象。如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。该目录的结构如下所示:

$ ls -F1
HEAD
config
description
hooks/
info/
objects/
refs/

该目录下可能还会包含其他文件,不过对于一个全新的git init版本库,这将是你看到的默认结构。 description文件仅供GitWeb程序使用,我们无需关心。config文件包含项目特有的配置选项。info目录包含一个全局性排除(global exclude)文件,用以放置那些不希望被记录在.gitignore文件中的忽略模式(ignored patterns)。hooks目录包含客户端或服务端的钩子脚本(hook scripts)。

剩下的四个条目很重要,即HEAD文件、(尚待创建的)index文件,和objects目录、 refs目录。这些条目是Git的核心组成部分。objects目录存储所有数据内容。refs目录存储指向数据(分支)的提交对象的指针。HEAD文件指示目前被检出的分支。index文件保存暂存区信息。

Git三种文件状态及三个区域的概念

Git管理的文件有三种状态,你的文件可能处于其中之一,已修改(Modified)、已暂存(Staged)和已提交(Committed)。已修改表示修改了文件,但还没保存到数据库中。已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。已提交表示数据已经安全的保存在本地仓库中(就是已经执行git commit提交到本地仓库)。

由此引入Git项目在本地有三个工作区域的概念,工作区域、暂存区域和本地Git仓库。工作区域是某个文件还没有修改,或者已经修改但没有放到暂存区中,那么它就属于这个区域。暂存区域,是当把已经修改的文件,通过git add命令处理后,就会放到暂存区域中。git本地仓库,是当通过git commit命令将暂存区域里面的文件提交后,就会放到这个区域。最终,当通过git push命令会将本地仓库中修改推送到远程仓库。

如下是一个Git工作的流程图:

Git分支概念和使用

几乎所有的版本控制系统都以某种形式支持分支。使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。在很多版本控制系统中,这是一个略微低效的过程(常常需要完全创建一个源代码目录的副本)。对于大项目来说,这样的过程会耗费很多时间。

有人把Git的分支模型称为它的必杀技特性,也正因为这一特性,使得Git从众多版本控制系统中脱颖而出。为何Git的分支模型如此出众呢?Git处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。 与许多其它版本控制系统不同,Git鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。

如上图,就是git的一个提交历史图,灰色图标就是每个commit提交,灰色图标中内容就是commitid的缩写,从commit节点的拓扑图来看,每个commit节点都有一个或多个父commit节点,称为commit节点的parent,当然有特例,有些commit节点是没有parent的,没有parent的称为root commit。

在前面的基础上,我们来理解下Git的branch含义,我们可以把branch理解为一个个的指针,branch指针指向某个commit节点,顺着该commit节点找到其所有parent节点和ancestor节点脉络,那么我们就知道了该分支所包括的所有提交内容。

上图中,origin/master表示远程仓库上的分支指针,master表示的是本地仓库中master分支指针,通过这个可以判断本地的提交有没有被pushu到远程仓库中。对于feature2,却没有origin/feature2,是因为该分支还没有push到远程仓库中,也就是远程仓库中还没有feature2分支。

实际上,Git分支管理之所有这么轻量,正式每个commit节点中,都包含了其所有父commit节点,这样顺着脉络,就可以找出任意一个分支的内容了。

在Git中,还有一个特殊的HEAD,HEAD也是一个指针,指向本地仓库中当前所在的分支(译注:将HEAD想象为本地仓库当前分支的别名)。

总结,Git中,branch可以理解为一个指针,该指针指向该分支上最新的一个提交。

使用git log命令或者一些图形化工具(如IDEA中Git面板工具)可以很直观的展示commit的拓扑图,还有分支的指针指向。透过commit拓扑图,我们可以清晰的看出来每个分支的内容,以及各个分支之间的关系。IDEA中Git面板的使用,可以参见IDEA部分。