本文是我使用 Git 一段时间和看过一些资料后的总结,以及个人见解,深感 Git 的规范使用非常重要,不规范的使用会带来很多麻烦。
同类工具比较
SVN与Git
原理上
- Git直接记录文件快照,SVN每次记录哪些文件作了更新、更新哪些行的内容
- Git 有本地仓库和远程仓库,SVN没有本地仓库
- Git 大多数操作是本地执行,SVN操作几乎都需要连接网络
操作上
- Git提交后保存在本地仓库,需要推到远程仓库;SVN提交后在远程仓库
- Git有各种“反悔”命令,SVN几乎没有
- Git有真正的branch,svn的branch其实是工作空间的副本
Git的使用现状
- 意义不明的提交信息
- 糟糕的版本树图
存在的问题
- 合并解决冲突容易出错
- 回滚时难以通过提交信息寻找回滚版本号
- 合并点可能看不出此次合并影响的文件
Git 工作流
Gitflow 工作流
Gitflow 为不同的分支分配一个很明确的角色,并定义分支之间如何和什么时候进行交互。分别有历史分支、功能分支、发布分支和维护分支。
历史分支
使用两个分支来记录项目的历史。
master
分支记录了正式发布的历史,而develop
分支作为功能的集成分支。因此,master
分支的每次提交都应分配一个版本号。
功能分支
功能分支是从develop
中checkout
出来的新分支,每个功能对应一个分支。
1.假设开发a功能:
git checkout -b feature-a develop
2.当新功能完成时,合并回develop分支。
git checkout develop
git merge --no-ff feature-a
git push
git branch -d feature-a
发布分支
1.当develop
分支开发到需要发布时,从develop
分支拉出一个发布分支,命名为release-*
或release/*
。
git checkout -b release-0.1 develop
2.该分支用于发布循环,只做bug修复、文档生成等面向发布的任务。新功能不再添加到这个分支上。
3.一旦发布完成,把发布分支merge
到master
分支上。
git checkout master
git merge --no-ff release-0.1
git push
4.打tag
记录版本号,方便跟踪每次发布。
git tag -a 0.1 -m "release 0.1 publish" master
git push --tags
5.把这些从新建发布分支以来做的修改merge
到develop
分支。
git checkout develop
git merge --no-ff release-0.1
git push
6.最后删除发布分支
git branch -d release-0.1
维护分支/热修复
当线上版本出现bug时,就需要用到维护分支,它用于快速给产品发布版本打补丁。
1.从master
分支拉一个维护分支(这是唯一从master
分支拉出来的分支)。
git checkout -b hotfix master
2.修复完成后,马上合并回master
和develop
。
git checkout master
git merge --no-ff hotfix
git push
git checkout develop
git merge --no-ff hotfix
git push
git branch -d hotfix
3.master
用新版本号打tag
。
git tag -a 0.2 -m "release 0.2 publish" master
git push --tags
图解
图片来自网络优点
- 单个功能独立开发,并行开发互不干扰
master
和develop
分支分别记录发布和功能开发的历史- 由于有发布分支,其他暂不发布的功能的开发不受发布的影响,可以继续提交
- 维护分支能快速打补丁,不影响正在开发的功能
缺点
- 复杂,分支繁多
- Git GUI不支持,纯命令行
- 对开发者要求高(理解工作流,熟悉Git命令)
- 所有功能分支基于不稳定的
develop
- 需要维护两个长期分支
master
和develop
GitHub Flow
GitHub 使用的工作流
- 所有在
master
上的东西都是可发布的(已发布或马上发布) - 开发新功能时,从
master
拉一个名称清晰的新分支 - 在本地提交到这个分支的同时把它
push
到远程仓库 - 当你需要得到反馈或帮助,或者该分支准备
merge
时,打开一个pull request
- 该分支被review且同意合并后,合并到
master
push
到master
后,应该立即发布
优点
- 操作简单
- 主干的代码有质量保证
缺点
- 测试线和正式环境的发布没有区分
Git-Develop
develop
作为固定的持续集成和发布分支
- 每一个功能都从
master
拉一个功能分支。 - 在这个功能分支上开发,功能完成到发布时,提交code review,通过后自动合并到
develop
。 - 待所有计划发布的变更分支代码都合并到
develop
后,rebase master
到develop
,完成发布。 - 应用发布成功后打一个
tag
。 develop
分支的发布版本合并回master
。
优点
- 操作相对简单
- 流程稍作改动,即可区分测试线和正式环境
- 每次开发都基于正式版本最新的代码(
master
),和当前开发的其他分支不产生依赖关系 master
始终是已发布状态
缺点
暂时想不到。。。
Pull Requests
Pull requests
不是一种工作流,而是一个能让开发者更方便地进行协作的功能,可以在提议的修改合并到正式项目之前对修改进行讨论。这种方式对分支的合并有一些限制,例如只有项目维护者有权限合并分支到仓库中。其工作方式:
- 开发者在本地仓库新建一个功能分支。
- 功能完成后,开发者
push
分支修改到远程仓库中。 - 开发者发起
Pull requests
。 - 团队成员收到通知,进行code review,讨论和修改。
- 项目维护者合并功能到仓库中并关闭
Pull Requests
。
Git使用技巧
git commit --amend
git commit --amend
最常见的用法是上次提交信息写错,或提交文件多了或漏了之时,重新提交覆盖上一次提交。
其实它还有一个用法,就是用来合并提交。例如上次提交的修改并不完全,再作修改之后可以用该命令把本次提交与上次提交合并在一起。
git reset
在需要回滚一次或多次提交时,可以用git reset
。由于该命令比较危险,建议用于已经把最新提交推到远程仓库上的本地分支。
- git reset HEAD [filename]
把已在暂时区的文件取消,恢复到已修改未暂存状态。
- git reset HEAD~[n]
git reset
后面可带参数HEAD~[n]
(n >= 1)。表示回退到n
个提交之前。同时,它也可以用来合并提交。下面的写法与git commit --amend
结果是一样的。
git reset HEAD~1
git commit
下面的用法则是合并了多次提交
git reset HEAD~2
git commit
- git reset [version]
git reset
后面也可以带版本号,直接回退到指定版本。
-
git reset的三种参数
- 使用参数
--hard
,如git reset --hard [version]
会执行以下操作:- 替换引用的指向。引用指向新的提交ID。
- 替换暂存区。替换后,暂存区的内容和引用指向的目录树一致。
- 替换工作区。替换后,工作区的内容变得和暂存区一致,也和HEAD所指向的目录树内容相同。
- 使用参数
--soft
,如git reset --soft [version]
会执行上述的操作a。即只更改引用的指向,不改变暂存区和工作区。 - 使用参数
--mixed
或者不使用参数(默认为--mixed
),如git reset [version]
会执行上述的操作a和b。即更改引用的指向及重置暂存区,但是不改变工作区。
- 使用参数
git merge --no-ff
--no-ff
是不快速合并的意思
与git merge的区别
git merge
的结果:
被merge的分支和当前分支在图形上并为一条线,被merge的提交点逐一合并到当前分支。
git merge --no-ff
的结果:
被merge的分支和当前分支不在一条线上,被merge的提交点还在原来的分支上,同时在当前分支上产生一个合并点。
git rebase
git rebase
一般解释为变基
,也有解释为衍合
,个人觉得变基
比较容易理解。
与git merge的区别
git merge
是把两个分支的代码合并到一起,其实git rebase
也是相同的作用,但是表现上是两种不同的形式。
例如现在 dev 提交了一次,master 在此之后也提交了一次,两个分支的状态如下:
- 提交点顺序
可以看出git merge
后,无论加不加--no-ff
参数,提交点的顺序都和提交的时间顺序相同,即 master 的提交在 dev 之后,如图:
而git rebase
后,顺序就变成被rebase
的分支(master)所有提交都在前面,进行rebase
的分支(dev)提交都在被rebase
的分支之后,在同一分支上的提交点仍按时间顺序排列,如图:
- 变基
从上面的图可以看出,dev 在rebase
master 后,分支发生了变化,原本是两个分支,rebase
的结果看起来是: dev 是基于 master 的分支,且产生了一些新提交。
一般来说,rebase
后的 dev 和远程的origin/dev
会发生分离,在命令行界面中会提示:
Your branch and 'origin/dev' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
这时需要用git push -f
强制推送,覆盖远程分支。若使用了提示中的git pull
,结果会变成合并,并产生一个合并提交点。
注:慎用git push -f
!
git pull --rebase
注意git pull
时请加上--rebase
,理由下面会说。
与git pull的区别
在一般情况下,加与不加--rebase
是没有区别的。然而,结合上面说的git rebase
功能,可以知道某个分支可能与其远程分支发生分离,而当你pull
时,你的本地分支还是和原来的远程分支一样,这时如果使用git pull
,则会变成你的本地分支和远程分支合并,正确的做法是git pull --rebase
,才会拉取到最新的分支。
所以推荐在任何时候pull
远程分支,最好加上--rebase
参数。
git reflog
查看提交记录的命令是git log
,而git reflog
的功能是查看本地操作记录,如此一来可以看到本地的commit
, merge
, rebase
等操作记录。
6fe46ab HEAD@{0}: rebase finished: returning to refs/heads/dev
6fe46ab HEAD@{1}: rebase: dev modify a
2c92bcb HEAD@{2}: rebase: checkout master
9b26f5d HEAD@{3}: reset: moving to 9b26f5db1e8597b884c45114fbbff36c440da274
5531fc0 HEAD@{4}: merge master: Merge made by the 'recursive' strategy.
9b26f5d HEAD@{5}: checkout: moving from master to dev
Git工具推荐
- SourceTree
- GUI Clients
参考资料
Pro Git,Git工具书: http://iissnan.com/progit/html/zh/ch1_5.html
Git 工作流指南,介绍了几种主流工作流: https://github.com/xirong/my-git/blob/master/git-workflow-tutorial.md
Gitflow 有害论: http://insights.thoughtworkers.org/gitflow-consider-harmful/
GitHub Flow: http://scottchacon.com/2011/08/31/github-flow.html
Google 的“主干开发”(trunk-based development): http://www.ruanyifeng.com/blog/2016/07/google-monolithic-source-repository.html