BY 童仲毅(geeeeeeeeek@github)

这是一篇在原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名(CC BY 2.5 AU)协议共享。

多种多样的工作流使得在项目中实施Git时变得难以选择。这份教程提供了一个出发点,调查企业团队最常见的Git工作流。

阅读的时候,请记住工作流应该是一种规范而不是金科玉律。我们希望向你展示所有工作流,让你融会贯通,因地制宜。

这份教程讨论了下面四种工作流:

中心化的工作流

过渡到分布式分版本控制系统看起来是个令人恐惧的任务,但你不必为了利用Git的优点而改变你现有的工作流。你的团队仍然可以用以前SVN的方式开发项目。

然而,使用Git来驱动你的开发工作流显示出了一些SVN没有的优点。首先,它让每个开发者都有了自己 本地 的完整项目副本。隔离的环境使得每个开发者的工作独立于项目的其它修改——他们可以在自己的本地仓库中添加提交,完全无视上游的开发,直到需要的时候。

第二,它让你接触到了Git鲁棒的分支和合并模型。和SVN不同,Git分支被设计为一种故障安全的机制,用来在仓库之间整合代码和共享更改。

如何工作

和Subversion一样,中心化的工作流将中央仓库作为项目中所有修改的唯一入口。和trunk不同,默认的开发分支叫做master,所有更改都被提交到这个分支。这种工作流不需要master之外的其它分支。

开发者将中央仓库克隆到本地后开始工作。在他们的本地项目副本中,他们可以像SVN一样修改文件和提交更改;不过,这些新的提交被保存在 本地 ——它们和中央仓库完全隔离。这使得开发者可以将和上游的同步推迟到他们方便的时候。

为了向官方项目发布修改,开发者将他们的本地master分支“推送”到中央仓库。这一步等同于svn commit,除了Git添加的是所有不在中央master分支上的本地提交。

管理冲突

中央仓库代表官方项目,因此它的提交历史应该被视作神圣不可更改的。如果开发者的本地提交和中央仓库分叉了,Git会拒绝将他们的修改推送上去,因为这会覆盖官方提交。

在开发者发布他们的功能之前,他们需要fetch更新的中央提交,在它们之上rebase自己的更改。这就像是:“我想要在其他人的工作进展之上添加我的修改。”它会产生完美的线性历史,就像和传统的SVN工作流一样。

如果本地修改和上游提交冲突时,Git会暂停rebase流程,给你机会手动解决这些冲突。Git很赞的一点是,它将git statusgit add命令同时用来生成提交和解决合并冲突。这使得开发者能够轻而易举地管理他们的合并。另外,如果他们改错了什么,Git让他们轻易地退出rebase过程,然后重试(或者找人帮忙)。

栗子

让我们一步步观察一个普通的小团队是如何使用这种工作流协作的。我们有两位开发者,John和Mary,分别在开发两个功能,他们通过中心化的仓库共享代码。

一人初始化了中央仓库

首先,需要有人在服务器上创建中央仓库。如果这是一个新项目,你可以初始化一个空的仓库。不然,你需要导入一个已经存在的Git或SVN项目。

中央仓库应该永远是裸仓库(没有工作目录),可以这样创建:

ssh user@host git init --bare /path/to/repo.git

但确保你使用的SSH用户名user、服务器host的域名或IP地址、储存仓库的地址/path/to/repo.git是有效的。注意.git约定俗成地出现在仓库名的后面,表明这是一个裸仓库。

所有人将仓库克隆到本地

接下来,每个开发者在本地创建一份完整项目的副本。使用git clone命令:

git clone ssh://user@host/path/to/repo.git

当你克隆仓库时,Git自动添加了一个名为origin的远程连接,指向“父”仓库,以便你以后和这个仓库交换数据。

John在开发他的功能

在他的本地仓库中,John可以用标准的Git提交流程开发功能:编辑、缓存、提交。如果你对缓存区还不熟悉,你也可以不用记录工作目录中每次的变化。于是你创建了一个高度集中的提交,即使你已经在本地做了很多修改。

git status # 查看仓库状态
git add <some-file> # 缓存一个文件
git commit # 提交一个文件</some-file>

记住,这些命令创建的是本地提交,John可以周而复始地重复这个过程,而不用考虑中央仓库。对于庞大的功能,需要切成更简单、原子化的片段时,这个特性就很有用。

Mary在开发她的功能

同时,Mary在她自己的本地仓库用相同的编辑/缓存/提交流程开发她的功能。和John一样,她不需要关心中央仓库的进展,她也 完全 不关心John在他自己仓库中做的事,因为所有本地仓库都是私有的。

John发布了他的功能

一旦John完成了他的功能,他应该将本地提交发布到中央仓库,这样其他项目成员就可以访问了。他可以使用git push命令,就像:

git push origin master

记住,origin是John克隆中央仓库时指向它的远程连接。master参数告诉Git试着将originmaster分支变得和他本地的master分支一样。中央仓库在John克隆之后还没有进展,因此这个推送如他所愿,没有产生冲突。

Mary试图发布她的功能

