2

程序员是怎么存档并管理文件版本的?

 2 years ago
source link: https://www.cnblogs.com/tanshaoshenghao/p/14979531.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

程序员是怎么存档并管理文件版本的?

大家好哇~ 欢迎来到波波和阿菌神奇的“科普”频道!

今天,我们为大家介绍程序员是如何怎么存档并管理文件版本的。

大家要做好心理准备,今天的“科普”稍有点点硬核,阿菌想从需求分析,产品设计,代码实现等全方位角度为大家“科普”,综合的东西较多,可能不太好看懂......

但内容应该还是有点点意思的,毕竟阿菌总是写一些乱七八糟的东西,如果暂时感觉难以消化可以考虑先收藏吖~

在开始之前,我们先为大家介绍一个概念,叫:版本管理。

我们先从日常生活讲起。

阿菌这坏小子有时会想回到自己的过去,比如说回到小学时候的自己,回到初中时的自己,回到高中时的自己,或者回到大学前的自己,重读一次大学等等......

人生的每个阶段,我们都可以看成自己的一个版本,比如说小学版的阿菌,初中版的阿菌,高中版的阿菌,大学版的阿菌......

要是老天真的给我们每个阶段都存了档,那我们就可以回到过去的版本,重新开始了!

从旧版本发展出新的人生,或许我们的人生可以拥有好几条分支路线呢......

虽然目前看来不太现实,就算真的有得选,阿菌也绝不会回到过去的版本,因为阿菌不敢保证在另一条人生分支上,还能遇到波姐......

咳咳,打住!

虽然人生没有存档,但是电脑上的文件可以存档!

大家考虑以下场景:假设学院安排我们做一次毕业晚会宣传活动,既要有 PPT,又要有文稿,还得有海报。

于是我们高高兴兴地把全学院的本科生资料编写到了 PTT,文稿和海报里。

这个时候,学院说:怎么可以只有本科生的资料?研究生的相关资料也要加进去!

然后我们在现有的 PPT,文稿和海报里,加入了研究生的资料。

接着学院说:临时通知,研究生的晚会另外举行!

大家可能就傻眼了:我们已经在 PPT,文稿和海报里加了研究生的信息,而且已经和本科生的内容融合到了一起,这删起来也太麻烦了吧!!!

要是我们提前把本科生的策划资料保存为一个版本就好了,这样就能直接把本科生的版本交给学院,完成工作。

现在场景有了,痛点有了,接下来我们着手设计一款软件产品来解决这个问题(有同学会说,阿菌你扯淡,我每个版本复制一份就行啦,搞个软件出来干嘛?呃呃,坚持看完就懂啦,它不只存档这么简单哦。我们手动拷贝存档容易出现各种各样的问题,比如忘了存,忘了存在哪,存的顺序搞乱了等等......试过就会有体会哦)。

现在我们有一个文件夹,文件夹目录下有 PPT,文稿和海报,要不我们就在这里创建多一个文件夹用于存档吧!把名字起名为“.jun”就好啦!

现在我们创建好了一个“.jun”文件夹用于存放当前目录的版本信息,接下来我们要思考的是,该如何保存各个文件的版本?

在计算机领域,我们来到的环节应该叫设计底层数据结构,我们可以把“.jun”文件夹看成一个数据库,这个数据库会用来保存当前文件夹下文件的版本数据。

嗯,不如这样吧!

我们就把这些.doc、.psd、.ppt文件称为 object 吧!(反正也不知道叫什么好)我们在“.jun”文件夹下创建一个“objects”文件用于存放各个 object 的信息,这样,我们就有了一个 object 数据库了!

呃,听起来好像很厉害,其实阿菌只是创建了两个文件夹......

现在我们想想一个 object 该存些什么东西比较好,究竟什么东西才能精准定位一个文件的版本信息呢?

