SVN VS GIT,版本管理工作模式、流程与工具选择

目录

背景

我们的产品线在十几个省独立部署,十几个省的个性化需求层出不穷,各省的bug也需要紧急修复,在svn集中版本控制模式下,主线版本、十几个分支版本如何有效管理,如何并行开发十几个省的个性化需求、同时并行修复十几个省的bug、主线版本、十多个分支版本发布周期及其节奏如何控制成为一个棘手问题:

  • 一直沿用branchs、tags、trunk分支模式,未关注有效的版本控制方法论和版本控制流程,往往是多人同时在同一个远程分支中工作,在一个迭代周期内,版本的发布由耗时最长的任务决定,同一迭代周期内已修复的bug或开发完成的feature无法及时发布,开发人员互相等待,效率降低,用户满意度下降;
  • 创建分支的动作是在远端执行,无法直接创建本地分支,分支的创建实际上是对远程发送创建指令,然后中央仓库完整拷贝(可能是全部)文件;远程创建完毕后才能从远端把分支检出到本地,在远端创建分支意味着创建分支是一件影响广泛、需要谨慎对待的事;
  • svn采用基于目录的分支模式,分支的重要标识是该分支的RUL,虽然也支持在在同一workspace下切换分支,但切换分支必须通过网络访问到中央版本库,分支管理严重依赖网络的稳定性。
  • 团队人员组织方式未能匹配当前产品发展要求,各小组的分工、组织、管理以及协同工作效率低下。

工具本身不是银弹,无论是svn还是git,都有其特点和优势,我们要解决的问题并不是工具的取舍问题,而是需要探寻我们的工作流模型如何更具有弹性、更高效、更便利,在建立工作模型的基础上,再根据团队特点、组织特点、工作特点,探索合理的workflow ,寻求和团队相适应的协作模式、版本管理规范以及团队组织配置方式,并选择相对更优的工具。

目标

  • 在多分支场景下理清主线与分支的关系,有效管理分支颗粒大小,有效提高团队协作效率
  • 研究一套高效的版本管理与协作流程规范,实现多版本并行开发、实现版本快速发布甚至随时发布
  • 梳理团队易于理解、便于执行的代码版本管理规范,并进行有效传达执行
  • 便捷高效的以代码review + 自动化单元测试为基础的代码质量管理规范
  • 构建自动单元测试、自动构建的基础支持环境
  • 在团队学习成本可控的前提下,选择合适的版本管理工具

主流协作模式分析

为了解决上述问题,下面对主流并行协作工作模式、常见版本控制工具进行介绍,结合当前团队现状,选择合适的团队协作模式和版本控制工具:

经典集中式svn协作模式

trunk    
|  
branches  
|  
tags  

这是svn的标准结构布局,trunk为主开发目录,branches为分支开发目录,tags为tag存档目录,tags分支通常不允许修改。但是具体这几个目录应该如何使用,svn并没有明确的规范,更多的还是用户自己的习惯。常见的方式为:

trunk

Truck(主干)是用来做主方向开发的,初始的开发应放在主线中,当模块开发完成后,需要修改,就要用到branch。

branches

branch(分支)开发和Truck(主干)开发是可以同时进行的,也就是并行开发,分支通常用于修复bug时使用,也可以用于为了引入新技术所做的验证性开发。

tags

tags用来备份代码,通常是只读的,不被用来开发,只是用来标记代码的状态、建立代码基线。

SVN版本控制流程

主干(trunk)、分支(branch)、标签(tag)的创建由配置管理员统一负责,开发人员在工作电脑中可以存在两个工作空间(workspace),用于根据不同开发需要切换主干和分支。(也可以在同一个workspace中直接切换分支,需要注意切换过程不要填错版本url)
  分支作用的选择:
集中式:trunk进行主要开发
  所有的开发都基于trunk进行开发,当一个版本/release开发告一段落(开发、测试、文档、制作安装程序、打包等)结束后,代码处于冻结状态(人为规定,可以通过hook来进行管理)。此时应该基于当前冻结的代码库,打tag。当下一个版本/阶段的开发任务开始,继续在trunk进行开发。
分散式:分支进行主要开发
  这种开发模式当中,trunk是不承担具体开发任务的,主要承担版本发布,一个版本/阶段的开发任务在开始的时候,根据已经release的版本做新的开发分支,并且基于这个分支进行开发。开发完成后merge回trunk分支。这种模式相对集中式开发而言具有更大的灵活性,最大的问题是代码合并困难。
在分支上开发的代码,可以在日常开发过程中自测通过后同步合并到主干,也可在分支通过测试并发布后统一合并到主干。建议采用日常开发过程中同步合并到主干,以降低最终合并的复杂度。