John已经成功地将他的更改发布到了中央仓库上,看看当Mary试着将她的功能推送到上面时会发生什么。她可以使用同一个推送命令:

git push origin master

但是,她的本地历史和中央仓库已经分叉了,Git会拒绝这个请求,并显示一段冗长的错误信息:

error: failed to push some refs to '/path/to/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Git防止Mary覆盖官方的修改。她需要将John的更新拉取到她的仓库,和她的本地修改整合后,然后重试。

Mary在John的提交之上rebase

Mary可以使用git pull来将上游修改并入她的仓库。这个命令和svn update很像——它拉取整个上游提交历史到Mary的本地仓库,并和她的本地提交一起整合:

git pull --rebase origin master

--rebase选项告诉Git,在同步了中央仓库的修改之后,将Mary所有的提交移到master分支的顶端,如下图所示:

如果你忽略这个选项拉取同样会成功,只不过你每次和中央仓库同步时都会多出一个“合并提交”。在这种工作流中,rebase和生成一个合并提交相比,总是一个更好的选择。

Mary解决了合并冲突

Rebase的工作是将每个本地提交一个个转移到更新后的master分支。也就是说,你可以一个个提交分别解决合并冲突,而不是在一个庞大的合并提交中解决。它会让你的每个提交保持专注,并获得一个干净的项目历史。另一方面,你更容易发现bug是在哪引入的,如果有必要的话,用最小的代价回滚这些修改。

如果Mary和John开发的功能没有关联,rebase的过程不太可能出现冲突。但如果出现冲突时,Git在当前提交会暂停rebase,输出下面的信息,和一些相关的指令:

CONFLICT (content): Merge conflict in <some-file>

Git的优点在于 每个人 都能解决他们自己的合并冲突。在这个例子中,Mary只需运行一下git status就可以发现问题是什么。冲突的文件会出现在未合并路径中:

# Unmerged paths:
# (use "git reset HEAD <some-file>..." to unstage)
# (use "git add/rm <some-file>..." as appropriate to mark resolution)
#
# both modified: <some-file>

接下来,修改这些文件。如果她对结果满意了,和往常一样缓存这些文件,然后让git rebase完成接下来的工作:

git add <some-file>
git rebase --continue

就是这样。Git会继续检查下个提交,对冲突的提交重复这个流程。

如果你这时候发现不知道自己做了什么,不要惊慌。只要运行下面的命令,你就会回到开始之前的状态:

git rebase --abort

Mary成功发布了她的分支

在她和中央仓库同步之后,Mary可以成功地发布她的修改:

git push origin master

接下来该怎么做

正如你所见,使用一丢丢Git命令来复制一套传统的Subversion开发环境也是可行的。这对于从SVN转变而来的团队来说很棒,但这样没有利用到Git分布式的本质。

如果你的团队已经习惯了中心化的工作流,但希望提高协作效率,那么探索Feature分支工作流的好处是完全值当的。每个功能在专门的独立分支上进行,在代码并入官方项目之前就可以启动围绕新修改的深度讨论。

Feature分支的工作流

一旦你掌握了中心化工作流的使用姿势,在你的开发流程中添加功能分支是一个简单的方式,来促进协作和开发者之间的交流。这种封装使得多个开发者专注自己的功能而不会打扰主代码库。它还保证master分支永远不会包含损坏的代码,给持续集成环境带来了是很大的好处。

封装功能的开发使得pull request的使用成为可能,用来启动围绕一个分支的讨论。它给了其他开发者在功能并入主项目之前参与决策的机会。或者,如果你开发功能时卡在一半,你可以发起一个pull request,向同事寻求建议。重点是,pull request使得你的团队在评论其他人的工作时变得非常简单。

如何工作

Feature分支工作流同样使用中央仓库,master同样代表官方的项目历史。但是,与其直接提交在本地的master分支,开发者每次进行新的工作时创建一个新的分支。Feature分支应该包含描述性的名称,比如animated-menu-items(菜单项动画)或issue-#1061。每个分支都应该有一个清晰、高度集中的目的。

Git在技术上无法区别master和功能分支,所以开发者可以在feature分支上编辑、缓存、提交,就和中心化工作流中一样。

此外,feature分支可以(也应该)被推送到中央仓库。这使得你和其他开发者共享这个功能,而又不改变官方代码。既然master只是一个“特殊”的分支,在中央仓库中储存多个feature分支不会引出什么问题。当然了,这也是备份每个开发者本地提交的好办法。

Pull Request

除了隔离功能开发之外,分支使得通过pull request讨论修改成为可能。一旦有人完成了一个功能,他们不会立即将它并入master。他们将feature分支推送到中央服务器上,发布一个pull request,请求将他们的修改并入master。这给了其他开发者在修改并入主代码库之前审查的机会。

代码审查是pull request的主要好处,但他们事实上被设计为成为讨论代码的一般场所。你可以把pull request看作是专注某个分支的讨论版。也就是说他们可以用于开发流程之前。比如,一个开发者在某个功能上需要帮助,他只需发起一个pull request。感兴趣的小伙伴会自动收到通知,看到相关提交中的问题。

