Git原理简单分析、一些实用技巧与Git Flow介绍。
VCS与DVCS
版本控制系统VCS(Version Control System)
- 版本控制
所谓版本控制,意思就是在文件的修改历程中保留修改历史,让你可以方便地撤销之前对文件的修改操作。类比编辑器的「撤销」功能,其实是文本编辑器帮你自动保存了之前的内容,当你按下「撤销」的时候,它就帮你把内容回退到上一个状态。
- 主动提交
主动提交改动而不是像编辑器的「撤销」功能一样自动保存。程序代码的修改的生命周期非常长。一次代码的修改,在几年后都有可能需要被翻出来,如果自动保存形式来保留修改历史,会导致改动历史非常频繁和无章可循。
- 中央仓库(多人合作的同步需求)
中央仓库解决了代码同步,多人协作的需求。中央仓库作为代码的存储中心:所有人的改动都会上传到这里,所有人都能也都能看到和下载到别人上传的改动。
DVCS分布式版本控制系统
与中央式VCS的区别除了中央仓库之外,团队中每一个成员的机器上都有一份本地仓库,这个仓库里包含了所有的版本历史。在本地机器上就可以提交代码、查看历史,而无需联网和中央仓库交互。保存版本历史的工作转交到了每个团队成员的本地仓库中,中央仓库就只剩下了同步功能。
DVCS与VCS对比
DVCS优点:
- 大多数的操作可以在本地进行,所以速度更快,而且由于无需联网,所以即使不在公司甚至没有在联网,你也可以提交代码、查看历史,从而极大地减小了开发者的网络条件和物理位置的限制。
- 由于可以提交到本地,所以你可以分步提交代码,把代码提交做得更细,而不是一个提交包含很多代码,难以 review 也难以回溯。
DVCS缺点:
- 由于每一个机器都有完整的本地仓库,所以初次获取项目(Git 术语:clone)的时候会比较耗时;
- 由于每个机器都有完整的本地仓库,所以本地占用的存储比中央式 VCS 要高
Git概念与原理
HEAD,branch,master
- HEAD
HEAD
是指向当前 commit
的引用,它具有唯一性,每个仓库中只有一个 HEAD
。在每次提交时它都会自动向前移动到最新的commit
。
- branch
branch
是一类引用。 HEAD
除了直接指向commit
,也可以通过指向某个 branch
来间接指向 commit
。branch
可以理解为从初始 commit
到 branch
所指向的 commit
之间的所有 commit
串。
- master
master
是 Git 中的默认 branch
。
Push
- push
push
做的事是:把当前 branch
的位置(即它指向哪个 commit
)上传到远程仓库,并把它的路径上的 commit
串一并上传。
push
的时候之后上传当前分支,并不会上传 HEAD
;远程仓库的 HEAD
是永远指向默认分支(即 master )的。
将master和feature推送到远程仓库后
Merge
merge
的意思是合并,通过指定一个 commit
,把它合并到当前的 commit
来。
merge操作
从目标 commit
和当前 commit
(即 HEAD
所指向的 commit
)分叉的位置起,把目标 commit
的路径上的所有 commit
的内容一并应用到当前 commit
,然后自动生成一个新的 commit
。
用途
分支合并:git merge branch1
拉取代码:pull
=fetch
(操作把远程仓库取到本地后)+merge
(合并远程最新的commit
)
merge场景
- 冲突(如果两个分支修改的是同一行代码)
此时Git 仓库处于冲突待解决的中间状态,利用ide或者其他修改工具进行冲突修改,手动commit
后进行提交。或者执行一次 merge --abort
来手动取消合并操作。
- HEAD领先于目标commit
merge
时的目标 commit
和 HEAD
处的 commit
并不存在分叉,而是 HEAD
领先于目标 commit
这种情况下merge
是一个空操作。
- HEAD落后于commit
会做一个fast-forward(快速前移),直接把 HEAD
以及它所指向的 branch
移动到目标 commit
Rebase
rebase
作用是在新位置重新提交,因为在 merge
之后,commit
历史就会出现分叉再汇合的结构,会让有些人觉得混乱而难以管理。如果不希望commit
历史出现分叉,可以用 rebase
来代替merge
。
rebase对比merge
- git merge feature
- rebase
# 将feature分支在master分支的HEAD指向的commit位置重新提交
git checkout feature
git rebase master
# 在master分支做一个fast-forward操作把 master的HEAD移到最新的commit
git checkout master
git merge feature
Reset
reset
用来重置 HEAD
以及它所指向的 branch
的位置的。
reset --hard commit
,把 HEAD
和它所指向的 branch
一起移动到了指定commit上。因为git的历史只能往回看,所以把HEAD
和branch
往回移动可以起到撤回commit
的作用。
- –hard
会在重置 HEAD
和 branch
的同时,重置工作目录里的内容。
- –soft
会在重置 HEAD 和 branch时,保留工作目录和暂存区中的内容,并把重置HEAD 所带来的新的差异放进暂存区。
- –mixed
会在重置 HEAD
和 branch
的同时,保留工作目录的内容(所有的差异),并清空暂存区
Checkout与reset
checkout
本质上的功能是签出( checkout )指定的 commit
。git checkout branch
的本质其实是把HEAD 指向指定的 branch ,然后签出这个branch 所对应的 commit 的工作目录。
checkout 和 reset 区别:
reset
在移动 HEAD
时会带着它所指向的branch
一起移动,而 checkout
只会移动HEAD
。
Revert
revert
撤销一个提交的同时会创建一个新的提交。这是一个安全的方法,因为它不会重写提交历史。比如,下面的命令会找出倒数第二个提交,然后创建一个新的提交来撤销这些更改,然后把这个提交加入项目中。
git checkout hotfix
git revert HEAD~2
相比 git reset
,它不会改变现在的提交历史。因此,git revert
可以用在公共分支上,git reset
应该用在私有分支上。你也可以把 git revert
当作撤销已经提交的更改,而 git reset HEAD
用来撤销没有提交的更改。
实用技巧
在本地提交了代码,发现提交了的代码有问题需要修改
这时候我们应该怎么处理,重新写一个修复的commit
吗?或者可以使用commit --amend
命令。
commit --amend
对最新一条 commit
进行修正,会把当前commit
里的内容和暂存区(stageing area)里的内容合并起来后创建一个新的 commit
,用这个新的 commit
把当前commit
替换掉。
需要修改本地的其他commit
通过rebase -i
开启交互式 rebase
,交互式 rebase
,就是在 rebase
的操作执行之前,你可以指定要 rebase
的 commit
串中的每一个 commit
是否需要进一步修改。 例如要修改倒数第二个commit
( git rebase -i HEAD~2
)。
把要修改的commit
操作指令修改为edit
,退出交互式rebase,此时rebase
过程已经停在了倒数第二个 commit 的位置,可以在本地进行修改并add到暂存区,使用commit --amend
修改最新的commit
,修复完成后可以用 rebase --continue
来继续 rebase 过程,把之后的 commit
直接应用上去。
丢弃提交
- 回退到某个commit(丢弃此commit后的所有提交)
git reset --hard``commit
- 丢弃某个commit(基于交互式rebase)
使用 git rebase -i
开启交互式rebase
,删除想要丢弃的commit
,保存退出即可。
- 丢弃某个commit(基于rebase –onto)
rebase --onto
参数
目标commit
、起点 commit
(rebase
的时候会把起点排除在外)、终点 commit
。
例如:git rebase --onto 第5个commit-id 第6个commit-id feature commit-id
丢弃掉feature分支的第6个commit
,把第7个commit
提交到第5个commit
上
合并commit
git rebase -i [startpoint] [endpoint]
使用git rebase -i
开启交互式rebase。其中-i
的意思是--interactive
,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]
则指定了一个编辑区间,如果不指定[endpoint]
,则该区间的终点默认是当前分支HEAD
所指向的commit
(注:该区间指定的是一个前开后闭的区间)。
git rebase -i 36224db
# pick:保留该commit(缩写:p)
# reword:保留该commit,但我需要修改该commit的注释(缩写:r)
# edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
# squash:将该commit和前一个commit合并(缩写:s)
# fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
# exec:执行shell命令(缩写:x)
# drop:我要丢弃该commit(缩写:d)
可以修改为如下把第二次、第三次提交都合并到第一次提交上
pick d2cf1f9 fix: 第一次提交
s 47971f6 fix: 第二次提交
s fb28c8d fix: 第三次提交
保存退出后是注释修改界面,此时对注释进行修改就可以完成一次commit
合并。
Git Flow
Git团队协作下面临的问题
- 如何开始一个Feature的开发,避免Feature之间的影响。
- 分支太多如何管理,如何知道每个分支的用处。
- 哪些分支已经合并回了master。
- 如何进行Release的管理。
- 线上代码出Bug了,如何快速修复。
什么是Git Flow
Git Flow是由Vincent Driessen基于Git这一版本控制系统所总结出来的一套可行的、能够满足很多项目开发需求的敏捷开发流程。
- Master分支
Master分支上是最近发布到生产环境的代码,此分支只能从其他分支合并,不能在这个分支直接修改。
- Develop 分支
主开发分支,包含所有要发布到下一个Release的代码,这个主要用于合并特性分支和Hotfix分支。
- Feature 分支
主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
- Release分支
当需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支
- Hotfix分支
在生产环境发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release
工作流程
- Master分支初始,Develop初始
- feature分支从Develop上拉取,进行特性开发并合并会Develop
- Release分支基于Develop分支创建,打完Release分之后,我们可以在这个Release分支上测试,修改Bug等。测试正常后发布Release分支时,合并Release到Master和Develop, 同时在Master分支上打个Tag记住Release版本号。
- hotfix分支基于Master分支创建,开发完后需要合并回Master和Develop分支,同时在Master上打一个tag。
很多时候标准方案并不是最好的,选择合适的最重要。