使用rebase的Git工作流

栏目: 编程工具 · 发布时间: 5年前

内容简介:Git是目前最为强大的代码版本管理工具,被开源社区和各大公司所广泛使用。使用Git进行团队协作开发是很便利的一件事情,但是在多人协作的过程中,我们也会面临如何运用好Git的问题。这种情况下,就出现了各种各样的风险总是与利益并存的,使用

Git是目前最为强大的代码版本管理工具,被开源社区和各大公司所广泛使用。使用Git进行团队协作开发是很便利的一件事情,但是在多人协作的过程中,我们也会面临如何运用好Git的问题。这种情况下,就出现了各种各样的 Git Workflow ,而本文将介绍一种基于 rebase 的工作流,这种工作流也是目前开源社区所比较推崇的做法,了解了这种工作流之后可以更好地优化对git的使用、对代码的管理

一、Rebase和Squash

1、Rebase是什么,为什么使用Rebase

rebase 是能够将我们对代码的更改从一个分支集成到另一个分支中的git命令之一(另一个命令是 Merge )。使用 rebase 的一个风险在于,它会改写 commit 历史,如果操作不当那么会是一种破坏性的操作。

风险总是与利益并存的,使用 rebase 也好处良多,体现在于:

  • 能够保持提交记录的清爽,不带来额外的提交历史(而使用 merge 会生成一条 mergecommit
  • 能够保持commit线的线性,保持成一条直线,从而能够容易地看出代码是如何推进的

2、Squash是什么,为什么使用Squash

rebase 可以运行在 “交互(interactive)” 模式下,交互模式下的 rebase 操作允许我们将多次 commit 进行压缩,从而合并成更少的、甚至单一的一次提交。

之所以这么做,是因为在我们有很多 commit 时,在最终合并到 master 分支时,这些 commit 就都会进入 mastercommit 历史中,那么这会导致 master 中的提交历史不那么可读。

举个例子,假设我们为某个 feature 进行了多次 commit ,每次 commit 都只是做了一点点的更新,那么commit历史看起来会像是这样:

e94d2fb (HEAD -> feat-china) feat: Add Shenzhen
81d2258 feat: Add Guangzhou
1525479 feat: Add Shanghai
e8021a2 feat: Add Beijing
40d9fc0 feat: Add headline
ce9657d (upstream/master, origin/master, origin/HEAD, master) feat: i18n (#1)
ab15f4f feat(i18n): Add support for Japanese
a39b290 feat: Add new headline
4774815 Initial commit

我们之所以频繁地提交 commit ,是为了能够及时地存档,以便能够在需要的时候方便地回滚代码。但是为了修复特定问题、开发某个特性的多次小提交,其实是仅仅在当前开发上下文中有意义的,可能我们为此提交了十几个 commit ,但是对于其他同事而言,他们可能就只想知道我们这次做了什么事情。那么通过 Squash 这个操作,我们就能够将这些小 commit 进行合并,组成一个有意义的 commit 。以下是用了 Squash 后的结果:

c27766b (Head -> feat-china) feat: show first-tier cities of China
4774815 (master) Initial commit

二、基本法则

在这套工作流中,有一些重要的法则,即:

  • 在fork的仓库上拉分支 每个贡献者都应该 fork 代码仓库并且在该 fork 出的仓库上进行开发。通过 fork 仓库,就可以拥有独立的工作空间,从而可以放心大胆地进行开发、修改等,对于多人开发而言,还无需事先获得分支权限也能继续开发( Tips: 其实并不建议多人在同一个分支上进行开发,因为这样子很容易导致合并冲突,如果某个 feature 需要不止一个人去开发,相比只拉一个 feature 分支,把大的 feature 拆分为小的可独立工作单元其实是一种更好的实践)
  • 禁止对一个多人协作的分支进行rebase 这一点 非常重要 ,贡献者只能对自己 fork 仓库的分支进行 rebase ,这是因为 rebase 会改写 commit 历史,是比较危险的一种操作,只有对自己 fork 仓库的分支进行 rebase ,才不会有任何问题

三、工作角色

在这套工作流中,涉及两个角色:

  • Maintainer(维护者) :维护者拥有仓库的写权限,他们可对 Pull Request 进行 Review 来决定通过或者拒绝,并且能创建 Git Tag 用于发布
  • Contributor(贡献者) :贡献者拥有仓库的读和 fork 权限,可以查看和创建 issue ,也可以提交 Pull Request 。贡献者也负责解决合并冲突,此外贡献者仅能推送分支到自己 fork 的仓库里

四、准备工作

在开始这套工作流之前,我们需要先做一些起始步骤。首先,需要先 fork 项目仓库(通常原项目仓库惯称为 upstream ,上游仓库);然后,在本地 clone 这个 fork 后的仓库(与此对应的远程仓库惯称为 origin );之后,在本地的 remote 记录中添加这个 upstream 仓库,具体步骤如下:

  1. Fork 上游仓库,如在 Github 中可以点击右上角的“Fork”按钮:
    使用rebase的Git工作流
  2. 在本地 clone 这个 fork 后的仓库,如:
$ git clone git@github.com:RuphiLau/playgit.git
$ cd playgit
  1. 添加上游仓库
$ git remote add upstream git@github.com:trialground/playgit.git
  1. 确认信息:通过 git remote -v 确认我们是否成功进行了配置,正常而言需要显示两组记录(一组 origin ,一组 upstream ),如:
origin git@github.com:RuphiLau/playgit.git (fetch)
origin git@github.com:RuphiLau/playgit.git (push)
upstream git@github.com:trialground/playgit.git (fetch)
upstream git@github.com:trialground/playgit.git (push)

五、工作流

好了,说了这么多前置知识,总算应该开始进入正题了。在工作流中,建议所提交的代码是关联一个 user story(用户故事) 或者一个 issue 的,可以和一些外部系统相关联(如 JIRAVersionOne ),本套工作流的步骤总结如下:

  • 第一步: fetch 上游仓库的更新
  • 第二步:upstream/master 分支和本地 master 合并
  • 第三步: 在本地新建分支
  • 第四步: 写代码、提交代码
  • 第五步: 再次 fetch 上游仓库的更新(以同步在拉取分支之后 upstream/master 中更新的内容)
  • 第六步: 对分支基于 upstream/master 进行 rebasesquash ,并且解决合并冲突(如果有)
  • 第七步: 推送分支
  • 第八步: 发起一个 Pull Request ,进行 Code ReviewCode Review 通过后,合并到 master ,此后可以删除本地分支

以下是具体的细节:

1、 fetch 上游仓库的更新

我们开发时,应当基于最新版本的代码库进行开发。国际惯例,我们一般将原始被fork的那个仓库成为 upstream 仓库(上游仓库),通过 fetch 命令,我们可以将 upstream 中的内容获取下来存在本地,即执行:

$ git fetch upstream

示例流程图:

ORIGIN:
 master: A->B

UPSTREAM:
 master: A->B->C

LOCAL:
          master: A->B
   origin/master: A->B
 upstream/master: A->B->C   # 获取结果

2、将 upstream/master 分支和本地 master 合并

一般情况下,我们都是先拉取远程分支,然后基于此创建本地新分支,这样子就能够拥有最新的代码。不过在创建本地新分支前,我们可以先执行以下命令:

$ git checkout master
$ git merge upstream/master

这样子将会执行一个 快速合并(Fast-Forward) 来让 masterupstream/master 指向同一个提交,示例图:

ORIGIN:
 master: A->B

UPSTREAM:
 master: A->B->C

LOCAL:
          master: A->B->C   # 新增了C
   origin/master: A->B
 upstream/master: A->B->C

3、创建本地新分支

现在 master 分支是最新的了,因此我们可以基于此来拉新分支:

$ git checkout -b feat-xyz

示例图:

ORIGIN:
 master: A->B

UPSTREAM:
 master: A->B->C

LOCAL:
          master: A->B->C
   origin/master: A->B
 upstream/master: A->B->C
        feat-xyz: A->B->C   # 新增了 feat-xyz 分支

4、编写代码、提交代码

这一步就是开发的主要步骤了,开发过程中我们可以时不时地提交 commit (当然也要确保 commit 是有意义的),示例图:

ORIGIN:
 master: A->B

UPSTREAM:
 master: A->B->C

LOCAL:
          master: A->B->C
   origin/master: A->B
 upstream/master: A->B->C
        feat-xyz: A->B->C->D->E->F   # 新增了 D、E、F 三次 commit

5、再次拉取代码

编码结束后,在发起一个 Pull Request 合并上游仓库的 master 分支之前,我们需要抓取一些 upstream 里的新提交记录(这是因为对于原仓库而言,我们不是唯一的一个开发者),示例图:

ORIGIN:
 master: A->B

UPSTREAM:
 master: A->B->C->C2->C3   # 其他人新提交了 C2、C3 两次 commit

LOCAL:
          master: A->B->C
   origin/master: A->B
 upstream/master: A->B->C
        feat-xyz: A->B->C->D->E->F

为了保持和 upstream/master 的同步,我们需要使用 git fetch

$ git fetch upstream

拉取之后,示例图:

LOCAL:
          master: A->B->C
   origin/master: A->B
 upstream/master: A->B->C->C2->C3   # 拉取结果
        feat-xyz: A->B->C->D->E->F

6、Rebase和Squash

rebase 会改变原 commit 所基于的分支(简称 变基 ),并且创建带着新 SHA-1 哈希的新 commit (与此同时会保留原有提交信息)。而 squash 会将多个 commit 压缩为一个新的 commit (也可以是多个 commit )并且带上新的 SHA-1 哈希值。通常情况下,我们会想要对目标合并分支进行 rebase ,当最终创建 Pull Request 的时候,这个 rebasesquash 后的分支就会从 origin 仓库的分支进入到 upstream 仓库的master分支里,所以我们需要 rebase upstream/master ,为了执行 squash 操作,我们需要运行交互模式的 rebase ,如下:

$ git rebase --interactive upstream/master

这将会打开默认的编辑器,然后呈现将被 rebasecommit 列表,如:

pick 40d9fc0 feat: Add headline
pick e8021a2 feat: Add Beijing
pick 1525479 feat: Add Shanghai
pick 81d2258 feat: Add Guangzhou
pick e94d2fb feat: Add Shenzhen

# Rebase ce9657d..e94d2fb onto ce9657d (5 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

开头即为 commit 列表,而 # 中的内容则是对一些操作的解释。 commit 列表中列出了从旧到新的每次 commit ,然后我们可以使用说明里的命令选择对这些 commit 执行怎样的一个操作,当我们操作完了后,可以保存文件然后退出编辑器。这之后, rebase 就会基于所选择的命令进行处理。

rebase 过程中,可能会有合并冲突(比如你和 upstream/master 里都修改了同个文件的同一部分),那么这时候,需要手动解决冲突,步骤如下:

  • 通过 git status 查看哪些文件发生了冲突
  • 手动解决冲突
  • 运行 git add 来暂存文件
  • 运行 git rebase --continue 来继续合并过程(不需要用 git commit 来解决合并冲突)

rebase 之后的示例图如下:

ORIGIN:
 master: A->B

UPSTREAM:
 master: A->B->C->C2->C3

LOCAL:
          master: A->B->C
   origin/master: A->B
 upstream/master: A->B->C->C2->C3
        feat-xyz: A->B->C->C2->C3->D->E->F   # Rebase结果

如果我们还执行了 squash 操作,那么这多次的 commit 会被压缩, git 会提示我们输入 commit message 来作为这次压缩后的 commit message ,所以我们通常需要让这条 message 能够概括多次 commit 的内容,示例图:

LOCAL:
          master: A->B->C
   origin/master: A->B
 upstream/master: A->B->C->C2->C3
        feat-xyz: A->B->C->C2->C3->DEF   # Squash结果

7、推送分支

为了创建一个 Pull Request ,我们需要将分支推送到 origin 仓库,可以运行:

$ git push --set-upstream origin feat-china

如果你已经推送过你的分支了,并且想要更新它,那么以上的命令会执行失败。这是因此 rebase 改写了 commit 历史,所以不再有一个公共的 commit ,因此需要使用 --force 选项来告诉 git 放弃并覆盖远程分支,即:

$ git push --force origin feat-china

Tips:以上也是为什么我们要在 fork 分支上独立写代码的主要原因

8、发起一个Pull Request

到这里,是时候对上游仓库的 master 分支发起一个来自 origin 仓库 feat-china 分支的 PR 了,通常在我们 push 之后,在 github 中可以看到如下图所示:

使用rebase的Git工作流

这时候点击 “Compare && pull request” 便可进入发起 PR 的流程

ORIGIN:
   master: A->B
 feat-xyz: A->B->C->C2->C3->DEF  ----
                                    | Pull Request
UPSTREAM:                           | 
 master: A->B->C->C2->C3        <----

进入 PR 创建页面后,我们可以填写对这个 PR 的描述信息,然后点击 Create pull request 便可成功发起 PR
使用rebase的Git工作流

我们可以通过填写 Reviewers 邀请同事进行 Code Review (与此同时,还可以通过 CI 处理一些流程,比如校验代码格式、判断是否已经 Review 通过,必须完成这项步骤才能合并代码):

使用rebase的Git工作流

CI (如果有)通过和 Code Review 通过后,我们便可以点击 Squash and merge 或者 Rebase and merge 来合并代码到 master (这里我们推荐 Squash and merge ,可以压缩多次 commit 为一次):

使用rebase的Git工作流

此后,维护者接受PR,代码就会被自动合并到 master 分支里,并且关闭该 PR 。然后,我们也可以做一些后期清理工作:删除本地分支和远程分支(如 feat-chinaorigin/feat-china ),可以执行如下命令:

$ git branch -D feat-china
$ git push origin --delete feat-china

最终效果为:

ORIGIN:
   master: A->B

UPSTREAM:
 master: A->B->C->C2->C3->DEF

LOCAL:
          master: A->B->C
   origin/master: A->B
 upstream/master: A->B->C->C2->C3->DEF

六、成果

现在,让我们来看一下这个工作流带来的成果,以下是以上步骤完成后,所呈现出的一个 commit 历史:

使用rebase的Git工作流

怎么样,是不是又干净又清爽呢?I do believe that you'll enjoy it !

参考资料


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming PHP

Programming PHP

Rasmus Lerdorf、Kevin Tatroe、Peter MacIntyre / O'Reilly Media / 2006-5-5 / USD 39.99

Programming PHP, 2nd Edition, is the authoritative guide to PHP 5 and is filled with the unique knowledge of the creator of PHP (Rasmus Lerdorf) and other PHP experts. When it comes to creating websit......一起来看看 《Programming PHP》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具