Git原理简单分析、一些实用技巧与Git Flow介绍。

VCS与DVCS

版本控制系统VCS(Version Control System)

  • 版本控制

所谓版本控制,意思就是在文件的修改历程中保留修改历史,让你可以方便地撤销之前对文件的修改操作。类比编辑器的「撤销」功能,其实是文本编辑器帮你自动保存了之前的内容,当你按下「撤销」的时候,它就帮你把内容回退到上一个状态。

  • 主动提交

主动提交改动而不是像编辑器的「撤销」功能一样自动保存。程序代码的修改的生命周期非常长。一次代码的修改,在几年后都有可能需要被翻出来,如果自动保存形式来保留修改历史,会导致改动历史非常频繁和无章可循。

  • 中央仓库(多人合作的同步需求)

中央仓库解决了代码同步,多人协作的需求。中央仓库作为代码的存储中心:所有人的改动都会上传到这里,所有人都能也都能看到和下载到别人上传的改动。

image

DVCS分布式版本控制系统

与中央式VCS的区别除了中央仓库之外,团队中每一个成员的机器上都有一份本地仓库,这个仓库里包含了所有的版本历史。在本地机器上就可以提交代码、查看历史,而无需联网和中央仓库交互。保存版本历史的工作转交到了每个团队成员的本地仓库中,中央仓库就只剩下了同步功能。

image

DVCS与VCS对比

DVCS优点:

  1. 大多数的操作可以在本地进行,所以速度更快,而且由于无需联网,所以即使不在公司甚至没有在联网,你也可以提交代码、查看历史,从而极大地减小了开发者的网络条件和物理位置的限制。
  2. 由于可以提交到本地,所以你可以分步提交代码,把代码提交做得更细,而不是一个提交包含很多代码,难以 review 也难以回溯。

DVCS缺点:

  1. 由于每一个机器都有完整的本地仓库,所以初次获取项目(Git 术语:clone)的时候会比较耗时;
  2. 由于每个机器都有完整的本地仓库,所以本地占用的存储比中央式 VCS 要高

Git概念与原理

HEAD,branch,master

  • HEAD

HEAD是指向当前 commit的引用,它具有唯一性,每个仓库中只有一个 HEAD。在每次提交时它都会自动向前移动到最新的commit

  • branch

branch是一类引用。 HEAD除了直接指向commit,也可以通过指向某个 branch来间接指向 commitbranch可以理解为从初始 commitbranch所指向的 commit之间的所有 commit串。

  • master

master是 Git 中的默认 branch

image

Push

  • push

push 做的事是:把当前 branch 的位置(即它指向哪个 commit )上传到远程仓库,并把它的路径上的 commit 串一并上传。

push 的时候之后上传当前分支,并不会上传 HEAD ;远程仓库的 HEAD 是永远指向默认分支(即 master )的。

image

将master和feature推送到远程仓库后

image

Merge

merge的意思是合并,通过指定一个 commit ,把它合并到当前的 commit来。

merge操作

从目标 commit 和当前 commit (即 HEAD 所指向的 commit)分叉的位置起,把目标 commit 的路径上的所有 commit 的内容一并应用到当前 commit,然后自动生成一个新的 commit

用途

分支合并:git merge branch1

