前言
先上图吧:
相信大家经常看到这个git-flow流程图,但是大部分人会有疑惑,我们项目中操作不是这么走的,刚好有空一探究竟,不同git-flow适合什么样团队,有什么优缺点。
常见的开发场景
开发新功能
- 从
develop
拉一个feature
分支。 - 在
feature
分支上做开发。 feature
开发完成后,需要提交测试,有两种场景:
- a. 一种是该任务功能不复杂,测试点不多,自测充分后可以直接关闭该任务,即合并
feature
分支; - b. 另一种是任务功能非常复杂,测试点也很多。这个时候不要马上完成任务,前期让QA直接在
feature
分支上测试,等测得比较稳定后再合并feature
分支。因为功能复杂的任务,自测完成后肯定还是有很多问题,过早合到develop
,develop
将有很长一段时间不能上线,会影响到其它的发布。
- 完成任务后将
feature
分支合并到develop
,删除原分支。
上线测试环境和上线生产环境
develop
会不断累积新内容等待上线。上线时,准备一个release
分支,准备release
分支时需要搞清楚这次上线新增的内容,列给QA,让QA有针对性的测试。- 在打包机器上打包对应的
release
分支,部署到应用服务器让QA测试,发现bug直接在release
分支的基础上修改。 release
测试通过后,可以上线了,则将release
合到master
和develop
,删除该release
分支。- 线上部署的时候,从
master
打一个包并标记版本,先部署到预发,在预发上简单回归测试,没问题后发布到线上服务器,上线完成。
紧急修复
- 对于一些需要马上修改并尽快上线的内容,走
hotfix
分支。 - 从最新的
master
拉hotfix
分支并在hotfix
分支上修改。 - 在打包机器上打包对应的
hotfix
分支,部署到应用服务器给qa测试,发现bug直接在hotfix
分支上修改。 hotfix
测试通过后,可以上线了,则将hotfix
合并到master
和develop
,删除该hotfix
原分支。
虽然谈到某个场景,相应处理都差不多,但具体到不同团队项目实现都会有不同差异,团队开发中,遵循一个合理、清晰的Git使用流程,是非常重要的。否则,每个人都提交一堆杂乱无章的commit,项目很快就会变得难以协调和维护。
多人合作大项目如何进行Git管理
Git Flow 开发流程
主体思想如下:
缺点:每个功能开发feature分支都在develop分支汇合,会导致同段时间进行开发的功能,必须同时上线。
master
【线上的分支】 - 是线上版本分支,也可以理解为随时可以发布的稳定版本,要求在每次版本封版后由主程序员合并release
分支代码进来,开发人员不可以随意操作。
develop
【开发基础分支】 - 包含待上线的新内容,是你进行任何新的开发的基础分支。当你开始一个新的功能分支时,它将是开发的基础,由此拉出feature
分支准备新功能开发。另外,该分支也汇集所有已经完成的功能,并等待经过release
分支测试通过后最终被整合到 master
分支中。
release
【上线分支】 - 当开发结束后用来提测并且为本次版本最终上线的分支,所有测试阶段的bug全部在此分支修复,测试结束后合并到 master
和 develop
分支中。
当准备将develop上的新内容发布到生产环境时,需要拉release分支。release分支可以隔离develop后续对本次上线的影响。当release拉出来后,不用担心其它的东西会合过来,只需要在这上面专注测试和修复bug。
feature
【新功能开发分支】 - 开发新功能时以develop分支为基础建立新的feature分支进行单独开发。当需要此功能的时候,只需要将该 feature
分支合并入 develop
分支,下次一并提测即可。
这样设计可以避免这个功能在尚未开发完成或者通过测试的时候混入发布的版本,而导致不可预知的不稳定。当然也可以同时开启多个 feature
分支进行不同新功能开发,在合适的时候合并提测即可。
hotfix
【线上紧急bug分支】 - 用来修复线上的紧急bug,应由 master
拉出,并在修复完成后合并入 master
和 develop
保证两分支的bug已修复。
具体操作流程可以参考下图:
从这个流程图还是能看出一些问题:
- 场景一:需求A能在最近一个发布窗口发布,需求B不能,则必须在下一个release周期才能把需求B合并到develop分支。
- 场景二: 同时提测的两个功能需求,其中一个已经测试完毕的A功能,另一个没有测试通过被打回的B功能,这时是无法撤回develop上的B,导致A一直等待B
我们团队定制的 Git-flow
缺点:合并动作会重复,冲突需要解决2次(因为feature代码不是从develop推上release,如果在合develop存在的冲突,到了合release分支也会存在冲突,这点可以通过每次有feature分支代码合并到release分支,release合并之后(如有冲突,待冲突解决之后)自动合并到develop,减少冲突解决次数)。需要特别注意的场景有
开始开发release/1.2.0 功能
git checkout -B feature/A master
# 编写一坨代码之后
git add .
git commit -m "feat: 新增购物车功能(#123)"
# 程序员A 接到task2,但发布周期不在release/1.2.0
git checkout -B feature/B master
# 编写一坨代码之后
git add .
git commit -m "feat: 新增图片懒加载功能(#126)"
复制代码
UAT QA测试阶段
- 功能测试通过之后,准备进入QA提测流程
# 主管新增 release/v1.2.0 用于发布
git checkout -B release/v1.2.0 master
# 处理程序员A的代码合并release/1.2.0
git checkout release/1.2.0
git merge feature/A --no-ff release/1.2.0
# 配置`release/*`自动合并到develop
# git branch -D feature/A # 删除特性分支
# 处理程序员B的代码合并到develop,不在本次发版范围
git checkout develop
git merge feature/B --no-ff
复制代码
注意merge分支是否使用 --no-ff
的区别:
准备生产环境流程
git checkout -B master
# 合并这次需要发布的代码分支
git merge release/v1.2.0 --no-ff
# 如果CI模块编写脚本 npm run release, 在master上完成以下操作
npm run test # 运行项目中的单元测试
npm run build # 打包组件
npm version --new-version # 更新版本号
git add & git commit & git push # 更新package.json 的版本号
git tag & git push # 给master 打上此次发布的版本号
# 接下来完成 deploy 任务
复制代码
hotfix流程
修复问题
$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ git commit -a -m "fix: xxxx"
复制代码
合并回release和master进行相关回归测试
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1
复制代码
$ git checkout release/1.2.0
Switched to branch 'release/1.2.0'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
复制代码
不足
:
- 场景一: develop分支合并了某个功能(但后面需求调整毙了,这个feature分支没有合并到某个release分支),因此需要不定期维护 develop 的基
git 基本操作
最后,送上Git的基本操作,再来复习一下。
初始化仓库
# 在当前目录新建一个Git代码库
$ git init
# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]
# 下载一个项目和它的整个代码历史
$ git clone [url]
复制代码
复制代码
# 快速命令
echo "# git-Demo" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin xxx
git push -u origin master
复制代码
配置 Git
Git的设置文件为 .gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。
# 显示当前的Git配置
$ git config --list
# 编辑Git配置文件
$ git config -e [--global]
# 设置提交代码时的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"
复制代码
复制代码
增加删除文件
# 添加指定文件到暂存区
$ git add [file1] [file2] ...
# 添加指定目录到暂存区,包括子目录
$ git add [dir]
# 添加当前目录的所有文件到暂存区
$ git add .
# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
$ git add -p
# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...
# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]
# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]
复制代码
复制代码
代码提交
# 提交暂存区到仓库区
$ git commit -m [message]
# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]
# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a
# 提交时显示所有diff信息
$ git commit -v
# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]
# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...
复制代码
复制代码
# 日常命令
git add .
git commit -m "本次提交的描述"
git pull
git push
复制代码
分支命令
# 列出所有本地分支
$ git branch
# 列出所有远程分支
$ git branch -r
# 列出所有本地分支和远程分支
$ git branch -a
# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]
# 新建一个分支,并切换到该分支
$ git checkout -b [branch]
# 新建一个分支,指向指定commit
$ git branch [branch] [commit]
# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]
# 切换到指定分支,并更新工作区
$ git checkout [branch-name]
# 切换到上一个分支
$ git checkout -
# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]
# 合并指定分支到当前分支
$ git merge [branch]
# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]
# 删除分支
$ git branch -d [branch-name]
# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]
复制代码
复制代码
标签命令
# 列出所有tag
$ git tag
# 新建一个tag在当前commit
$ git tag [tag]
# 新建一个tag在指定commit
$ git tag [tag] [commit]
# 删除本地tag
$ git tag -d [tag]
# 删除远程tag
$ git push origin :refs/tags/[tagName]
# 查看tag信息
$ git show [tag]
# 提交指定tag
$ git push [remote] [tag]
# 提交所有tag
$ git push [remote] --tags
# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]
复制代码
复制代码
查看信息
# 显示有变更的文件
$ git status
# 显示当前分支的版本历史
$ git log
# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat
# 搜索提交历史,根据关键词
$ git log -S [keyword]
# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s
# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature
# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]
# 显示指定文件相关的每一次diff
$ git log -p [file]
# 显示过去5次提交
$ git log -5 --pretty --oneline
# 显示所有提交过的用户,按提交次数排序
$ git shortlog -sn
# 显示指定文件是什么人在什么时间修改过
$ git blame [file]
# 显示暂存区和工作区的差异
$ git diff
# 显示暂存区和上一个commit的差异
$ git diff --cached [file]
# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD
# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]
# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"
# 显示某次提交的元数据和内容变化
$ git show [commit]
# 显示某次提交发生变化的文件
$ git show --name-only [commit]
# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]
# 显示当前分支的最近几次提交
$ git reflog
复制代码
复制代码
远程同步
# 下载远程仓库的所有变动
$ git fetch [remote]
# 显示所有远程仓库
$ git remote -v
# 显示某个远程仓库的信息
$ git remote show [remote]
# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]
# 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]
# 上传本地指定分支到远程仓库
$ git push [remote] [branch]
# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force
# 推送所有分支到远程仓库
$ git push [remote] --all
复制代码
复制代码
撤销
# 恢复暂存区的指定文件到工作区
$ git checkout [file]
# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]
# 恢复暂存区的所有文件到工作区
$ git checkout .
# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]
# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard
# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]
# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]
# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]
# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]
# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop
复制代码
结语
没有最好的,只有最合适,这句话放到团队如何选择git-flow流程一样适用。
拓展
[# A successful Git branching model] (nvie.com/posts/a-suc…)