一旦pull request被接受了,发布功能的行为和中心化的工作流是一样的。首先,确定你本地的master和上游的master已经同步。然后,将feature分支并入master,将更新的master推送回中央仓库。

栗子

下面这个

四种常见 Git 工作流比较的更多相关文章

  1. http协议里定义的四种常见数据的post方法

    原文 https://blog.csdn.net/charlene0824/article/details/51199292 关于http协议里定义的四种常见数据的post方法,分别是: applic ...

  2. 四种常见的App弹窗设计,你有仔细注意观察吗?

    弹窗又称为对话框,是App与用户进行交互的常见方式之一.弹窗分为模态弹窗和非模态弹窗两种,两者的区别在于需不需要用户对其进行回应.模态弹窗会打断用户的正常操作,要求用户必须对其进行回应,否则不能继续其 ...

  3. (转)四种常见的 POST 提交数据方式

    四种常见的 POST 提交数据方式(转自:https://imququ.com/post/four-ways-to-post-data-in-http.html) HTTP/1.1 协议规定的 HTT ...

  4. 四种常见的提示弹出框(success,warning,error,loading)原生JavaScript和jQuery分别实现

    原文:四种常见的提示弹出框(success,warning,error,loading)原生JavaScript和jQuery分别实现 虽然说现在官方的自带插件已经有很多了,但是有时候往往不能满足我们 ...

  5. 一文读懂四种常见的XML解析技术

    之前的文章我们讲解了<XML系列教程之Schema技术_上海尚学堂java培训技术干货><XML的概念.特点与作用.XML申明_上海Java培训技术干货>,大家可以点击回顾一下 ...

  6. 四种常见的 POST 提交数据方式(application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml)

    四种常见的 POST 提交数据方式(application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml) 转 ...

  7. application/json 四种常见的 POST 提交数据方式

    四种常见的 POST 提交数据方式   HTTP/1.1 协议规定的 HTTP 请求方法有 OPTIONS.GET.HEAD.POST.PUT.DELETE.TRACE.CONNECT 这几种.其中 ...

  8. 转:application/json 四种常见的 POST 提交数据方式

    四种常见的 POST 提交数据方式 HTTP/1.1 协议规定的 HTTP 请求方法有 OPTIONS.GET.HEAD.POST.PUT.DELETE.TRACE.CONNECT 这几种.其中 PO ...

  9. POST提交数据时四种常见的数据格式

    最近项目部署到新环境tomcat+mysql,想看看项目部署成功没有,就用soupui调对应接口开测试,soupui使用比较简单,给上接口地址,入参xml报文,把入参的media Type设置为app ...

随机推荐

  1. linux块设备驱动

    块设备驱动程序<1>.块设备和字符设备的区别1.读取数据的单元不同,块设备读写数据的基本单元是块,字符设备的基本单元是字节.2.块设备可以随机访问,字符设备只能顺序访问. 块设备的访问:当 ...

  2. 09-伪数组 arguments

    arguments代表的是实参.有个讲究的地方是:arguments只在函数中使用. (1)返回函数实参的个数:arguments.length 例子: fn(2,4); fn(2,4,6); fn( ...

  3. JNI和NDK

    作者:十岁的小男孩 QQ:929994365 心之安处即是吾乡 前言 本文试图通过解答以下三个问题来达到学习JNI和NDK的目的.是什么?有什么用?怎么用?文章内容前三节来自下面第一个链接的博主共享, ...

  4. PHP 抽象类

    * 抽象类 * 1.使用关键字: abstract * 2.类中只要有一个方法声明为abstract抽象方法,那么这个类就必须声明为抽象类 * 3.抽象方法只允许有方法声明与参数列表,不允许有方法体; ...

  5. php中静态方法和静态属性的介绍

    静态分为两个部分:静态属性和静态方法 静态的东西都是给类用的(包括类常量),非静态的都是给对象用的 静态属性 在定义属性的时候,使用关键字static修饰的属性称之为静态属性. 静态方法 使用stat ...

  6. ruby学习--条件控制

    条件控制 本人喜欢用程序demo记录的方式来记录某方法的使用,如times方法,仅作个人学习记录 #--------------if语句(相反是unless)而while相同于until------- ...

  7. 【C++ Primer | 15】构造函数与拷贝控制

    合成拷贝控制与继承 #include <iostream> using namespace std; class Base { public: Base() { cout << ...

  8. 里氏代换原则(Liskov Substitution Principle,LSP)

    第一种定义: 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换为o2,程序P的行为没有发生变化,那么类型S是类型T的子类型. 第二种定义: 所有引 ...

  9. tomcat启动慢解决方案

    一.环境 centos6.7  tomcat8    jdk1.8 二.现象 启动tomcat会花费10多分钟,正常情况下几秒就可以了. 三.解决办法 度娘了一下是因为jdk在生成随机数上耗时,找到j ...

  10. 【AtCoder】AGC017

    在此处输入标题 标签(空格分隔): 未分类 A - Biscuits dp[i][0/1]表示当前和是偶数还是奇数,直接转移即可 #include <bits/stdc++.h> #def ...