第 01 天:认识 Git 版本控制
笔者使用 Subversion (SVN) 已经将近 10 年,从来都不觉得有任何必要换成其他版本控制平台,直到前几年因为云端化的改变,慢慢转成 TFS 版本控制 (TFS Service),转变的过程还算顺利,只因为 SVN 与 TFS 的版本控制概念相近,都属于集中式版本控制系统。这类集中式版本控制系统,使用上简单、直观且容易进行权限控制,说真的,在大部分的开发情境下,Subversion 或 TFS 足够使用,那又是什么原因或是需求,迫使我们一定要转到 Git 版本控制呢?我相信,不同人采用 Git 一定有他的理由,有些人觉得好玩、有些人觉得新鲜、有些人觉得功能强大,无论如何,只要这个理由能够支持你去主动学习一个陌生技术,都是好的,本篇文章除了带大家认识 Git 版本控制机制外,也会说说我想转换到 Git 的理由。
文章目的
在软件开发领域,对源代码进行版本控制是非常重要的一件事,不同于 Subversion 或 TFVC (Team Foundation Version Control) 这类集中式版本控制系统,Git 是一套分布式版本控制系统(DVCS; Distributed Version Control System),并带来许多版本控制上的各种优势与解决传统集中式版本控制的缺失,例如支持本地操作、备份容易、功能强大且弹性的分支与合并等等。不过,由于 Git 版本控制无论在版控概念与工具使用上,都与传统集中式版控工具差异什大,因此造成了不小的学习门槛。
虽然说本次文章的主题是「30 天精通 Git 版本控制」,不过,说实在的,还真有点言过其实了,因为 Git 博大精深,有非常多细节可以探究,如果真的要应用在工作上,学几天可以真正上手呢?每天学一点,连续学习 30 天,似乎是个合理的数字 (或太多?),如果有一个工具大家都要用,而且要立刻上手的工具,如果学 30 天都还不知道怎么活用,那这学习门槛也太高了些。因此我想,这个系列的文章,主要还是专注于「如何在 30 天内学会 Git 版本控制,而且必须要能熟练的应用在实际开发工作上」,这才是本系列的真正目的,那些繁琐的细节,我不会特别强调,但总是有些重要的概念与细节还是不能错过,我会尝试在每一个主题中提到一部份,一有机会就会深入探讨,希望大家可以通过做中学,深刻体会 Git 版本控制的强大魅力。
转换的契机
这几个月,公司因为有个大型项目,参与开发人数超过 12 人,最后大家决定采用 Git 作为本次项目的版本控制机制,与其说我们采用了 Git 版本控制,其实真正采用的原因是「我们选择使用 GitHub 作为我们的版控平台」,原因就是 GitHub 平台实在整合得太好,完整的 Git 版控支持、问题追踪与管理、线上 Wiki 文件管理、友善的源代码审核(Code Review)介面。这些特性,都能有效协助我们在多人协同开发的过程中,減少团队沟通的问题。
刚开始接触 Git 说实在挺辛苦的,因为 Git 版本控制的观念,实在与 Subversion 差太多,没有办法很直观的去体会其差异,就算给了你 GUI 图形化工具介面,你也不见得就会使用。你知道的,一个强大又好用的工具在你手上,「错误的使用方式」比「不会用」还可怕!说穿了,就是你必须先建立一套思维模式(Mindset),了解 Git 的运行原理,然后再上手使用 Git 相关工具 (无论是指令列工具或图形化介面工具),才是正途!
学习的方法
我在刚学习 Git 的时候,看了好几本书 (其实是挑重点看),也看了许多线上的文章与简报,什至还看了好几部教学影片,看着看着,确实可以学会如何使用 Git 工具,我觉得并不会太过艰辛。不过,Git 的指令与参数非常多,完全超出大脑能记忆的范围,除非每天使用,否则哪有可能一天到晚打指令进行版控,如果每次要使用 Git 指令都要查书的话,那这也太没效率了点,当下的我就直觉地认为,学习 Git 最终还是要回归到好用的 GUI 工具,否则这东西在团队中可能不容易推广。
再者,因为 Git 是属于「分布式版本控制」机制,当开发人数开始变多,版本库又开始变成一人一份时,在第一次进行多人分支与合并的过程时,大家都饱受煎熬,而且持续一段不短的时间。虽然公司内部有先进行技术分享,不过由于大家都是第一次学,那些 Git 的抽象概念,还没办法深入人心,只能基于 Git 的使用方法进行分享,例如工具怎么用、有哪些常用的指令、什么特殊的情況下应该使用什么指令,诸如此类的。过程中就算说出复杂的原理,由于大家对于 Git 的认知还很模糊,不同人对 Git 版控方式的理解也不尽相同,所吸收到的知识与概念,也不一定一致。所以,虽然上完课了,大家还是需要好几天的时间不断磨合,相互讨论,互相解决问题,如果你只有一人使用 Git 的话,确实不容易感受 Git 带来的好处,也恐怕不容易坚持下去。
所以,我认为,要学好 Git 版本控制,若先知道以下几点,也许比较容易学会:
先拥有 Git 基础该念,通过指令的方式学习是最快的方式,不要跳过这一段
找多一点人跟你一起学 Git 版本控制,最好能直接用在实际的开发工作上
团队中最好要有几个先遣部队,可以多学一点 Git 观念,好分享给其他人,或有人有问题时,能随时提供协助
了解 Git 属于「分布式版本控制」,每个人都有一份完整的仓库(Repository),所以必须经常合并文件
使用 Git 的时候,分支与合并是常态,但只要有合并,就会有冲突,要学会如何解决冲突
认识 Git 版本控制
Git 的出现,来自于 Linux 之父 "Linus Torvalds" 开发 Linux kernel 的时候,因为早期的版本控制方法非常没有效率,属集中式控制,当 Linux kernel 这类复杂又庞大的项目在进行版本控制时,出现了许多问题。最早期 Linux kernel 采用 BitKeeper 进行版本控制,但后来 Linus Torvalds 基于 BitKeeper 与 Monotone 的使用经验,设计出更棒的 Git 版控系统。原先 Git 只被设计成一个低阶的版控工具,用来当做其他版控系统(SCM)的操作工具,后来才渐渐演变成一套完整的版本控制系统。
有趣的是,Linus Torvalds 改采 Git 进行版本控制初期,由于 Git 太过复杂,许多版控观念跟以往差异太大,也受到世界各地开放源代码社群的反对,但经过几年的努力与发展,操作 Git 的相关工具也越来越成熟,才渐渐平抚反对的压力,从 2013 年的市场调查看来,全世界已有 30% 的开放源代码项目改采 Git 进行版本控制,这是个非常惊人的市占率,意味着 Git 绝对有其惊艳之处,不好好研究一番还不行呢!
讲到 Git 的架构,完全是基于 Linus Torvalds 在维护 Linux kernel 这个大型项目时得到的经验,以及他本身在文件系统优化方面的丰富经验进行设计,也因为这样,Git 包含了以下几个重要的设计:
强力支持非线性开发模式 (分散式开发模式)
Git 拥有快速的分支与合并机制,还包括图形化的工具显示版本变更的历史路径。
Git 非常强调分支与合并,所以版本控制的过程中,你会不断的在执行分支与合并动作。
Git 的分支机制非常轻量,没有负担,每一次的分支只是某个 commit 的参考指标而已。
分散式开发模型
参与 Git 开发的每个人,都将拥有完整的开发历史记录。
当开发人员第一次将 Git 版本库复制(clone)下来后,完全等同于这份 Git 版本库的「完整备份」。
整个版本库中所有变更过的文件与历史记录,通通都会储存在本机仓库(local repository)。
相容于现有操作系统
Git 版本库其实就只是一个资料夹而已,资料夹中有许多相关的设定档与各种 blob 物件文件而已。
Git 版本库可以用任何方式发布,所以你用 HTTP, FTP, rsync, SSH 甚至于用 Git protocol 都可以当成存取 Git 版本库的媒介,相容性极高。
有效率的处理大型项目
由于完整的版本库会复制(clone)一份在本机,该版本库包含完整的文件与版本变更记录,所以针对版本控制中的各种文件操作速度,将会比直接从远端存取来的快上百倍之多。
这也代表着,Git 版本控制不会因为项目越来越大、文件越来越多,而导致速度变慢。
历史记录保护
Git 版控的过程,每次 commit 都会产生一组 hash id 编号,而且每个版本在变化的过程都会参考到这个 hash id,只要 hash id 无法比对的上,Git 就会无法运作,所以当项目越来越大,版本库复制(clone)的越来越多份,你几乎无法窜改文件的内容或版本记录。
请记得: 每个人都有一份完整的版本库,你改了原始的那份,所有人的版本库就无法再合并回原本的版本库了,所以你几乎不可能任意窜改版本记录。
以工具集为主的设计 (Toolkit-based design)
Git 被设计成一个一个的工具软件(指令列工具),你可以很轻易的组合不同工具的使用,使用上非常弹性。
弹性的合并策略 (Pluggable merge strategies)
Git 有一个拥有良好设计的「不完整合并(incomplete merge)」 机制,以及多种可以完成合并的演算法,并在最后告知使用者为何无法自动完成合并,或通知你需要手动进行合并动作。
被动的垃圾回收机制
在使用 Git 的时候,若想要中断目前的操作或回复上一个操作,都是可以的,你完全可以不必担心可能有其中一个指令下错,或指令执行到一半当机等问题。
Git 的垃圾回收机制,其实就是那些残留在文件系统中的无用文件,这个垃圾回收机制只会在这些无用的物件累积一段时间后自动执行,或你也可以自行下达指令清空它。例如: git gc --prune
定期的封装物件
我们在 Git 中提到的 "物件" 其实就是代表版本库中的一个文件。而在版本变动的过程中,项目中的代码或其他文件会被更新,每次更新时,只要文件内容不一样,就会建立一个新的 "物件",这些不同内容的文件全部都会保留下来。
你应该可以想像,当一个项目越来越大、版本越来越多时,这个物件会越来越多,虽然每个文件都可以各自压缩让文件变小,不过过多的文件还是会文件存取变得越来越没效率。因此 Git 的设计有个机制可以将一群老旧的 "物件" 自动封装进一个封装档(packfile)中,以改善文件存取效率。
那些新增的文件还是会以单一文件的方式存在着,也代表一个 Git 版本库中的 "文件" 就是一个 Git "物件",但每隔一段时间就会需要重新封装(repacking)。
照理说 Git 会自动执行重新封装等动作,但你依然可以自行下达指令执行。例如: git gc
如果你要检查 Git 维护的文件系统是否完整,可以执行以下指令: git fsck
关于 Git 的分散式版控系统,我再重申几件事:
Git 完全不需要服务器端的支持就可以运作版本控制,因为每个人都有一份完整的仓库副本。
因为每个人都有一份完整的仓库副本,所以每次提交版本变更时,都仅提交到本地的仓库而已,因此提交速度非常快,也不用网络连线,可大幅节省开发时间。
由于每个人都有一份完整的仓库副本,代表着在使用 Git 版本控制时,没有所谓的「权限控制」这件事,每个成员都能把仓库复制(clone)回来,也都可以在本地提交变更,没有任何权限可以限制。使用 Git 时,唯一能设定的权限是,你有没有权利存取上层仓库(upstream repository)或远端仓库(remote repository)的权限。
如果需要跟別人交换变更后的版本,随时可以通过「合并」的方式进行,Git 拥有非常强悍的合并追踪(merge tracing)能力。
要合并多人的版本,你只要有存取共用仓库(shared repository)的权限或管道即可。 例如:在同一台服务器上可以通过资料夹权限进行共用,或通过 SSH 远端存取另一台服务器的 Git 仓库,也可以通过 Web 服务器等方式来共用 Git 仓库。
今日小结
今天这篇只是个大致介绍,若看不太懂 Git 的设计理念没关系,你可以用一段时间之后再回来看这篇文章,或许会有更深一层的体会。
我觉得要写「认识 Git 版本控制」比教大家怎么用还难许多,以下我在列出一些 Git 相关连结,供大家进一步学习。
参考连结
Last updated