总结一下 Git 日常开发使用常用指令,以及一个常见的 GitFlow 实践模型。
工具准备 - Chocolatey
因为日常主要在 Windows 平台做开发,本文又设计了一些软件工具,故而先行引入一个 Windows 平台的包管理工具,Chocolatey,使用Powershell运行以下脚本即可成功安装 Chocolatey 的开源免费版本:
1 | Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) |
之后用的的软件工具都会直接以 Chocolatey 的方式安装,当然也可以Google搜索直接下载安装软件包。
没装 Git 环境的可以直接使用 Chocolatey 安装:
1 | choco install -y git |
Git 常用命令
一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要记住60~100个命令。
新建代码库
1 | # 在当前目录新建一个Git代码库 |
配置
Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。
1 | # 显示当前的Git配置 |
增加/删除文件
1 | # 添加指定文件到暂存区 |
代码提交
1 | # 提交暂存区到仓库区 |
分支
1 | # 列出所有本地分支 |
标签
1 | # 列出所有tag |
查看信息
1 | # 显示有变更的文件 |
远程同步
1 | # 下载远程仓库的所有变动 |
撤销
1 |
|
其他
1 | # 生成一个可供发布的压缩包 |
GitFlow(Git分支管理策略)
本文描述的是 Vincent Driessen 提出的一种比较成功的分支管理模型,非常适合借鉴甚至直接拿来使用。
两条主要分支
主分支 master
首先,代码库应该有一个、且仅有一个主分支。所有提供给用户使用的正式版本,都在这个主分支上发布。
Git版本库初始化以后,自动建立的默认分支叫做 master,我们通常把它作为主分支。
开发分支 develop
主分支只用来分布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支,叫做 develop。
这个分支可以用来生成代码的最新隔夜版本(nightly)。如果想正式对外发布,就在 master 分支上,对 develop 分支进行”合并”(merge)。
由 master 分支创建 develop 分支:
1 | git checkout -b develop master |
将 develop 分支发布到 master分支:
1 | # 切换到 master 分支 |
这里稍微解释一下,上一条命令中的–no-ff参数是什么意思。
默认情况下,Git执行”快进式合并”(fast-farward merge),会直接将 master 分支指向 develop 分支。
使用 –no-ff 参数后,会在合并时在 master 分支上生成一个新节点。
为了保证版本演进的清晰,我们希望采用这种做法。
关于合并的更多解释,请参考Benjamin Sandofsky的《Understanding the Git Workflow》。
三条辅助分支
在主要分支 master 和 develop 之外,我们的开发模型使用各种辅助分支来帮助团队成员之间的并行开发,简化特性的跟踪,为生产版本做准备,并协助快速解决生产问题。
临时性分支主要有三种:
- 特性(feature)分支
- 预发布(release)分支
- 热修复(hotfix)分支
与主要分支不同,这些辅助分支总是有一个有限的生命期,使用完以后,应该马上删除,使得代码库的常设分支始终只有 master 和develop。
特性分支(feature)
特性分支是为了开发某种特定功能,从 develop 分支上面分离出来,开发完成后,要再并入 develop。
特性分支(主题分支/功能分支)用于开发新的功能。
当开始开发一个功能时,包含这个功能的正式版本发布时间是不确定的。
特性分支的本质是,只要特性还在开发中,它就一直存在,最终会被合并回到 develop 中或者被删除。
特性分支的名字,可以采用feature-*的形式命名。
创建
创建一个特性分支:
1 | # 由 develop 分支 检出新的特性分支 "feature-x" |
完成
开发完成后,将特性分支合并到develop分支:
1 | # 切换到分支 'develop' |
Tips
删除特性分支
1 | # 删除分支 |
– no-ff 标志使 merge 即使可以通过快进执行 merge也始终创建一个新的提交对象。 这样可以避免丢失关于特性分支和特性组合在一起的历史存在的信息,这些特性组合在一起添加了特性。
直进式合并下不可能从 Git 历史记录中看到哪些提交对象一起实现了某个特性,如果一定需要,那就必须手动读取所有的日志消息,还原整个特性(即一组提交)是一件真正令人头疼的事情,而如果使用 – no-ff 标志,则很容易实现。
虽然它会创建更额外的空的提交对象,但是收益比成本大得多。
预发布分支(release)
预发布分支用来完成正式版本发布的准备工作。 可以在预发布分支上修复较小的错误,并为发行版准备元数据(版本号、构建日期等)。
当 develop (几乎)反映了新版本的期望状态,即针对即将构建的发行版的所有特性都已经合并到 develop 时,从 develop 分支检出一个预发布分支。
从发布分支的开始,即将发布的版本才会被分配一个版本号。
在此之前,开发分支反映了“下一个版本”的变化,但是在发布分支启动之前,还不清楚“下一个版本”最终会变成0.3还是1.0。
这个决定是在发布分支的开始时做出的,并根据项目中关于版本号碰撞的规则执行。
预发布分支是 develop 分支上面分出来的,预发布结束以后,必须合并进 develop 和 master分支。
它的命名,可以采用release-*的形式。
创建
假设1.1.5版本是当前的产品版本,我们即将发布一个大的版本。
开发的状态已经为“下一个版本”做好了准备,我们已经决定这将成为1.2版本(而不是1.1.6或2.0)。
PS:假设存在脚本 bump-version.sh 接收一个版本号参数,功能是修改项目的版本号相关文件以切换最终发布产品的版本。
1 | # 由 develop 分支 检出新的预发布分支 "release-1.2" |
预发布分支存在的时间区间为:从完成下一个版本所有特性开发,到正式发布版本。
在此期间,可以在这个分支(而不是在 develop 分支)中提交 bug 修复。
这里严禁添加大型的新特性,大型新特性必须合并到 develop,已经在预发布阶段的情况下,新的大型新特性最好归入下一个版本。
完成
当发布分支的状态准备好成为真正的发布时,需要执行一些操作。
首先,将发布分支合并到 master 中(根据定义,master 上的每个提交都是一个新版本,请牢记这一点)。
接下来,必须对 master 上的提交进行标记(tip),以便将来参考这个历史版本。
最后,需要将在发行版分支上所做的更改合并回到 develop 中,以便将来的发行版也包含这些错误修复。
1 | # 切换到分支 'master' |
这一步很可能导致合并冲突,毕竟我们已经更改了版本号。 需要修复并提交。
到这里我们完成了预发布流程,发布分支可以直接删除:
1 | $ git branch -d release-1.2 |
热修复分支(hotfix)
热修复分支非常类似于预发布分支,因为它们也意味着为新的产品发布做准备,尽管是计划外的。
它们产生于生产版本发现的问题有立即采取行动的必要性时。
当生产版本中的关键错误必须立即解决时,从标记生产版本的主分支上的相应标记分检出新的热修复分支。
通常修复不会涉及所有团队成员,团队成员在开发分支上的工作可以继续,bug 涉及的成员在热修复分支上做快速修复。
修补bug分支是从 master 分支上面分出来的。修补结束以后,再合并进 master 和 develop 分支。
它的命名,可以采用hotfix-*的形式。
创建
假设版本1.2是当前正在运行的生产版本,并且由于一个严重的 bug 而导致了问题。
但是 develop 分支上的变化仍然是不稳定的。
这时我们可以由 master 分支检出一个 hotfix 分支并开始修复问题:
** 热修复通常意味着需要发布生产版本,所以同样需要更改版本号 **
1 | # 检出热修复分支 "hotfix-1.2.1" |
接下来,经过紧锣密鼓的排查,我们找到了错误点并做出了修复:
1 | $ git commit -m "Fixed severe production problem" |
完成
当修复完成时,bug 修复合并回 master 中的同时也需要合并回 develop 中,以保证 bug 修复也包含在下一个版本中。
这与发布分支的完成方式完全相似。
首先,更新主版本并标记发布版本。
1 | # 切换到分支 'master' |
接下来,更新开发版本:
1 | # 切换到分支 'develop' |
这里还存在一种特殊情况,就是当预发布分支存在时,热修复应当合并到热修复分支而不是开发分支。
最后,删除临时分支:
1 | $ git branch -d hotfix-1.2.1 |
注意事项
1. 使用非直进式合并(git merge –no-ff)
默认情况下,Git执行”快进式合并”(fast-farward merge),会直接将 master 分支指向 develop 分支。
使用 –no-ff 参数后,会在合并时在 master 分支上生成一个新节点。
非直进式合并始终创建一个新的提交对象。 这样可以避免丢失关于特性分支和特性组合在一起的历史存在的信息,这些特性组合在一起添加了特性。
直进式合并下不可能从 Git 历史记录中看到哪些提交对象一起实现了某个特性,如果一定需要,那就必须手动读取所有的日志消息,还原整个特性(即一组提交)是一件真正令人头疼的事情,而如果使用 –no-ff 标志,则很容易实现。
虽然它会创建更额外的空的提交对象,但是收益比成本大得多。
为了保证版本演进的清晰,我们希望采用这种做法。
Tips:可以通过修改全局配置实现
1 | # pull操作时禁止 --no-ff |
2. 使用变基式拉取(git pull –rebase)
多人同时在同一分支开发的情况下,推荐在拉取代码时使用 –rebase,
这是因为默认的 git pull 操作相当于git fetch + git merge FETCH_HEAD,会经过一次合并,生成一个新的节点,之前的提交分开显示。
而 git pull –rebase 则相当于 git fetch + git rebase FETCH_HEAD,不会生成新的节点,是将两个分支融合成一个线性的提交。
想要更好的提交树,使用rebase操作会更好一点。
这样可以线性的看到每一次提交,并且没有增加提交节点。
merge 操作遇到冲突的时候,当前merge不能继续进行下去。手动修改冲突内容后,add 修改,commit 就可以了。
而rebase 操作的话,会中断rebase,同时会提示去解决冲突。
解决冲突后,将修改add后执行git rebase –continue继续操作,或者git rebase –skip忽略冲突。
Tips: 可以配置为总是使用 pull-rebase
1 | # 配置 pull 操作时总是使用 -rebase |
常用工具
TortoiseGit
TortoiseGit 是一个提供给 Git的 Windows Shell 界面程序,基于 TortoiseSVN。它是开源的,可以完全免费使用。
界面
……
安装
TortoiseGit-2.9.0.0-64bit.msi
TortoiseGit-LanguagePack-2.9.0.0-64bit-zh_CN.msi
1 | choco install -y TortoiseGit |
Sourcetree
https://www.sourcetreeapp.com/
Sourcetree 是一个针对 Windows 和 Mac 的免费 Git 客户端,简化了与 Git 仓库的交互,这样你就可以专注于编码。
通过 Sourcetree 的 Git GUI 可以可视化和管理存储库。
界面
安装
SourceTreeSetup-3.3.6.exe
1 | choco install -y SourceTree |
参考文献
[1]Vincent Driessen.A successful Git branching model[OL].nvie.com,2010.
[2]阮一峰.Git 工作流程[OL].www.ruanyifeng.com,2015.
[3]阮一峰.Git分支管理策略[OL].www.ruanyifeng.com,2012.