SVN VS GIT,版本管理工作模式、流程与工具选择
目录
- 背景
- 目标
- 主流协作模式分析
- 主流版本管理工具:SVN和Git
- 小结
- 快速掌握Git
- 团队Git版本管理规范
- FAQ
- 参考
背景
我们的产品线在十几个省独立部署,十几个省的个性化需求层出不穷,各省的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
- 未经开发经理同意,禁止采用强制提交方式提交代码