Git 是一款开源的分布式版本控制系统,具备分布式、轻量级分支、强大的协作能力以及适用于大小项目的版本管理。本文简要介绍Git工具的特性、Git中的对象以及分支管理,以加深了解。
1、版本控制系统介绍
版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。它的主要目的是跟踪和记录软件开发过程中的每个版本,记录每个版本的修改历史、版本号和发布日期等信息,以便更好地管理代码和文档,并帮助开发人员更好地协作和开发软件。
通过版本控制,开发人员可以随时回滚到之前的稳定版本,恢复错误或进行分支开发等操作,以便更好地管理软件开发过程中的变化和风险。
1.1 分布式版本控制系统
版本控制通常与版本控制系统(Version Control System,VCS)相关联,如Git、Mercurial、SVN等。这些系统提供了存储代码、管理版本历史、分支和合并等功能,帮助开发人员更好地协作和管理软件开发过程中的版本变化。
1.1.1 版本控制系统的发展
最早的版本控制系统是本地化管理的,采用某种简单的数据库记录文件的历次更新差异。但随着合作开发和协调工作的需要,集中式的版本控制系统(CVCS)出现了,不同的开发人员通过客户端连接到集中管理的服务器上,提交或读取最新的文件,比如常用的SVN就是这种工作模式。
在集中式的版本控制系统中,管理员可以轻松管控每个开发者的权限,并且维护成本低。但是这种架构下集中式服务器存在明显的单点故障,如果版本控制服务器宕机,则无法提供服务。如果服务器数据丢失并且无法恢复,那么整个项目的数据和变更历史也随之丢失。
为解决以上问题出现了分布式版本控制系统(DVCS),其基本设计理念是客户端不仅仅提取最新版本的快照,而是把代码仓库完整的镜像下来,相当于在本地有一份完整的数据拷贝。这样,当CVS服务器故障时,通过任何一个本地的镜像仓库就可以恢复数据。同时还可以和不同的远端代码仓库进行交互,设置不同的协作流程。
1.1.2 常见的版本控制系统
常用的分布式版本控制工具包括以下几种:
- Git:开源的分布式版本控制系统,广泛用于软件开发和项目版本管理。Git在处理大型项目和团队协作方面表现出色,是许多企业和开发团队的首选工具。
- Mercurial:另一种流行的分布式版本控制系统,与Git类似。它同样支持协作开发、分支管理、版本回滚等功能,并具有良好的跨平台支持性。
- SVN:Subversion(SVN)是一种集中式版本控制系统,它在一些团队和项目中仍然被广泛使用。SVN提供了易于使用的界面和强大的版本管理功能,尤其适用于小型团队和单个个体的项目管理。
- Perforce:一种高性能的分布式版本控制系统,适用于大型团队和大型项目的版本管理。Perforce提供了丰富的功能,包括版本控制、工作空间管理、分支和合并等,还支持多语言和跨平台支持。
- Bazaar:开源的分布式版本控制系统,提供类似于Git和Mercurial的功能。Bazaar强调易用性和可扩展性,支持多种平台和开发环境,包括Python、C++、Java等。
下面主要介绍Git版本控制工具的功能及使用。
1.2 Git版本控制系统特性介绍
1.2.1 直接记录快照
Git和其它VCS系统不同之处在于对待数据的方式上,其它系统如Mercurial、SVN等以文件变更列表的方式存储信息,存储的信息是一组基础文件以及随着时间累积的对文件的更新操作,通常称为基于差异的版本控制。如下图所示:
Git则是将文件系统的快照保存起来,每个版本保存的是当前文件系统的全量复制数据。每当提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。为了效率,如果文件没有修改,Git不再重新存储该文件,而是只保留一个链接指向之前存储的文件。
1.2.2 本地执行操作
在Git中大部分操作只需要访问本地的文件和资源,因为本地磁盘上有项目的完整历史,大部分操作不需要和远端的服务端进行交互,瞬间完成文件查询和提交。比如浏览项目的历史,可以直接读取本地的数据库进行查询;或者想对比当前版本和历史版本的差异,可以直接在本地读取历史文件进行对比。这样在一些离线环境下也可以正常工作,非常的便捷。
1.2.3 完整性保证
Git中的所有数据在存储前都会计算校验和,如果在传输过程中丢失信息或者损坏文件,Git能够及时发现。Git使用SHA-1计算哈希值,基于Git中文件的内容或目录结构计算出来40个十六进制字符组成的字符串。
1.2.4 只添加数据操作
在Git中执行的绝大部分操作都是添加数据的操作,也就是Git中不会执行任何可能导致文件不可恢复的操作。在数据没有提交更新时有可能丢失,但是一旦提交到Git上,就很难丢失数据,因为每次更新时每个客户端本地都会保存一份完整的快照数据。
1.3 Git本地安装部署
1)使用以下命令在centos系统中安装Git
# yum install git –y
# git --version
git version 1.8.3.1
2)配置用户和email地址
# git config --global user.name "Tango"
# git config --global user.email tango@com
# git config --list
user.name=Tango
user.email=tango@com
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
2、Git工具原理剖析
2.1 Git中三种状态
2.1.1 Git文件三种状态
在Git中文件有三种状态:已提交(committed)、已修改(modified) 和 已暂存(staged)
- 已修改:表示修改了文件,但还没保存到数据库中。
- 已暂存:表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
- 已提交:表示数据已经安全地保存在本地数据库中。
2.1.2 Git项目三个阶段
基于这三个状态,Git项目分为三个阶段:工作区、暂存区以及Git目录
- 工作区是对项目的某个版本独立提取出来的内容,这些数据是从Git仓库的压缩数据库中提取出来,放在磁盘上用于后续的使用或修改。
- 暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在Git仓库目录中。
- Git仓库目录是Git用来保存项目的元数据和对象数据库的地方。
2.1.3 Git基本工作流程
Git基本工作流程如下:
- 在工作区中修改文件;
- 将想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区;
- 提交更新,找到暂存区的文件,将快照永久性存储到Git目录。
如果Git目录中保存着特定版本的文件,就属于 “已提交” 状态;如果文件已修改并放入暂存区,就属于“已暂存”状态;如果自上次检出后,作了修改但还没有放到暂存区域,就是“已修改”状态。
2.2 Git中对象
2.2.1 Git目录结构
当在一个新目录或已有目录执行git init时,Git会创建一个.git目录,.git目录的典型结构如下:
drwxr-xr-x. 2 root root 6 Jul 1 19:08 branches
-rw-r--r--. 1 root root 92 Jul 1 19:08 config
-rw-r--r--. 1 root root 73 Jul 1 19:08 description
-rw-r--r--. 1 root root 23 Jul 1 19:08 HEAD
drwxr-xr-x. 2 root root 242 Jul 1 19:08 hooks
drwxr-xr-x. 2 root root 21 Jul 1 19:08 info
drwxr-xr-x. 5 root root 40 Jul 1 19:13 objects
drwxr-xr-x. 4 root root 31 Jul 1 19:08 refs
有4个目录非常重要,是Git的核心组成部分:
- HEAD文件:指向目前被检出的分支
- index文件:保存暂存区信息
- objects目录:存储所有数据内容
- refs目录:存储指向数据(分支、远程仓库和标签等)的提交对象的指针
除了这4个目录,其它如description文件仅供GitWeb程序使用;config文件包含项目特有的配置选项; info目录包含一个全局性排除(global exclude)文件,用以放置那些不希望被记录在.gitignore文件中的忽略模式;hooks目录包含客户端或服务端的钩子脚本。
2.2.2 数据对象
Git的核心部分是一个简单的键值对数据库,向Git仓库中插入任意类型的内容,会返回一个唯一的键值,通过该键值可以在任意时刻再次取回该内容。
1)初始化Git版本库
# git init tango
Initialized empty Git repository in /usr/local/git/tango/.git/
# find .git/objects/
.git/objects/
.git/objects/pack
.git/objects/info
可以看到Git对objects目录进行了初始化,并创建了pack和info子目录,但均为空
2)使用git hash-object创建一个新的数据对象并将它手动存入Git数据库中
# echo 'test tango' | git hash-object -w --stdin
ef9624cb2770e61445fdba38dacd4af9c9240c19
上述命令将数据存储到Git库中并返回一个40字符的hash值,也是一个唯一键值
3)查看Git中存储的数据
# find .git/objects -type f
.git/objects/ef/9624cb2770e61445fdba38dacd4af9c9240c19
在objects找到与内容对应的文件,一个文件对应一条内容,文件的命名是以内容加上header信息通过HSA-1算法计算hash值取得。前两个字符用于命名子目录,余下的38个字符则用作文件名。通过cat-file命令可以显示保存的内容:
# git cat-file -p ef9624cb2770e61445fdba38dacd4af9c9240c19
test tango
上述保存的对象称为数据对象(Blob Object)
2.2.3 树对象
树对象(Tree Object)能解决文件名保存的问题,允许将多个文件组织在一起。Git类似UNIX文件系统的方式,所有内容均以树对象和数据对象的方式存储。其中树对象对应了UNIX中的目录项,数据对象则大致上对应了inodes或文件内容。 一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的SHA-1指针,以及相应的模式、类型、文件名信息。
1)通过命令git update-index为一个单独文件创建一个暂存区,如test01
# echo "version 01" > test01
# git hash-object -w test01
df1c656f9be822fcb40701da71a32b10dc56cc50
# git update-index --add --cacheinfo 100644 \
> df1c656f9be822fcb40701da71a32b10dc56cc50 test01
2)通过git write-tree命令将暂存区内容写入一个树对象
# git write-tree
f2e68037cac9dcebccfdf85ad36dff4c04b0f19d
# git cat-file -p f2e68037cac9dcebccfdf85ad36dff4c04b0f19d
100644 blob df1c656f9be822fcb40701da71a32b10dc56cc50 test01
# git cat-file -t f2e68037cac9dcebccfdf85ad36dff4c04b0f19d
tree
3)创建新的树对象
# echo "new version" > test02
# git update-index --add test02
# git write-tree
f94cde1983fe96dec7cb0e83d09f66f63289d765
# git cat-file -p f94cde1983fe96dec7cb0e83d09f66f63289d765
100644 blob df1c656f9be822fcb40701da71a32b10dc56cc50 test01
100644 blob 2777791d6de7aac03f38b1b8403ad0fae82e28ac test02
4)通过调用git read-tree命令,可以把树对象读入暂存区,并指定–prefix选项,将一个已有的树对象作为子树读入暂存区
# git read-tree --prefix=bak f2e68037cac9dcebccfdf85ad36dff4c04b0f19d
# git write-tree
79fdfbd5a6a32829908c40babd8781a1c143784e
# git cat-file -p 79fdfbd5a6a32829908c40babd8781a1c143784e
040000 tree f2e68037cac9dcebccfdf85ad36dff4c04b0f19d bak
100644 blob df1c656f9be822fcb40701da71a32b10dc56cc50 test01
100644 blob 2777791d6de7aac03f38b1b8403ad0fae82e28ac test02
工作目录的根目录包含两个文件以及一个名为bak的子目录,该子目录包含test01文件的第一个版本。
2.2.4 提交对象
提交对象(commit object)是快照信息的保存。通过调用commit-tree命令创建一个提交对象,指定树对象的hash值以及该提交的父提交对象。
# echo 'first commit' | git commit-tree f2e68
411366ee5bd910edf128778fbf991fb5a2209959
通过git cat-file命令查看这个新提交对象
# git cat-file -p 411366
tree f2e68037cac9dcebccfdf85ad36dff4c04b0f19d
author Tango <tango@com> 1688222097 +0800
committer Tango <tango@com> 1688222097 +0800
first commit
提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交);之后是作者/提交者信息;留空一行,最后是提交注释。
2.2 Git分支
1)Git提交保存数据的过程
在进行提交操作时,Git会保存一个提交对象,其中包含了指向暂存内容快照的指针,同时还有作者信息、提交时候的输入信息和指向它的父对象指针。
每次修改后提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
2)Git分支
Git分支,在本质上是指向提交对象的可变指针。Git的默认分支是Master,在多次提交操作后,其实已经指向最后那个提交对象的Master分支。Master分支在每次提交时候会自动移动。下图是分支及其提交历史:
3)分支创建
使用git branch命令可以创建分支,在提交对象上会创建一个分支。
4)Head指针和分支切换
在Git中Head指针是指向当前所在的本地分支,比如上图的master上。如果需要切换到一个已经存在的分支,比如新建的分支testing,使用命令git checkout完成。
# git checkout testing
再切换回master分支上更新提交时,就会忽略新的分支testing,出现项目交叉。因此可以在不同分支之间来回切换,并且最后还可以合并起来。Git分支的创建和销毁都非常高效,因为本质上是包含所指对象校验和。
2.3 分布式Git
与传统的集中式CVS相比,Git的分布式特性使得开发者之间的协作变得更为灵活便捷。集中式系统中通常使用的是单点协作模型,中心的服务器做代码仓库,所有的开发这作为客户端与其同步。当两个开发人员同仓库克隆代码下来并修改提交的时候,第一个开发者可以顺利的提交,但是第二个开发者必须等第一个开发人员提交后并将其中的工作合并进来再提交上去,才不会覆盖掉。在Git中通过权限控制,也支持这种传统的工作流程模式。
2.3.1 集成管理者工作流
Git支持分布式管理流程,允许多个仓库存在,每个开发者拥有自己仓库的写权限和其他所有人仓库的读权限。当开发人员要为项目贡献时,可以克隆一份项目代码到本地仓库,然后将自己的修改推送上去,并通知官方仓库的维护者拉取更新合并到主项目。维护者可以在本地将贡献者的仓库添加为远程仓库并测试,然后将合并后的修改推送到官方仓库。主要流程如下所示:
- 项目维护者推送到主仓库;
- 贡献者克隆此仓库,做出修改;
- 贡献者将数据推送到自己的公开仓库;
- 贡献者给维护者发送邮件,请求拉取自己的更新;
- 维护者在自己本地的仓库中,将贡献者的仓库加为远程仓库并合并修改;
- 维护者将合并后的修改推送到主仓库;
这是GitHub和GitLab等集线器式(hub-based)工具最常用的工作流程。
2.3.2 主管与副主管工作流
多仓库工作流程的变种,适用于超大型的项目,比如Linux内核项目。各个副主管(lieutenant)分别负责集成项目中的特定部分,副主管头上还有一位称为主管(dictator)的总集成管理者负责统筹,主管维护的仓库作为参考仓库,为所有协作者提供他们需要拉取的项目代码。整个流程如下:
- 普通开发者在自己的主题分支上工作,并根据master分支进行变基。这里是主管推送的参考仓库的master分支。
- 副主管将普通开发者的主题分支合并到自己的master分支中。
- 主管将所有副主管的master分支并入自己的master分支中。
- 最后,主管将集成后的master分支推送到参考仓库中,以便所有其他开发者以此为基础进行变基。
3、总结
Git 作为一款开源的分布式版本控制系统,广泛使用在开发项目的版本管理中。总结一下,具备以下特性:
- 分布式:Git是一个分布式的版本控制系统,每个开发者都保存完整的版本历史,不需要联网进行代码比较或签入。这使得开发人员可以在不同地点的设备上进行开发,不受网络限制。
- 轻量级分支:Git支持轻量级分支,使得团队成员可以方便地进行协作开发。分支的创建和切换非常快速,而且分支的合并也非常容易操作,这使得团队可以轻松地进行多个并行开发任务。
- 高效处理大型项目:Git在处理大型项目版本管理方面表现出色。它能够高效地管理大型项目的文件和版本历史,并提供快速的代码检索、比较和回滚等操作。
- 强大的协作功能:Git提供了丰富的协作功能,如分支、合并、标签等,以帮助团队成员更好地协同工作。通过远程仓库,开发人员可以方便地与其他成员共享代码,进行代码合并和协作开发。
- 可定制性:Git提供了丰富的配置选项和钩子(hooks),使得开发人员可以根据自己的需求和团队的工作流程进行定制。可以通过配置文件(如 .gitconfig)来设置个人和团队的偏好,以及定义自定义操作。
- 社区支持:Git是一个开源项目,拥有庞大的社区支持和活跃的开发人员。这使得 Git 能够持续地改进和发展,并与其他工具和库集成,满足不同场景的需求。
本文简要介绍了Git工具的特性,以及Git中的对象和Git分支管理和分布式工作流程实现。
参考资料:
- https://git-scm.com/book/zh/v2