想来想去,不如这样吧,我们一个 object 至少得包含三个信息:

  1. 文件的原本信息,我们PPT,WORD文档中的内容就叫原本信息,直接保存原本信息可能需要很大的空间(至少和这些文件本身一样大),我们可以先压缩,再保存。

  2. object 的类型,考虑到我们当前文件夹下除了有 PPT,文稿和海报之外,以后还可能放新的文件或者新的文件夹,文件和文件夹都应该叫 object,只不过可以用不同的类型区分他们。(大家可以在这留个心眼,这是这款版本控制管理软件的精华部分,后面就知道啦)

  3. 一串字符数字,我们起个专业点的名字叫哈希值,用于标识当前的 object,每个 object 都有独一无二的哈希值(其实就一串乱七八糟的数字字符,这样不容易重复)。

看到这里,大家可能会有疑惑:你们为啥一直在说怎么设计,我们更想知道的是,为什么这么设计?

下面揭示谜底:

大家先看第一张图,当前文件夹下的 PPT,文稿和海报,我们可以分别用三个 object 表示:

由于他们放在同一个目录下,于是我们可以用一个大的 object 来标记他们,我们把这个大 object 的类型定义为 tree(树干的感觉),这个 tree object 对等的就不是一个个文件了,而是一个文件夹:

细心的读者朋友会发现:咦?这个 tree object 貌似已经包含了当前文件夹下的所有文件信息,也就是说,这个 tree object 貌似已经可以记录当前文件夹的版本信息了!

我们可以通过这个 tree object 找到PPT,文稿和海报对应的 object 们,这些 object 保存有PPT,文稿和海报某个时间点的原信息,我们只要把这些信息解压出来,就能把文件夹恢复到曾经存档时候的样子了。

接下来的问题是:我们不会只保留一份存档,我们会保存很多份存档,如何才能把一系列存档组织起来呢?

接下来我们引入一个新的 object 类型,我们叫起名叫 commit 类型好了,就是一个提交的意思。想要把存档串联起来,我们得加上一项参数,指明上一个 commit object 是谁,或许我们还可以加上时间等信息,这样一来,每一次提交就是一个版本:

上面这个图由于位置不够,画得不够直观,我们再画一个图,大家明白每个 commit object 都指向一个 tree object 就行了。也就是说:commit object 只是 tree object 的一层封装,虽然还原出一个文件夹过去的存档我们只需要 tree object,但封装成了 commit object 后,通过“father”这个属性,就能把一系列的存档连起来,而且还能盖上时间戳,这样整个存档记录就很清晰了。

讲到这里不知道大家会不会冒出一些奇怪的想法,假设学院布下了两个任务:我们本科生,既要和研究生搞一次毕业联谊,又要和博士生搞一次毕业联谊,我们这套版本管理系统还能用上吗?(也只有阿菌才能想出这样奇奇怪怪的活动)

当然可以啦,请看下图:

我们可以创建两个 commit object(两个新的存档),分别指向最开始保存了本科生资料的存档,然后我们就能分别在两个新的存档上干活啦,而且两个存档互不影响,可以继续在两个存档之后建立新的存档,就像下面这样:

我们可以给上面的功能起个好听的名字,叫“分支”。

分支的功能还可以这样用,我们考虑下面这种场景:

学院不再搞花样了,只要做好本科生的毕业活动策划就行。但是学院规定的时间很短,阿菌一个人做不完,他找来了他的同学阿叉和阿勾一起做,三个人分别负责PPT,文稿和海报。

为了不影响最开始的版本,他们三个每人拉出来一条分支进行工作,每个工作阶段的内容照样进行版本管理:

等各自的工作完成后,合并出最新的版本:

这样一来,大家协同工作起来就方便多啦。

可能有同学会问:阿菌,你设计的 commit object 不是有一个父指针么?每个父指针指向上一个 commit object,在上面的图里,最终版的父指针指向谁呀?貌似一共有三个父节点?

呃,这个确实是阿菌疏忽了,图没有画好......

是这样的,有时候我们数据结构设计好了,最好就不要改了,遇到现有数据结构不能掌控的场景,我们就要设计处理流程,这就是所谓的“算法”吧......(毕竟增删改查某种程度也算是算法......)

我们总是说:软件 = 数据结构 + 算法,下面阿菌带大家还原这个处理流程的设计。

因为我们的 commit object 只能指向一个父节点,于是我们设计的合并流程是这样子的:最终的合并,交给一个人处理(站在一条分支上处理,假设叫它主分支)。

