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

2019-07-26  本文已影响0人  908c5d752fc4

目录

背景

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

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

目标

主流协作模式分析

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

经典集中式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分支

develop分支

辅助分支

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

辅助分支包括:

feature 分支使用规范

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

release 分支使用规范

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

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

hotfix 分支使用规范

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

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会有一些不同,但基本的过程是这样的:

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


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

SVN:集中式版本控制系统

SVN简介

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

SVN的主要特点

SVN常用工具

服务端

客户端

Git:分布式版本控制系统

Git简介

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

Git的主要特点

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对象的类型包括:

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

git中文件的三种状态

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

受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 的安装及初始化

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日常工作最佳实践

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

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版本管理规范

FAQ

上一篇下一篇

猜你喜欢

热点阅读