Git 工作流

中心化的工作流

优势

  • 首先它让每个开发者都有自己的本地的完整项目副本。隔离的环境使得每个开发都的工作独立于项目的其它修改 —— 他们可以在自己的本地仓库中添加提交,完全无视上游的开发,直到需要的时候。
  • 其次,它让你接触到了 Git 分支和合并模型。Git 分支被设计为故障安全的机制,用来在仓库之间整合代码和共享更改。

如何工作

  • 中心化的工作将中央仓库作为项目中所有修改的唯一入口。默认的开发分支叫做 master,所有的更改都被提交到这个分支。这种工作流不需要 master 之外的其它分支。
  • 开发者将中央仓库克隆到本地后开始工作。在他们的本地项目副本中,他们可以像 SVN 一样修改文件和提交更改;不过这些新的提交被保存在本地 —— 它们和中央仓库完全隔离。这使得开发者可以将和上游的同步推迟到他们方便的时候。
  • 为了向官方项目发布修改,开发者将他们本地 master 分支“推送”到中央仓库。这一步等同于 svn commit,除了 Git 添加的是所有不在中央 master 分支上的提交。

管理冲突

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

  • 在开发在提交功能之前,需要 fetch 更新中央提交,在它们之上 rebase 自己的更改。
  • 如果本地修改和上游提交的冲突时,Git 会暂停 rebase 流程,给你机会手工解决这些冲突。Git 很赞的一点是,它将 git status 和 git add 命令同时用来生成提交和解决合并冲突。这使得开发能够轻而易举的管理他们的合并。另外,如果他们改错了什么,Git 能让他们轻易的退出 rebase 过程,然后重试。

例子

  • 项目管理员生成一个空的版本库

    ssh user@host git init --bare /path/to/repo.git
    
  • 三个人 A, B, C 同时编写同一个项目,需要先在本地创建一个完整的项目副本。

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

此时,Git 自动添加了一个名为 origin 的运程连接,指向中央仓库,以方便提交。 A 可以使用标准 Git 提交流程开发功能:编辑、缓存、提交。

git status
git add <some file>
git commit

同时,B 也在本地进行自己的开发工作。

  • A 发布了他们修改
    git push origin master
    

此时中央仓库会将 master -> origin/master

  • B 试图发布修改
    git push origin master
    

但是因为 A 已经提交了功能到中央仓库,导致 B 的本地历史和中央仓库分叉,Git 会拒绝本次提交。

  • B 如果想提交,必须要先 rebase 本地仓库

可以使用 git pull 来拉取并修改,

git pull --rebase origin master
  • --rebase 命令告诉 Git,在同步中央仓库的修改之后,将 B 的所有提交移到 master 分支的顶端。

  • 如果没有冲突的文件,B 就可以直接进行提交了,但是如果存在冲突,可以根据提示查找冲突的文件,修改之后,可以继续 rebase 操作。
    git add <some-file>
    git rebase --continue
    

同样的,如果此时不知道自己做了什么,可以回滚一次操作。

git rebase --abort
  • 然后再进行 push 就可以提交到中央版本库了。

基于功能人分支的工作流

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 已经同步。然后可以将 feature 分支并入 master,将更新的 master 推送回中央仓库。

Gitflow 工作流

  • GitFlow 工作流围绕项目发布定义了一个严格的分支模型。有些地方比功能分支工作流更复杂,为管理大型项目提供了框架。
  • 和功能分支工作流相比,这种工作流没有增加任何新的概念或命令。它给不同的分支指定了特定的角色,定义它们应该如何、什么时候交流。除了功能分支之外,它还为准备发布、维护发布、记录发布分别使用了单独的分支。当然,还能享受到功能分支工作流带来的所有好处:pull request、隔离实验和更高效的协作。

如何工作

  • GitFlow 工作流仍然使用中央仓库作为开发者沟通的中心。和其它工作流一样,开发者在本地工作,将分支推送到中央仓库。唯一的区别在于项目的分支结构。

历史分支

  • 和单独的 master 分支不同,这种工作流使用两个分支来记录项目历史。master 分支储存官方发布历史,develop 分支用来整合功能分支。同时,这还方便了在 master 分支上给所有提交打上版本号标签。

  • 工作流剩下的部分围绕这两个分支的差别展开。

功能分支

  • 每个新功能都放置在自己的分支中,可以在备份/协作时推送到中央仓库。但是与其合并到 master,功能分支将开发分支作为父分支。当一个功能完成时,它将被合并回 develop。功能永远不应该支持在 master 上交互。

  • 功能分支加上 develop 分支就是我们之前据说的功能分支工作流。

