深入了解Git:分布式版本控制,轻量级分支,高效协作,适用于各种规模的项目。
原文标题:分布式版本控制系统Git介绍
原文作者:牧羊人的方向
冷月清谈:
不同于传统的基于差异的版本控制系统,Git 保存文件系统的完整快照,并在文件未修改时仅保留链接,提高了效率。其本地执行操作的特性使得大部分操作无需联网即可完成,例如浏览项目历史和对比版本差异,这对于离线环境下的工作非常便捷。
Git 的数据完整性由 SHA-1 校验和保证,可以及时发现数据传输过程中的丢失或损坏。此外,Git 仅执行添加数据的操作,确保提交后的数据难以丢失。
Git 的核心是一个键值对数据库,通过唯一的键值可以随时取回数据。它使用三种对象类型:数据对象(blob)存储文件内容,树对象(tree)组织文件,提交对象(commit)保存快照信息,包括指向树对象的指针、作者信息、提交信息和父对象指针。
Git 的分支是指向提交对象的可变指针,默认分支为 Master。创建分支实际上是在提交对象上创建一个新的指针。HEAD 指针指向当前所在的本地分支。通过 `git checkout` 命令可以在不同分支之间切换,并最终合并它们。
Git 的分布式特性支持多种协作流程,例如集成管理者工作流和主管与副主管工作流,使得团队协作更加灵活。
怜星夜思:
2、Git 的分布式工作流程如何应用于实际的团队协作中?有哪些最佳实践可以分享?
3、除了命令行操作,还有哪些 GUI 工具可以辅助我们更好地使用 Git?它们各自有什么特点?
原文内容
Git是一款开源的分布式版本控制系统,具备分布式、轻量级分支、强大的协作能力以及适用于大小项目的版本管理。本文简要介绍Git工具的特性、Git中的对象以及分支管理,以加深了解。
1、版本控制系统介绍
版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。它的主要目的是跟踪和记录软件开发过程中的每个版本,记录每个版本的修改历史、版本号和发布日期等信息,以便更好地管理代码和文档,并帮助开发人员更好地协作和开发软件。
通过版本控制,开发人员可以随时回滚到之前的稳定版本,恢复错误或进行分支开发等操作,以便更好地管理软件开发过程中的变化和风险。
版本控制通常与版本控制系统(Version Control System,VCS)相关联,如Git、Mercurial、SVN等。这些系统提供了存储代码、管理版本历史、分支和合并等功能,帮助开发人员更好地协作和管理软件开发过程中的版本变化。
最早的版本控制系统是本地化管理的,采用某种简单的数据库记录文件的历次更新差异。但随着合作开发和协调工作的需要,集中式的版本控制系统(CVCS)出现了,不同的开发人员通过客户端连接到集中管理的服务器上,提交或读取最新的文件,比如常用的SVN就是这种工作模式。
在集中式的版本控制系统中,管理员可以轻松管控每个开发者的权限,并且维护成本低。但是这种架构下集中式服务器存在明显的单点故障,如果版本控制服务器宕机,则无法提供服务。如果服务器数据丢失并且无法恢复,那么整个项目的数据和变更历史也随之丢失。
为解决以上问题出现了分布式版本控制系统(DVCS),其基本设计理念是客户端不仅仅提取最新版本的快照,而是把代码仓库完整的镜像下来,相当于在本地有一份完整的数据拷贝。这样,当CVS服务器故障时,通过任何一个本地的镜像仓库就可以恢复数据。同时还可以和不同的远端代码仓库进行交互,设置不同的协作流程。
-
Git:开源的分布式版本控制系统,广泛用于软件开发和项目版本管理。Git在处理大型项目和团队协作方面表现出色,是许多企业和开发团队的首选工具。
-
Mercurial:另一种流行的分布式版本控制系统,与Git类似。它同样支持协作开发、分支管理、版本回滚等功能,并具有良好的跨平台支持性。
-
SVN:Subversion(SVN)是一种集中式版本控制系统,它在一些团队和项目中仍然被广泛使用。SVN提供了易于使用的界面和强大的版本管理功能,尤其适用于小型团队和单个个体的项目管理。
-
Perforce:一种高性能的分布式版本控制系统,适用于大型团队和大型项目的版本管理。Perforce提供了丰富的功能,包括版本控制、工作空间管理、分支和合并等,还支持多语言和跨平台支持。
-
Bazaar:开源的分布式版本控制系统,提供类似于Git和Mercurial的功能。Bazaar强调易用性和可扩展性,支持多种平台和开发环境,包括Python、C++、Java等。
下面主要介绍Git版本控制工具的功能及使用。
Git和其它VCS系统不同之处在于对待数据的方式上,其它系统如Mercurial、SVN等以文件变更列表的方式存储信息,存储的信息是一组基础文件以及随着时间累积的对文件的更新操作,通常称为基于差异的版本控制。如下图所示:
Git则是将文件系统的快照保存起来,每个版本保存的是当前文件系统的全量复制数据。每当提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。为了效率,如果文件没有修改,Git不再重新存储该文件,而是只保留一个链接指向之前存储的文件。
在Git中大部分操作只需要访问本地的文件和资源,因为本地磁盘上有项目的完整历史,大部分操作不需要和远端的服务端进行交互,瞬间完成文件查询和提交。比如浏览项目的历史,可以直接读取本地的数据库进行查询;或者想对比当前版本和历史版本的差异,可以直接在本地读取历史文件进行对比。这样在一些离线环境下也可以正常工作,非常的便捷。
Git中的所有数据在存储前都会计算校验和,如果在传输过程中丢失信息或者损坏文件,Git能够及时发现。Git使用SHA-1计算哈希值,基于Git中文件的内容或目录结构计算出来40个十六进制字符组成的字符串。
在Git中执行的绝大部分操作都是添加数据的操作,也就是Git中不会执行任何可能导致文件不可恢复的操作。在数据没有提交更新时有可能丢失,但是一旦提交到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工具原理剖析
-
已修改:表示修改了文件,但还没保存到数据库中。
-
已暂存:表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
-
已提交:表示数据已经安全地保存在本地数据库中。
-
工作区是对项目的某个版本独立提取出来的内容,这些数据是从Git仓库的压缩数据库中提取出来,放在磁盘上用于后续的使用或修改。
-
暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在Git仓库目录中。
-
Git仓库目录是Git用来保存项目的元数据和对象数据库的地方。
-
在工作区中修改文件;
-
将想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区;
-
提交更新,找到暂存区的文件,将快照永久性存储到Git目录。
如果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
-
HEAD文件:指向目前被检出的分支
-
index文件:保存暂存区信息
-
objects目录:存储所有数据内容
-
refs目录:存储指向数据(分支、远程仓库和标签等)的提交对象的指针
除了这4个目录,其它如description文件仅供GitWeb程序使用;config文件包含项目特有的配置选项;info目录包含一个全局性排除(global exclude)文件,用以放置那些不希望被记录在.gitignore文件中的忽略模式;hooks目录包含客户端或服务端的钩子脚本。
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)
树对象(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文件的第一个版本。
提交对象(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
提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交);之后是作者/提交者信息;留空一行,最后是提交注释。
在进行提交操作时,Git会保存一个提交对象,其中包含了指向暂存内容快照的指针,同时还有作者信息、提交时候的输入信息和指向它的父对象指针。
每次修改后提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
使用git branch命令可以创建分支,在提交对象上会创建一个分支。
在Git中Head指针是指向当前所在的本地分支,比如上图的master上。如果需要切换到一个已经存在的分支,比如新建的分支testing,使用命令git checkout完成。
# git checkout testing
与传统的集中式CVS相比,Git的分布式特性使得开发者之间的协作变得更为灵活便捷。集中式系统中通常使用的是单点协作模型,中心的服务器做代码仓库,所有的开发这作为客户端与其同步。当两个开发人员同仓库克隆代码下来并修改提交的时候,第一个开发者可以顺利的提交,但是第二个开发者必须等第一个开发人员提交后并将其中的工作合并进来再提交上去,才不会覆盖掉。在Git中通过权限控制,也支持这种传统的工作流程模式。
-
项目维护者推送到主仓库;
-
贡献者克隆此仓库,做出修改;
-
贡献者将数据推送到自己的公开仓库;
-
贡献者给维护者发送邮件,请求拉取自己的更新;
-
维护者在自己本地的仓库中,将贡献者的仓库加为远程仓库并合并修改;
-
维护者将合并后的修改推送到主仓库;
这是GitHub和GitLab等集线器式(hub-based)工具最常用的工作流程。
-
普通开发者在自己的主题分支上工作,并根据master分支进行变基。这里是主管推送的参考仓库的master分支。
-
副主管将普通开发者的主题分支合并到自己的master分支中。
-
主管将所有副主管的master分支并入自己的master分支中。
-
最后,主管将集成后的master分支推送到参考仓库中,以便所有其他开发者以此为基础进行变基。
3、总结
-
分布式:Git是一个分布式的版本控制系统,每个开发者都保存完整的版本历史,不需要联网进行代码比较或签入。这使得开发人员可以在不同地点的设备上进行开发,不受网络限制。
-
轻量级分支:Git支持轻量级分支,使得团队成员可以方便地进行协作开发。分支的创建和切换非常快速,而且分支的合并也非常容易操作,这使得团队可以轻松地进行多个并行开发任务。
-
高效处理大型项目:Git在处理大型项目版本管理方面表现出色。它能够高效地管理大型项目的文件和版本历史,并提供快速的代码检索、比较和回滚等操作。
-
强大的协作功能:Git提供了丰富的协作功能,如分支、合并、标签等,以帮助团队成员更好地协同工作。通过远程仓库,开发人员可以方便地与其他成员共享代码,进行代码合并和协作开发。
-
可定制性:Git提供了丰富的配置选项和钩子(hooks),使得开发人员可以根据自己的需求和团队的工作流程进行定制。可以通过配置文件(如 .gitconfig)来设置个人和团队的偏好,以及定义自定义操作。
-
社区支持:Git是一个开源项目,拥有庞大的社区支持和活跃的开发人员。这使得 Git 能够持续地改进和发展,并与其他工具和库集成,满足不同场景的需求。
本文简要介绍了Git工具的特性,以及Git中的对象和Git分支管理和分布式工作流程实现。
参考资料:
-
https://git-scm.com/book/zh/v2