我们注意到,svn的工作流没有明确定义,开启分支是在远程仓库上开启,分支的开启与合并是一项高成本活动,在实际团队工作中往往造成分支包含的工作颗粒度过大。

这个协作模式在项目型软件中,当只有极少量线上部署版本,需求变化、bug梳理不多时,在小规模团队中非常便捷。

[图片上传失败...(image-95a78e-1564123012241)]

富有影响力的分布式gitflow协作模式

Git Flow有主分支和辅助分支两类分支。其中主分支用于组织与软件开发、部署相关的活动;辅助分支组织为了解决特定的问题而进行的各种开发活动。辅助分支完成其使命后,可以 / 应该从远程仓库中删除,以保持远程版本库的整洁。

[图片上传失败...(image-f05a3-1564123012241)]
(图片来源:https://shockerli.net/post/git-flow-guide/

主分支

主分支是所有开发活动的核心分支。所有的开发活动产生的输出物最终都会反映到主分支的代码中。主分支包含master分支和develop分支。

master分支

  • master分支存放的是随时可供在生产环境中部署的稳定版本代码
  • master分支保存官方发布版本历史,通过在master分支上打上tag来标识不同的发布版本
  • 一个项目只能有一个master分支
  • 仅在发布新的可供部署的代码时才更新master分支上的代码
  • 每次更新master,都需对master添加指定格式的tag,用于发布或回滚
  • master分支是保护分支,不可直接push到远程仓master分支
  • master分支代码只能被release分支或hotfix分支合并

develop分支

  • develop分支是保存当前最新开发成果的分支
  • 一个项目只能有一个develop分支
  • develop分支衍生出各个feature分支
  • develop分支是保护分支,不可直接push到远程仓库develop分支
  • develop分支不能与master分支直接交互

辅助分支

辅助分支是用于组织解决特定问题的各种软件开发活动的分支。辅助分支主要用于组织软件新功能的并行开发、简化新功能开发代码的跟踪、辅助完成版本发布工作以及对生产代码的缺陷进行紧急修复工作。这些分支与主分支不同,通常只会在有限的时间范围内存在。

辅助分支包括:

  • 用于开发新功能时所使用的feature分支
  • 用于辅助版本发布的release分支
  • 用于修正生产代码中的缺陷的hotfix分支
  • 用于辅助老版本运维、发布老版本补丁的support分支

feature 分支使用规范

  • 命名规则:feature/*
  • develop分支的功能分支
  • feature分支使用develop分支作为它们的父类分支
  • 以功能为单位从develop拉一个feature分支
  • 每个feature分支颗粒要尽量小,以利于快速迭代和避免冲突
  • 当其中一个feature分支完成后,它会合并回develop分支
  • 当一个功能因为各种原因不开发了或者放弃了,这个分支直接废弃,不影响develop分支
  • feature分支代码可以保存在开发者自己的代码库中而不强制提交到主代码库里
  • feature分支只与develop分支交互,不能与master分支直接交互

如有几个同事同时开发,需要分割成几个小功能,每个人都需要从develop中拉出一个feature分支,但是每个feature颗粒要尽量小,因为它需要我们能尽早merge回develop分支,否则冲突解决起来就没完没了。同时,当一个功能因为各种原因不开发了或者放弃了,这个分支直接废弃,不影响develop分支。

release 分支使用规范

  • 命名规则:release/,“”以本次发布的版本号为标识
  • release分支主要用来为发布新版的测试、修复做准备
  • 当需要为发布新版做准备时,从develop衍生出一个release分支
  • release分支可以从develop分支上指定commit派生出
  • release分支测试通过后,合并到master分支并且给master标记一个版本号
  • release分支一旦建立就将独立,不可再从其他分支pull代码
  • 必须合并回develop分支和master分支

release分支是为发布新的产品版本而设计的。在这个分支上的代码允许做小的缺陷修正、准备发布版本所需的各项说明信息(版本号、发布时间、编译时间等)。通过在release分支上进行这些工作可以让develop分支空闲出来以接受新的feature分支上的代码提交,进入新的软件开发迭代周期。
  当develop分支上的代码已经包含了所有即将发布的版本中所计划包含的软件功能,并且已通过所有测试时,我们就可以考虑准备创建release分支了。而所有在当前即将发布的版本之外的业务需求一定要确保不能混到release分支之内(避免由此引入一些不可控的系统缺陷)。

成功的派生了release分支,并被赋予版本号之后,develop分支就可以为“下一个版本”服务了。所谓的“下一个版本”是在当前即将发布的版本之后发布的版本。版本号的命名可以依据项目定义的版本号命名规则进行。

hotfix 分支使用规范

  • 命名规则:hotfix/*
  • hotfix分支用来快速给已发布产品修复bug或微调功能
  • 只能从master分支指定tag版本衍生出来
  • 一旦完成修复bug,必须合并回master分支和develop分支
  • master被合并后,应该被标记一个新的版本号
  • hotfix分支一旦建立就将独立,不可再从其他分支pull代码

除了是计划外创建的以外,hotfix分支与release分支十分相似:都可以产生一个新的可供在生产环境部署的软件版本。
  当生产环境中的软件遇到了异常情况或者发现了严重到必须立即修复的软件缺陷的时候,就需要从master分支上指定的TAG版本派生hotfix分支来组织代码的紧急修复工作。
  这样做的显而易见的好处是不会打断正在进行的develop分支的开发工作,能够让团队中负责新功能开发的人与负责代码紧急修复的人并行的开展工作。

support 分支使用规范

  • 命名规则:support/*

Say you had a project, and you were happily releasing new versions.
Maybe your current production version was 8.x. But you had some Really
Important Customers who refused to upgrade to anything after 6.0. Now,
if someone found a security flaw in the 6.0 version of your project,
it would be a bad idea to hang all those Really Important Customers
out to dry. So you release a new hotfix against 6.0, even though all
their problems would be solved if they just upgraded to the new
release (It has been nearly 10 years since you released 6.0 after
all).
In order to keep supporting these Really Lazy Customers, you release
hotfix 6.0.1 to fix the security bug. In "normal" git, it would look
something like:

> git checkout 6.0
> git checkout -b support/6.x
> git checkout -b hotfix/6.0.1

make your fix, then:

> git checkout support/6.x
> git merge hotfix/6.0.1
> git branch -d hotfix/6.0.1
> git tag 6.0.1

if needed, this change can also be applied to a hotfix for 8.x,
via cherry-pick.
You would keep the support/6.x version around as long as your Really
Important Customers still needed security fixes for 6.0, and would
make 6.x.x releases off this branch instead of production.

其他几种常见的协作模型

Forking工作流

Forking工作流是分布式工作流,充分利用了Git在分支和克隆上的优势。可以安全可靠地管理大团队的开发者(developer),并能接受不信任贡献者(contributor)的提交。
forking工作流最大的特点是每个开发者在两个远程仓库中工作,Forking工作流要先有一个公开的正式仓库存储在服务器上。 但一个新的开发者想要在项目上工作时,不是直接从正式仓库克隆,而是fork正式项目在服务器上创建一个拷贝。
  这个fork来的仓库拷贝作为他个人公开仓库 —— 其它开发者不允许push到这个仓库,但可以pull到修改。 在创建了自己服务端拷贝之后,和gitflow工作流一样,开发者执行git clone命令克隆仓库到本地机器上,作为私有的开发环境。
  要提交本地修改时,push提交到自己公开仓库中 —— 而不是正式仓库中。 然后,给正式仓库发起一个pull request,让项目维护者知道有更新已经准备好可以集成了。 对于贡献的代码,pull request也可以很方便地作为一个讨论的地方。

Pull Requests

Pull Requests通常在github、gitlab等基于git的工作平台上使用。
  当要发起一个Pull Request,你所要做的就是请求(Request)另一个开发者(比如项目的维护者) 来pull你仓库中一个分支到他的仓库中。这意味着你要提供4个信息以发起Pull Request: 源仓库、源分支、目的仓库、目的分支。
  在不同的工作流中使用Pull Request会有一些不同,但基本的过程是这样的:

  • 开发者在本地仓库中新建一个专门的分支开发功能。
  • 开发者push分支修改到公开的Bitbucket仓库中。
  • 开发者通过Bitbucket/gitlab/github发起一个Pull Request。
  • 团队的其它成员review code,讨论并修改。
  • 项目维护者合并功能到官方仓库中并关闭Pull Request。

前面我们讨论了目前主流的几种协作模型,不涉及具体工具的选择,比如可以选择git工具而使用经典集中式svn协作模式,或者选择svn工具而应用gitflow协作模式。下面我们将进入工具层面的讨论。


主流版本管理工具:SVN和Git

SVN:集中式版本控制系统

SVN简介

SVN(Subversion)是集中式管理的版本控制器,SVN只有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新,这意味着协同工作时必须与中央仓库联网。

SVN的主要特点

  • svn中的版本号revision是全局版本号,svn commit 操作被当作一次原子事务操作。每当版本库接受了一个提交,文件系统进入了一个新的状态,叫做revision,每个revision被赋予一个全局独一无二的递增的自然数。
  • 每个版本库及其版本都有唯一的URL(中心库地址),每个用户都从这个地址获取代码和数据;
  • 获取该版本代码的更新,也只能连接到这个唯一的版本库,同步以取得最新数据;
  • 提交必须有网络连接;
  • 较为精细的权限控制能力,提交需要授权,如果没有写权限,提交会失败;
  • SVN原理上关心文件内容的具体差异。每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容。

SVN常用工具

服务端

  • VisualSVN:用于搭建svn服务器的工具,主要功能为用户版本控制和版本库存储;

客户端

  • TortoiseSVN : 封装了svn客户端核心并进行优化后的svn客户端工具

Git:分布式版本控制系统

Git简介

Git是分布式管理的版本控制器,这是git和svn两者之间最核心的区别。
  Git每一个终端都是一个仓库,包括中央仓库在内,每个仓库在原理上都是平等的,客户端并不只提取最新版本的文件快照,而是把原始的代码仓库完整地镜像下来。每一次的提取操作,实际上都是一次对代码仓库的完整备份。
  git每次提交,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。这项特性作为 Git 的设计哲学,建在整体架构的最底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。
  Git记录版本历史只关心文件数据的整体是否发生变化。Git 不保存文件内容前后变化的差异数据。
  实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一连接。Git 的工作完全依赖于这类指纹字串,所以你会经常看到这样的哈希值。实际上,所有保存在 Git 数据库中的东西都是用此哈希值来作索引的,而不是靠文件名。

Git的主要特点

  • Git中每个版本库原则上都是平等的
  • Git的每一次提取操作,实际上都是一次对代码仓库的完整备份。
  • 强大的分支管理能力
  • 多数操作是添加操作
  • 近乎所有操作都是本地执行
  • 直接记录快照,而非差异比较
  • 提供离线版本控制能力
  • 更为强大的冲突合并能力
  • 更多可选协作模式
  • 有一定的学习成本,学习曲线比较陡峭
  • 相对较弱的权限控制能力

git常用工具

服务端

  • 直接使用git
  • github
  • gitlab

客户端

  • Source Tree
    号称是最好用的Git GUI工具,内建持Git Flow支持
  • TortoiseGit
    与TortoiseSVN一脉相承的操作体验
  • Eclipse – Egit
    Eclipse内置了egit插件来提供git的集成支持,低版本Eclipse的egit功能较弱
  • Visual Studio – Git Integration & GitHub Extension
    VS里面的Git支持已经相当的完善

小结

svn的优势在于团队比较熟悉,几乎无学习成本,基于目录的分支方式简单、容易理解,也略显粗暴,集中式的版本管理模式在团队集中办公、集中开发,且产品分支少、需求明确,开发过程分支少、所有工作均具备网络环境、基于瀑布式的开发模式时简单易行。当然也可以在svn集中版本控制模式下应用gitflow工作流。
  git的优势在于分布式、可离线版本控制,github、gitlab等类似工具和平台的出现使得git分布式版本控制形成了一定的生态,git在开源软件的协同、分发更具优势,与此带来的问题是相对较弱的权限控制。git有一定的学习成本,想要享受git的便利和功能,就需要付出深入学习的成本。
  git在提供工具的同时,还明确给出了协同工作分支的建议模型gitflow,gitflow逻辑严谨,适应性强,是更为先进的协同工作和版本控制的思想,可以有效解决团队当前面临的诸多问题。gitflow可以在svn中使用,但它终究是为git量身设计和打造的,为了更好的享受gitflow带来的便利,团队选择 git + gitflow的模式。

快速掌握Git

git的物理结构

本质上,Git是一套内容寻址(content-addressable)文件系统。在操作系统中,仓库就是一个文件夹。但是为什么这些文件夹就是Git仓库呢?这是因为Git在初始化的时候会生成一个.git的文件夹,Git进行版本控制所需要的所有文件都放在这个文件夹中。
[图片上传失败...(image-47df1c-1564123012241)]
[图片上传失败...(image-db6f46-1564123012241)]

config文件

该文件主要记录针对该项目的一些配置信息,例如是否以bare方式初始化、remote的信息等,通过git remote add命令增加的远程分支的信息就保存在这里;

objects文件夹

该文件夹主要包含git对象。Git中的文件和一些操作都会以git对象来保存,git对象分为BLOB、tree和commit三种类型,例如git commit便是git中的commit对象,而各个版本之间是通过版本树来组织的,比如当前的HEAD会指向某个commit对象,而该commit对象又会指向几个BLOB对象或者tree对象。objects文件夹中会包含很多的子文件夹,其中Git对象保存在以其sha-1值的前两位为子文件夹、后38位位文件名的文件中;除此以外,Git为了节省存储对象所占用的磁盘空间,会定期对Git对象进行压缩和打包,其中pack文件夹用于存储打包压缩的对象。

info文件夹

用于从打包的文件中查找git对象;

HEAD文件

该文件指明了git branch(即当前分支)的结果,比如当前分支是master,则该文件就会指向master,但是并不是存储一个master字符串,而是分支在refs中的表示,例如ref: refs/heads/master。

index文件

该文件保存了暂存区域的信息。该文件某种程度就是缓冲区(staging area),内容包括它指向的文件的时间戳、文件名、sha1值等;

Refs文件夹

该文件夹存储指向数据(分支)的提交对象的指针。其中heads文件夹存储本地每一个分支最近一次commit的sha-1值(也就是commit对象的sha-1值),每个分支一个文件;remotes文件夹则记录你最后一次和每一个远程仓库的通信,Git会把你最后一次推送到这个remote的每个分支的值都记录在这个文件夹中;tag文件夹则是分支的别名,这里不需要对其有过多的了解;

除此以外,.git目录下还有很多其他的文件和文件夹,这些文件和文件夹会额外支撑一些其他的功能,但是不是Git的核心部分,因此稍作了解即可。

hooks文件夹

主要定义了客户端或服务端钩子脚本,这些脚本主要用于在特定的命令和操作之前或者之后进行特定的处理,比如:当你把本地仓库push到服务器的远程仓库时,可以在服务器仓库的hooks文件夹下定义post_update脚本,在该脚本中可以通过脚本代码将最新的代码部署到服务器的web服务器上,从而将版本控制和代码发布无缝连接起来;

description文件

仅供GitWeb程序使用,不需要过多的关心;

logs

记录了本地仓库和远程仓库的每一个分支的提交记录,即所有的commit对象(包括时间、作者等信息)都会被记录在这个文件夹中,因此这个文件夹中的内容是我们查看最频繁的,不管是Git log命令还是tortoiseGit的show log,都需要从该文件夹中获取提交日志;

info文件夹

保存了一份不希望在.gitignore 文件中管理的忽略模式的全局可执行文件,基本也用不上;

COMMIT_EDITMSG文件

记录了最后一次提交时的注释信息。从以上的描述中我们可以发现,.git文件夹中包含了众多功能不一的文件夹和文件,这些文件夹和文件是描述Git仓库所必不可少的信息,不可以随意更改或删除;尤其需要注意的是,.git文件夹随着项目的演进,可能会变得越来越大,因为任何文件的任何一个变动,都需要Git在objects文件夹下将其重新存储为一个新的对象文件,因此如果一个文件非常大,那么你提交几次改动就会造成.git文件夹容量成倍增长。
  因此,.git文件夹更像是一本书,每一个版本的每一个变动都存储在这本书中,而且这本书还有一个目录,指明了不同的版本的变动内容存储在这本书的哪一页上,这就是Git的最基本的原理。

git的逻辑结构

Git是一套内容寻址(content-addressable)文件系统,Git 的核心部分是一个简单的键值对数据库(key-value data store)。 你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索(retrieve)该内容。
  Git采用HashTable的方式进行查找,也就是说,Git只是通过简单的存储键值对(key-value pair)的方式来实现内容寻址的:

  • key就是文件(头+内容)的哈希值(采用sha-1的方式,40位)
  • value就是经过压缩后的文件内容。要操作对象时,需要通过key来指定所要操作的对象。

Git对象的存储方式也很简单,基本可以用如下表达式来表示:

Key = sha1(file_header + file_content)
Value = zlib(file_content)

** git逻辑结构中是其重要的体系思想:工作树、提交树等在git中发挥非常重要的作用。

git 中的四种对象

<img src="/assets/git data model.jpg" align="center" width="60%" />

<img src="/assets/git data model2.jpg" align="center" width="60%" />

Git中的文件和一些操作都会以git对象来保存,Git对象的类型包括:

  • blob对象
  • tree对象
  • commit对象
  • tag对象

Commit组件包含了Tree,Tree组件中又有Blob组件:

  • BLOB对象可以存储几乎所有的文件类型,全称为binary large object,顾名思义,就是大的二进制表示的对象,这种对象类型和数据库中的BLOB类型(经常用来在数据库中存储图片、视频等)是一样的,当作一种数据类型即可;
  • tree对象是用来组织BLOB对象的一种数据类型,你完全可以把它想象成二叉树中的树节点,只不过Git中的树不是二叉树,而是"多叉树";
  • commit对象表示每一次的提交操作,由tree对象衍生,每一个commit对象表示一次提交,在创建的过程中可以指定该commit对象的父节点,这样所有的commit操作便可以连接在一起,而这些commit对象便组成了提交树,branch只不过是这个树中的某一个子树罢了。如果你能理解commit树,那Git几乎就已经理解了一半了。

git中文件的三种状态

<img src="/assets/git file status2.png" align="center" width="70%" />

受Git管理的三种状态

  • staged
  • modified
  • committed
    图中处于untracked状态的文件不受git管理。

Git文件的三个流转区域

  • 工作区域
  • 索引区域
  • 本地数据区域

本地仓库与远程仓库的关系

<img src="/assets/git file status5.png" align="center" width="40%" />


<img src="/assets/git file status and command.png" align="center" width="50%" />

从上述图形的描述我们可以直观看出来,git的几乎所有操作都属于本地操作,会影响远程仓库的命令只有push、remote xx;将文件从远程仓库同步到本地的有pull、(rebase?)、fetch、clone。其他git命令基本都属于本地操作。

近乎所有操作都是本地操作:

<img src="/assets/pull vs fetch.png" align="center" width="100%" />

git 的安装及初始化

  • 下载Git并安装 官方地址为:

https://git-scm.com/download/win
https://git-scm.com/download/mac
https://git-scm.com/download/linux

git的结构

关于git命令

git客户端图形化工具的所有操作都是基于git命令本身,深入掌握git命令之后任何一个git客户端图形化工具的使用都能运用自如,并且可以通过图形化界面的操作深刻理解该操作背后具体做了什么事。因此本文仅讲解git命令,你可以在日常工作中根据个人喜好使用命令行、图形化工具方式,这两种方式没有高低之分,关键在于你是否理解git背后做了什么。

git命令分为常用命令、辅助操作命令、外部互操作命令、底层命令几大类。git的所有命令可以通过列出:

git --help -a -v

可以看到git支持的全部命令,包括:

Main Porcelain Commands
   add                  Add file contents to the index
   am                   Apply a series of patches from a mailbox
   archive              Create an archive of files from a named tree
   bisect               Use binary search to find the commit that introduced a bug
   branch               List, create, or delete branches
   bundle               Move objects and refs by archive
   checkout             Switch branches or restore working tree files
   cherry-pick          Apply the changes introduced by some existing commits
   citool               Graphical alternative to git-commit
   clean                Remove untracked files from the working tree
   clone                Clone a repository into a new directory
   commit               Record changes to the repository
   describe             Give an object a human readable name based on an available ref
   diff                 Show changes between commits, commit and working tree, etc
   fetch                Download objects and refs from another repository
   format-patch         Prepare patches for e-mail submission
   gc                   Cleanup unnecessary files and optimize the local repository
   gitk                 The Git repository browser
   grep                 Print lines matching a pattern
   gui                  A portable graphical interface to Git
   init                 Create an empty Git repository or reinitialize an existing one
   log                  Show commit logs
   merge                Join two or more development histories together
   mv                   Move or rename a file, a directory, or a symlink
   notes                Add or inspect object notes
   pull                 Fetch from and integrate with another repository or a local branch
   push                 Update remote refs along with associated objects
   rebase               Reapply commits on top of another base tip
   reset                Reset current HEAD to the specified state
   revert               Revert some existing commits
   rm                   Remove files from the working tree and from the index
   shortlog             Summarize 'git log' output
   show                 Show various types of objects
   stash                Stash the changes in a dirty working directory away
   status               Show the working tree status
   submodule            Initialize, update or inspect submodules
   tag                  Create, list, delete or verify a tag object signed with GPG
   worktree             Manage multiple working trees

Ancillary Commands / Manipulators
   config               Get and set repository or global options
   fast-export          Git data exporter
   fast-import          Backend for fast Git data importers
   filter-branch        Rewrite branches
   mergetool            Run merge conflict resolution tools to resolve merge conflicts
   pack-refs            Pack heads and tags for efficient repository access
   prune                Prune all unreachable objects from the object database
   reflog               Manage reflog information
   remote               Manage set of tracked repositories
   repack               Pack unpacked objects in a repository
   replace              Create, list, delete refs to replace objects

Ancillary Commands / Interrogators
   annotate             Annotate file lines with commit information
   blame                Show what revision and author last modified each line of a file
   cherry               Find commits yet to be applied to upstream
   count-objects        Count unpacked number of objects and their disk consumption
   difftool             Show changes using common diff tools
   fsck                 Verifies the connectivity and validity of the objects in the database
   get-tar-commit-id    Extract commit ID from an archive created using git-archive
   gitweb               Git web interface (web frontend to Git repositories)
   help                 Display help information about Git
   instaweb             Instantly browse your working repository in gitweb
   merge-tree           Show three-way merge without touching index
   rerere               Reuse recorded resolution of conflicted merges
   rev-parse            Pick out and massage parameters
   show-branch          Show branches and their commits
   verify-commit        Check the GPG signature of commits
   verify-tag           Check the GPG signature of tags
   whatchanged          Show logs with difference each commit introduces

Interacting with Others
   archimport           Import an Arch repository into Git
   cvsexportcommit      Export a single commit to a CVS checkout
   cvsimport            Salvage your data out of another SCM people love to hate
   cvsserver            A CVS server emulator for Git
   imap-send            Send a collection of patches from stdin to an IMAP folder
   p4                   Import from and submit to Perforce repositories
   quiltimport          Applies a quilt patchset onto the current branch
   request-pull         Generates a summary of pending changes
   send-email           Send a collection of patches as emails
   svn                  Bidirectional operation between a Subversion repository and Git

Low-level Commands / Manipulators
   apply                Apply a patch to files and/or to the index
   checkout-index       Copy files from the index to the working tree
   commit-graph         Write and verify Git commit graph files
   commit-tree          Create a new commit object
   hash-object          Compute object ID and optionally creates a blob from a file
   index-pack           Build pack index file for an existing packed archive
   merge-file           Run a three-way file merge
   merge-index          Run a merge for files needing merging
   mktag                Creates a tag object
   mktree               Build a tree-object from ls-tree formatted text
   pack-objects         Create a packed archive of objects
   prune-packed         Remove extra objects that are already in pack files
   read-tree            Reads tree information into the index
   symbolic-ref         Read, modify and delete symbolic refs
   unpack-objects       Unpack objects from a packed archive
   update-index         Register file contents in the working tree to the index
   update-ref           Update the object name stored in a ref safely
   write-tree           Create a tree object from the current index

Low-level Commands / Interrogators
   cat-file             Provide content or type and size information for repository objects
   diff-files           Compares files in the working tree and the index
   diff-index           Compare a tree to the working tree or index
   diff-tree            Compares the content and mode of blobs found via two tree objects
   for-each-ref         Output information on each ref
   ls-files             Show information about files in the index and the working tree
   ls-remote            List references in a remote repository
   ls-tree              List the contents of a tree object
   merge-base           Find as good common ancestors as possible for a merge
   name-rev             Find symbolic names for given revs
   pack-redundant       Find redundant pack files
   rev-list             Lists commit objects in reverse chronological order
   show-index           Show packed archive index
   show-ref             List references in a local repository
   unpack-file          Creates a temporary file with a blob's contents
   var                  Show a Git logical variable
   verify-pack          Validate packed Git archive files

Low-level Commands / Synching Repositories
   daemon               A really simple server for Git repositories
   fetch-pack           Receive missing objects from another repository
   http-backend         Server side implementation of Git over HTTP
   send-pack            Push objects over Git protocol to another repository
   update-server-info   Update auxiliary info file to help dumb servers

Low-level Commands / Internal Helpers
   check-attr           Display gitattributes information
   check-ignore         Debug gitignore / exclude files
   check-mailmap        Show canonical names and email addresses of contacts
   check-ref-format     Ensures that a reference name is well formed
   column               Display data in columns
   credential           Retrieve and store user credentials
   credential-cache     Helper to temporarily store passwords in memory
   credential-store     Helper to store credentials on disk
   fmt-merge-msg        Produce a merge commit message
   interpret-trailers   add or parse structured information in commit messages
   mailinfo             Extracts patch and authorship from a single e-mail message
   mailsplit            Simple UNIX mbox splitter program
   merge-one-file       The standard helper program to use with git-merge-index
   patch-id             Compute unique ID for a patch
   sh-i18n              Git's i18n setup code for shell scripts
   sh-setup             Common Git shell script setup code
   stripspace           Remove unnecessary whitespace
(END)

如果你想查看具体某个命令(以remote命令为例)的用法说明,可以使用如下命令:

git remote --help

快速掌握git命令

git相关概念、命令、注意事项等,请仔细阅读、练习以下思维导图所讲解知识点:

<img src="/assets/cyberwinds-git-turtorial.png" align="center" width="100%" />

git版本控制的相关角色

  • git管理员: 拥有主库的所有权限、负责账号、权限的管理和维护。
  • 开发经理: 具有将开发人员的合并需求(MR)合入主库的权限。基于安全考虑,我们设置为只能通过MR的方式将代码合入主库,而不能直接push到主库。负责管理 重要远程分支 的开启、切换、合并、废弃、标记(tag)等
  • 开发工程师: 只能从自己的个人代码库(服务端)提交合并代码的请求(merge request/pull request),是否能够合入,由开发经理负责、各开发工程师共同进行审核。
  • 测试工程师: 对所有分支拥有只读权限

中小团队git日常工作最佳实践

团队日常工作最佳实践的目的至少包含以下四点:

  • 本地工作流程在源头上尽量减少冲突的发生,或者及时发现冲突、及时解决冲突
  • 合理的冲突解决方案
  • 清晰的项目提交树、清晰的提交说明
  • 清晰的分支、tag管理,便利的里程碑版本切换和提取
    以下是推荐的日常工作模式:
    (原文《USING GIT IN A TEAM: A CHEATSHEET 》)
    <img src="/assets/workflow-team-practice.gif" align="center" width="100%" />

About half the time I use Git on projects only I will ever see, and the rest of the time I work collaboratively with a handful of people in my team. The workflow outlined below caters very well to this kind of use, and we've had success with it while we've been building Boords.

It's aimed at people who want to use Git in a simply, efficiently and with a minimum of fuss. I've been heavily influenced by the concepts covered in the Git course on Upcase. If you're looking to improve your skills as a developer I can heartily recommend signing up for a subscription with them.

As I say, the ideas here aren't anything new. It basically boils down to check out a new branch, work on it, then merge your changes back into the master branch in a single, curated commit. The idea is that by merging a single commit with all your changes rather than lots of smaller commits, your master branch nice stays and neat.

STEP BY STEP

To give a little more context, let's go through each of the steps above in a bit more detail so we can investigate what's going on.

STEP 1: CREATE A NEW BRANCH TO WORK ON

/*Create a new feature branch*/
git branch jc_new_feature
/*Checkout your new feature branch*/
git checkout jc_new_feature

When naming feature branches, a good best practice is to start with your initials, then the feature name (e.g. jc_feature_name). This helps others working on a project to see who's been doing what.

STEP 2: WRITE SOME CODE, COMMIT REGULARLY

/*Add all files to the stage*/
git add .
/*Commit files*/
git commit -m "Description of this commit"
/*Optional (but recommended) push local branch to remote*/
git push origin jc_feature_name

Commit your code regularly. I've never regretted making a commit, but have regretted not making one. You'll have a chance to change your commit messages before merging back into master.

STEP 3: FETCH WHEN YOU'RE DONE

When you're ready to merge your features back into the master branch, run git fetch.

git fetch

Fetching makes sure you're up to date when merging changes back into master. This doesn't actually merge the code with the code your machine (git pull does that), but instead updates references to any remote changes which may have happened while you've been working locally. It's groundwork for the next stage.

STEP 4: SQUASH YOUR COMMITS AND GET READY TO MERGE

Now you'll rebase your changes into the master branch. This effectively condenses down all the commits you've made on your feature branch (jc_feature_name) into one commit. We'll merge this one single commit back into the master branch, keeping everything nice and neat.

/*Open the interactive rebase tool*/
git rebase -i origin/master

This will open an interactive rebase tool, which shows all the commits you've made on your branch. You'll then "squash" all your changes into one commit.

To do this, replace pick with s for all but the top commit. s is shorthand for squash - imagine all the changes you've made being "squashed" up into the top commit.

<img src="/assets/rebase-1.jpg" align="center" width="100%" />
<center style="color:gray">Interactive rebase tool</center>

<img src="/assets/rebase-2.jpg" align="center" width="100%" />
<center style="color:gray">
"Squashing" three commits into one. Here the second and third
commits are squashed into the first commit.</center>

When you close this window you'll see all your existing commits, which you can edit down into a simpler, more concise commit message.

<img src="/assets/rebase-3.jpg" align="center" width="100%" />
<center style="color:gray">All previous commit messages</center>

<img src="/assets/rebase-4.jpg" align="center" width="100%" />
<center style="color:gray">Replacing existing commits with a new commit message</center>

Exit the rebase tool and you're ready to merge.
<img src="/assets/rebase-5.jpg" align="center" width="100%" />

STEP 5: MERGE YOUR CHANGES

Switch to the master branch in preparation of merging your changes. When merging always remember that you're merging into the branch you're currently on.

/*Checkout the master branch*/
git checkout master
/*Merge jc_feature_name INTO master*''
git merge jc_feature_name

The changes from the jc_feature_name branch are now merged into your master branch. Happy days. Now's a good time to push your changes to the remote master branch.

/*Push your local master branch to remote*/
git push origin master

STEP 6:CLEANUP

With your changes merged into the master branch, you can safely delete your feature branches.

/*Delete remote feature branch (the colon is important!)*/
git push origin :jc_feature_name
/*Delete local branch*/
git branch -d jc_feature_name

And with that, you're done.

MAKING IT WORK

It's very easy to feel insecure about using Git, and the spectre of losing work looms large. The key to making this system work is to go through the process a lot, regularly branching and merging. This gets you comfortable with the workflow and has the added bonus of making sure one branch never gets too far out of line with another.

It's not a perfect system. It doesn't take into account pull requests for one. Fixing merge conflicts can be fiddly (although this is the case regardless of your workflow). But overall it gets the job done and I can say without hesitation that it's made my work, and by extension my very existence, that little bit easier.

团队Git版本管理规范

  • 本地修改代码前一定要先pull当前分支的远程分支
  • 提交分支前也需要pull远程分支、合并本地分支后提交
  • 除非你非常清楚你在做什么,否则禁用rebase
  • 未经开发经理同意,禁止采用强制提交方式提交代码

FAQ

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348