拉取代码:pull=fetch(操作把远程仓库取到本地后)+merge(合并远程最新的commit

merge场景

  1. 冲突(如果两个分支修改的是同一行代码)

此时Git 仓库处于冲突待解决的中间状态,利用ide或者其他修改工具进行冲突修改,手动commit后进行提交。或者执行一次 merge --abort 来手动取消合并操作。

  1. HEAD领先于目标commit

merge 时的目标 commitHEAD 处的 commit 并不存在分叉,而是 HEAD 领先于目标 commit

image

这种情况下merge 是一个空操作。

  1. HEAD落后于commit

image

会做一个fast-forward(快速前移),直接把 HEAD以及它所指向的 branch移动到目标 commit

image

Rebase

rebase作用是在新位置重新提交,因为在 merge 之后,commit历史就会出现分叉再汇合的结构,会让有些人觉得混乱而难以管理。如果不希望commit 历史出现分叉,可以用 rebase来代替merge

rebase对比merge

image

  1. git merge feature

image

  1. rebase
# 将feature分支在master分支的HEAD指向的commit位置重新提交
git checkout feature
git rebase master
# 在master分支做一个fast-forward操作把 master的HEAD移到最新的commit
git checkout master
git merge feature

image

Reset

reset 用来重置 HEAD 以及它所指向的 branch 的位置的。

reset --hard commit,把 HEAD 和它所指向的 branch 一起移动到了指定commit上。因为git的历史只能往回看,所以把HEADbranch往回移动可以起到撤回commit的作用。

  • –hard

会在重置 HEADbranch 的同时,重置工作目录里的内容。

  • –soft

会在重置 HEAD 和 branch时,保留工作目录和暂存区中的内容,并把重置HEAD 所带来的新的差异放进暂存区。

  • –mixed

会在重置 HEADbranch 的同时,保留工作目录的内容(所有的差异),并清空暂存区

Checkout与reset

checkout 本质上的功能是签出( checkout )指定的 commitgit 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替换掉。

image

需要修改本地的其他commit

通过rebase -i 开启交互式 rebase,交互式 rebase,就是在 rebase 的操作执行之前,你可以指定要 rebasecommit 串中的每一个 commit 是否需要进一步修改。 例如要修改倒数第二个commit git rebase -i HEAD~2)。

image

把要修改的commit 操作指令修改为edit,退出交互式rebase,此时rebase 过程已经停在了倒数第二个 commit 的位置,可以在本地进行修改并add到暂存区,使用commit --amend 修改最新的commit,修复完成后可以用 rebase --continue来继续 rebase 过程,把之后的 commit 直接应用上去。

image

丢弃提交

  1. 回退到某个commit(丢弃此commit后的所有提交)

git reset --hard``commit

  1. 丢弃某个commit(基于交互式rebase)

使用 git rebase -i 开启交互式rebase,删除想要丢弃的commit,保存退出即可。

image

  1. 丢弃某个commit(基于rebase –onto)

rebase --onto参数  目标commit 、起点 commitrebase 的时候会把起点排除在外)、终点 commit 。 例如:git rebase --onto 第5个commit-id 第6个commit-id feature commit-id

丢弃掉feature分支的第6个commit ,把第7个commit提交到第5个commit

image

合并commit

git rebase -i  [startpoint]  [endpoint]

使用git rebase -i 开启交互式rebase。其中-i的意思是--interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]则指定了一个编辑区间,如果不指定[endpoint],则该区间的终点默认是当前分支HEAD所指向的commit(注:该区间指定的是一个前开后闭的区间)。

git rebase -i 36224db

image

# 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团队协作下面临的问题

  1. 如何开始一个Feature的开发,避免Feature之间的影响。
  2. 分支太多如何管理,如何知道每个分支的用处。
  3. 哪些分支已经合并回了master。
  4. 如何进行Release的管理。
  5. 线上代码出Bug了,如何快速修复。

什么是Git Flow

Git Flow是由Vincent Driessen基于Git这一版本控制系统所总结出来的一套可行的、能够满足很多项目开发需求的敏捷开发流程。

image

  • 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初始

image

  • feature分支从Develop上拉取,进行特性开发并合并会Develop

image

  • Release分支基于Develop分支创建,打完Release分之后,我们可以在这个Release分支上测试,修改Bug等。测试正常后发布Release分支时,合并Release到Master和Develop, 同时在Master分支上打个Tag记住Release版本号。

image

  • hotfix分支基于Master分支创建,开发完后需要合并回Master和Develop分支,同时在Master上打一个tag。

image

很多时候标准方案并不是最好的,选择合适的最重要。