从上图可以看到,我们以阿叉制作PPT的分支作为主分支,把阿勾的分支内容合并到了阿叉的分支上,这样阿叉的分支就多出来一个合并节点,这个合并节点指向的是阿叉分支上的前一个节点,这样一来,PPT 和文稿内容就合并到主分支上了。

滴滴滴~ 下面我们把阿菌那坏小子制作的海报也合并上去:

这样一来,我们既没有破坏原来的数据结构,也没有破坏软件的设计:一个存档版本管理软件,我们可以在阿叉这条主分支上找到所有的内容。

当然阿菌这样的设计不太好,我们其实可以设计得更好,比如说单独抽出来一条主分支,阿叉,阿勾,阿菌制作各自内容的时候单独拉分支(一共四条分支),每个人都制作好了,再合并回主分支。这样主分支就会非常干净利落,而不是像现在这样,在主分支上,还能看到阿叉的各个版本......

呃呃,软件开发就是这样的啦,在摸索中不断总结最佳实践!

估计大家看着看着就能看出来啦:阿菌,你这讲的不是 Git 么?

没错,阿菌今天介绍的就是一款叫 Git 的分布式版本控制软件-版本控制部分的底层设计原理,现在大多数程序员都是基于 Git 进行协同开发的。和上面例子不同的是,程序员写的是代码文件,而不是文稿和PPT。有时候一个功能往往会有好几个程序员开发,大家可以理解为分组开发。常见主流的协作流程会是这样的:

简单介绍一下:

一、master 分支存储了正式发布的历史版本,是一个功能完整且随时可以发布到线上进行部署的可用分支。

二、Hotfix 分支是用来修复线上bug,快速打补丁的。

三、Release 是一个发布分支。

四、Develop 一般作为功能的集成分支。

五、 Feature 分支则是功能分支。

至于每个分支具体的用法,大家可以到网上搜索,根据阿菌的经验,其实看了也没啥用。只有真正到公司里参与到开发项目中,才能真正领会到各个分支的意义。

今天介绍的内容,是 Git 这款软件最基本的原理,在搞清楚了这个的基础上使用 Git,会轻松很多。Git 的另一个重要的特色是:分布式。也就是说,它是用于多人(公司或团队)协同进行存档和版本控制的。

有位大神看了我们的文章后认为如果我们能讲讲分布式,那这篇文章会更加加分,那肯定没问题。

我们现代的程序员,写代码的时候上来就是用 Git,理所应当以为版本管理系统都是现在分布式的样子。殊不知,以前的版本管理系统都是集中式的。

我们先简单介绍一下什么叫集中式,还是用本文的案例:

像上图这样,有一个集中的地方管理所有文件,每个人开发只要拉取特定文件进行开发,这叫集中式开发。

集中式开发的弊端大致有以下两点:

效率。中央仓库出了问题,所有人都无法正常工作了,因为大家都依赖他拉取和推送文件。
稳定性。中央仓库挂了,存档就没了。

针对以上弊端,我们能想到的,最直接的处理方式是:每个人都保存一份仓库的拷贝。如下图所示:

有同学会问,欸,阿菌,那我还有必要学 SVN 那些集中式版本管理软件么?

当然不用,Git 的出现,已经完全颠覆了过去的集中式版本管理系统。SVN 的版本管理策略和 Git 还是有很大区别的,阿菌没有说 SVN 不好,大家要知道,这里涉及的原因很多。这是时代变化所引起的变革,以及大环境所需导致的变迁,不存在孰好孰坏的问题。

我们尝试开辟一个角度想(不一定对):在以前的年代,内存,磁盘,计算资源都是很宝贵的,当时的机器根本就不适宜支持我们在每台机器上,保存完整的工程文件存档,也不可能采用 Git 这种压缩保存整个文件原信息的策略(增量保存能省很多空间,代价是牺牲性能)。所以,使用 SVN 这样集中式的版本管理系统,或许是个很好的选择。

现在机器越来越好了,磁盘大,网络快,直接就能在每个人的机器上保存完整副本。更重要的是,Git 本身的设计非常优良,它站在 Linux 操作系统肩上发家(最初是因为想分布式开发 Linux 而创造的 Git),后来还发展出了 Github 这样的开源社区。慢慢地,大家都愿意迁移到 Git 上开发了。