发布分支

  • 一旦 develop 分支的新功能足够发布,你可以从 develop 分支 fork 一个发布分支。这个分支的创建开始了下个发布周期,只有和发布相关的任务应该在这个分支进行,如修复 bug、生成文档等。一旦准备好发布,发布分支将合并进 master,打上版本号的标签。另外,它也应该合并回 develop,后者可能在发布启动之后有了新的进展。
  • 使用一个专门的分支来准备发布确保一个团队完善当前的发布,其它团队可以继续开发下一个发布的功能。它还建立了清晰的开发阶段。
  • 通常约定:
    • 从 develop 创建分支
    • 合并进 master 分支
    • 命名规范 release-* 或者 release/*

维护分支

  • 维护或者“紧急修复”分支用来快速给产品发布打上补丁。这是唯一可以从 master 上 fork 的分支。一旦修复完成了,它应该被并入 master 和 develop 分支,master 应该打上更新的版本号的标签。
  • 有一个专门的 bug 修复开发线使得团队能够处理 issue,而不打断其它工作流或是要等到下一个发布周期。你可以将维护分支看作在 master 分支上工作的临时发布分支。

例子

创建一个开发分支

  • 为默认的 master 分支创建一个互补的 develop 分支。最简单的办法是在本地创建一个空的 develop 分支,将他推送到服务器上:

    git branch develop
    git push -u origin develop
    
  • 这个分支将会包含项目中所有的历史,而 master 将包含不完全的版本。其他开发者应该将中央仓库克隆到本地,创建一个分支来追踪 develop 分支:

    git clone http://xxx/xx/repo.git
    git checkout -b develop origin/develop
    

开始了新的功能

  • 当两个人都需要在不同分支上开始工作,即为自己的功能创建单独的分支。且他们的分支都是基于 develop 而不是 master:

    git checkout -b some-feature develop
    
  • 他们都使用“编辑、缓存、提交”的一般约定来向功能分支添加提交:

    git status
    git add <some-file>
    git commit
    

完成功能

  • 添加了一些提交后,可以使用 pull request,现在正是发起的好时机,请求将新功能并入 develop 分支。否则可以先并入本地的 develop 分支,推送到中央仓库:

    git pull origin develop
    git checkout develop
    git merge some-feature
    git push
    git branch -d some-feature
    
  • 第一个命令在尝试并入功能分支之前确保 develop 分支已经是最新的。注意,功能绝不该直接并入 master。冲突的处理方式和中心化工作流相同。

发布新功能

  • 当另外的开发人员,仍在他自己的分支上工作时,开始准备项目的第一个官方发布。和开发功能一样,新建一个分支来封装发布的准备工作。这也正是发布的版本号创建的第一步:

    git checkout -b release-0.1 develop
    
  • 这个分支用来整理提交,充分测试,更新文档,为即将到来的发布做各种准备。它就像是一个专门用来完善发布的功能分支。

  • 一旦发布准备稳妥,即将其并入 master 和 develop,然后删除发布分支。合并回 develop 很重要,因为可能已经有关键的更新添加到发布分支上,而开发新功能需要用到它们。同样的,如果团队重视代码审查,现在将是发起 pull request 的完美时机。

    git checkout master
    git merge release-0.1
    git push
    git checkout develop
    git merge release-0.1
    git push
    git branch -d release-0.1
    
  • 发布分支是功能开发(develop)分支和公开发布(master)之间的过渡阶段。不论什么时候,将提交并入 msater 时,你应该为提交打上方便引用的标签:

    git tag -a 0.1 -m "Initial public release" master
    git push --tags
    
  • Git 提供了许多钩子,即仓库中特定事件发生时被执行的脚本。当你向中央仓库推送 master 分支或者标签时,你可以配置一个钩子来自动化构建公开发布。

终端用户发现一个 Bug

  • 正式发布之后,两个开发一起为下一个发布开发功能。这时,一个终端用户开了一个 issue 抱怨说当前发布中存在一个 Bug。为了解决这个 bug,先从 master 创建一个维护分支,用几个提交修复这个 issue,然后直接合并回 master。

    git checkout -b issue*001 master
    ##Fix the bug
    git checkout master
    git merge issue-*001
    git push
    
  • 和发布分支一样,维护分支包含了 develop 中需要的重要更新,因此需要执行同样的合并。接下来,可以删除这个分支:

    git checkout develop
    git merge issue-*001
    git push
    git branch -d issue-*001
    

各分支的意义

  • feature (多个) 主要是自己玩了,差不多的时候要合并回 develop 去。不与 master 交互。
  • develop (同时间一个) 主要是和 feature 以及 release 交互
  • release (同时间一个) 总是基于 develop,最后又合并回 develop。当然对应的 tag 要合并到 master 分支,生命周期短,仅是为了发布程序
  • hotfix (同一时间一个) 总是基于 master,并最后合并到 master 和 develop。生命同期较短,用来修复 bug 或小粒度修改发布
  • master (仅一个) 关联 tag 和发布

模型中各个模块内容的使用

  • 在这个模型中,master 和 develop 都具有象征意义。master 分支上的代码总是稳定的 (stable build),随时可以发布出去。develop 上的代码总是从 feature 上合并过来的,可以进行 Nightly Builds,但不直接在 develop 上进行开发。当 develop 上的 featur 足够多以致于可以进行新版本的发布时,可以创建 release 分支。
  • release 分支基于 develop,进行委阴简单的修改后就被合并到 master,并打上 tag,表示可以发布了。紧接着 release 将被合并到 develop;此时 Develop 可能往前跑了一段,出现合并冲突,需要手工解决冲突后再次合并,这步完成后就删除 release 分支
  • 当从已发布版本中发现 bug 要修复时,就应用到 hotfix 分支了。hotfix 基于 master 分支,完成 bug 修复或者紧急修改后,要 merge 回 master,打上一个新的 tag,并 merge 回 develop,删除 hotfix 分支。
  • 由此可见 release 和 hotfix 的生命周期都较短,而 master 和 develop 虽然总是存在,但去不常使用。
Author
Tags
总结(3) Emacs(2) org mode(4) 年度清单(2) 读书清单(2) 电影清单(2) 电视清单(2) Python(3) 学习计划(1) 子弹笔记(1) 手帐体系(1) 时间管理(1) 时间使用效率(1) 形意拳(1) 知识管理(1) 简历(1) 技能水平(1) 生活(1) 减法生活(1) 阅读(1) 阅读分组(1) org(1) docx(1) markdown(2) cmder(1) 图床(1) jsdelivr(1) github(1) 安卓系统(1) 手动更新手机系统(1) post link(1) post path(1) hexo(2) hugo(2) GitHub Pages(1) travis(1) 自动部署博客(1) presentation(1) gcc(1) g++(1) 默认表格(1) 复杂表格(1) multimd table(1) google adsense(1) evil(1) surround(1) librime(2) emacs-rime(1) liberime(1) dot(1) graphviz(1) use-sub-superscripts(1) ditaa(1) Evil Multiple cursors(1) turn evil mode off(1) modifier keys(1) keymap(1) super(1) hyper(1) install Emacs(1) Mac OS(1) keybinds(1) clocktable(1) coding system(1) image library(1) keybind(1) spacemacs(1) org babel(1) homebrew(1) dd(1) diskutil(1) tmux(1) xcode(1) xcrun(1) node-gyp(1) tar(1) tree(1) pacman(1) plantuml(1) find(1) grep(2) du(1) apt-get(1) apt(1) regex(1) wildcard(1) zsh(1) gdb(1) lsof(1) netstat(1) rebase(2) workflow(1) 工作流(1) .gitignore(1) postbuffer(1) hung up(1) merge(1) cache(1) reset(1) Git(1) command(1) submodule(1) GitHub Pull Request(1) pr(1) Matplotlib(1) beautifusoup(1) 下载小说(1) SOLID(1) Leetcode(2) 透视表(1) read_excel()(1) pandas(1) conda(1) django(1) markdown-deux(1) mysql(1) oracle(1) 模糊查询(1) logging level(1) reflex(1) Field(1) Maven(1) Selenium(1) 下拉框(1) select into(1) insert into select(1) CAST(1) CONVERT(1) timestamp(1) concat(1) concat_ws(1) group_concat(1) union(1) grant privileges(1) 重置 root 密码(1) 报错 10060(1) 清理连接数(1) create user(1) set password(1) exists(1) in(1) markdown-it(1) emoji(1) tasks(1) valine(1) Code 504(1) Hugo(1) theme(1) substring(1) indexOf(1) RegExp(1) 定位节点(1) 正则匹配(1) async(1) 异步请求(1) css(2) layui(1) 滚动条(1) JavaScript(1)