再换一个角度:大家想想,如果一个工程真的非常非常庞大,单台机器不能拉取整个工程进行开发,集中式的版本控制无疑是更好的选择。

但是现在业界流行微服务,系统的拆分解耦是大势所趋,这也注定了大工程会被拆分成小工程。而体量小的工程,恰好非常适合使用分布式版本控制。

存在即合理,任何一项技术,我们在评价它时,都不能脱离时代背景和现实需求。

我们这篇文章不教操作,关于 Git 操作的文章,网上一抓一大把,各种奇技淫巧,应有尽有。

各位如果想自己玩出奇技淫巧,那就跟着阿菌一起深入数据结构探索原理吧,那些只教奇技淫巧的博客,通常都不怎么说原理,懂了原理才能更好地发掘奇技淫巧吖!

有同学可能会问:阿菌,原理懂了,但是不太能和操作对应起来,我们平时用 Git,就是一条 pull 和一条 push,两条指令走天下。分支我是懂了,但这些分布式操作我还不太懂,不太能联系起来。。。

阿菌实习时的导师,曾教给阿菌一个非常重要的学习方法:当我们看到一个没接触过的东西的时候,我们首先要想 —— 如果这个东西交给你来做,你会怎么实现?

阿菌看过一点点 Git 的源码,也翻过官方文档,但是不可能在这里把这些东西全倒出来,我们尝试引导大家一点点思考。(最终的深入精确学习,还是建议大家看源码文档,想要深入学一个东西,这苦是不得不吃的)

我们沿用这个图进行讲解,在看之前,大家务必先搞清楚 object 的底层数据结构,以及怎样把 object 串联起来形成分支。我们先回顾一下当时的总览图:

之前我们提到以阿叉作为主分支,是一个不太好的设计,但是不影响我们讲解,我们假设阿叉已经开发完了,远程仓库就是阿叉的仓库。

现在阿勾已经开发完了,阿勾会把他的本地仓库推送到远程仓库,阿勾的仓库是这样子的:

阿菌能猜到一些小伙伴看到这懵逼了,千万不能懵逼,一定要全文串联回忆,我们的版本管理软件,就是一条链表加树,这里的每个圈圈都是一个 commit object,他们是对应的,阿菌只是用一个小圈圈代替,以节省位置:

明白了这个之后,我们来看一下合并的流程,首先阿勾会把自己的本地仓库推送到远程仓库(主分支):

现在,大家明白,为什么合并分支会多出来一个提交了吧?

还有一些常用的指令,什么 rebase 吖,不就是调整链表么?不懂的话可以去刷刷算法题,打打基础。

不过,多人一起办公难免会遇到一些问题,比如说大家同时修改了一个文件,发生了冲突。但懂了原理后这都很好解决,大家沟通一下用谁修改的版本就好了,协商出一个不冲突的版本,然后合并就行。合并的原理上文已经提到啦,一通百通。

也正由于是多人协同办公,注定我们的软件要具备联网的能力,涉及许多网络交互,这是分布式软件必不可少滴内容。但大家要相信,最底层原理懂了,具体如何使用,如何与他人在网络中合作,那都是很简单的内容,学一个软件关键是要学透它的底层数据结构,学明白后,上层操作都会引刃而解的。

前两天我们发布了一篇文章,能帮助大家进一步深入学习并掌握 Git 的底层原理:Git原理学习捷径:实现一个简易的Git

大家可以抽一个上午的时间跟着阿菌简单操作一下,毕竟 Talk is easy,想学会点东西,还是得动手实践。

阿菌是用 Go 语言写的,写得比较烂,有兴趣的同学用自己熟悉的语言写就好啦,毕竟写代码只是实现想法,撸起袖子加油干就好~

想了一下,下一篇技术类的科普或许可以给大家介绍一下爬虫,因为爬虫这个技能点容易延申到单机,集群,分布式这些听起来高大上的东西,我们会尽最大的努力带有兴趣的小伙伴入个门儿,有趣的理论加实践,学起来可带劲儿啦!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK