git原理及如何选择分支模式
一、git 原理介绍
1.git的四个工作区域
Git有四个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository或Git Directory)、git仓库(Remote Directory)。
2.文件的四种状态
Untracked:未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged.
Staged:暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存,文件状态为Modified;
Mosified:文件已修改, 仅仅是修改, 并没有进行其他的操作.
Committed: 文件已提交修改;
3.git的目录结构:
进入隐藏的 .git 目录之后可以看到如上图所示结构
核心文件:config,objects,HEAD,index,refs 这 5 个文件夹
- config:这里存储项目的一些配置信息,比如是否以 bare 方式初始化,remote 信息等。git remote add 添加的远程分支信息就保存在这里
- objects:这里保存 git 对象,git 中的一些操作以及文件都会以 git 对象形式保存在这里,git 对象分为 BLOB,tree,commit 三种类型,比如 git commit 就是 commit 类型变量,各个版本之间通过版本树进行组织,比如 HEAD 指向某个 commit 对象,而 commit 对象又会指向几个 BLOB 对象或者 tree 对象。objects 文件夹中有很多子文件夹,其中 git 对象保存在以其 sha-1 值的前两位为子文件夹后 38 位为文件名的文件中,此外 git 为了节省存储对象所占用的磁盘空间,会定期对 git 对象进行压缩和打包,其中 pack 文件夹用于存放打包压缩的对象,info 文件夹用于从打包的文件中查找 git 对象
- HEAD:该文件指明了本地的分支结果,如本地分支是 master,那么 HEAD 就指向 master,分支在 refs 中就会表示成
refs:refs/heads/master
- index:该文件 stage 暂存区信息,也就是 add 之后保存到的区域,内容包括它指向的文件的时间戳,文件名,sha1 值等
- refs:该文件夹保存了指向分支的提交对象也就是 commit 对象的指针,其中的 heads 文件夹存储了本地每一个分支最近一次提交的 sha-1 值,即 commit 对象的 sha-1 值,每个分支一个文件;remotes 文件夹则记录你最后一次和远程仓库的通信,也就是说这里会记录最后一次每个分支推送到远端的值;tags 文件夹存储分支别名
4.常用指令及操作
4.1 身份设置
为了告知远程仓库本地的身份,需要对使用者的用户名和邮箱进行配置。具体的配置命令如下:
$ git config --global user.name "rpflo" $ git config --global user.email "rpflo@example.com"
4.1 协作设置
众所周知,linux 平台下的换行和 windows 中是不一样的。所以在这两个平台上的源代码文件在协同合作时可能会导致问题。例如程序员 A 在 linux 下编写代码文件 X 提交至远程仓库,此时换行为 LF。程序员 B 拉取下来之后同样对文件 X 修改保存,修改部分的换行变成了 CRLF。程序员再去拉取 X 完之后,在 linux 中运行的时候可能导致问题。所以在换行的处理上需要做一个全局设置,设置如下。这个命令表示提交代码时自动转换成 LF,而迁出时根据平台进行更改。
$ git config --global core.autocrlf input
4.2 建立仓库
$ git init Initialized empty Git repository in ~/Desktop/my_app/.git/
运行完之后你的文件夹内多出一个.git 隐藏文件夹,隐藏文件夹主要是为了保存本地配置,例如远程仓库的地址,当前的 HEAD 指针。
my_app/
.git/
config
description
HEAD
hooks/
info/
objects/
refs/
熟悉使用 git 命令即可,这里面的东西不需要我们手动进行更改。如果不想使用 git,可以直接删除这个文件,那么他就不再是一个 git 仓库。
4.3 做出修改
新建一个文件 README,写入内容,如果用命令行,类似于这样:
$ echo "I am a git repo." > README
此时,你可以通过 status 命令来查看有哪些文件是未加入仓库(untracked),有哪些文件是做过更改(modified)或删除(deleted)等
git status
4.4 暂存修改
将本地修改进行暂存(staged),以备后续的提交。
git add README
告知 git,下一次代码提交中需要包括此文件。使用以下命令可以将文件移出暂存区。
git reset HEAD <file>
4.5 提交修改
使用 git commit 命令将暂存区的文件提交至本地仓库。
git commit -m [message]
4.6推送远程仓库
1. 设置远程仓库
$ git remote add origin git@github.com:your_name/my_app.git
或者是
$ git remote add origin https://github.com/your_name/my_app.git
2.推送代码至远程仓库
语法是git push <remote> <branch>
。如果本地有多次提交,这个命令会一次性推送至远程仓库
$ git push origin master Counting objects: 3, done. Writing objects: 100% (3/3), 242 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /example/my_app.git * [new branch] master -> master
4.7 拉取远程代码
语法是git pull <remote> <branch>
,这个命令将拉取所有远程仓库中有而本地仓库中没有的提交。
tips: 最佳实践:每次 push 代码之前进行 pull,保证你自己的代码是最新的。
4.8 分支操作
查看分支
git branch
新建本地分支develop
git branch develop
切换分支到develop
git checkout develop
创建分支时基于某个commit点
git branch new_branch_name commit_SHA
分支合并
当我们在不同的分支上进行开发完成后,就可以将两个分支合并。Git提供了两种合并方式:merge和rebase。这里我们以merge为例进行讲解。
首先,我们需要需要切换到需要接受变更的分支上,使用“git merge”命令合并分支。例如,如果需要将“develop”分支合并到“master”分支上:
git checkout master
git merge develop
分支合并时,可能会产生冲突,这时候需要解决冲突后再进行提交。
1、git冲突的场景
情景一:多个分支代码合并到一个分支时;
情景二:多个分支向同一个远端分支推送代码时;
实际上,push操作即是将本地代码merge到远端库分支上。
关于push和pull其实就分别是用本地分支合并到远程分支 和 将远程分支合并到本地分支
所以这两个过程中也可能存在冲突。
git的合并中产生冲突的具体情况:
<1>两个分支中修改了同一个文件(不管什么地方)
<2>两个分支中修改了同一个文件的名称
两个分支中分别修改了不同文件中的部分,不会产生冲突,可以直接将两部分合并。
2、冲突解决方法
情景一:在当前分支上,直接修改冲突代码--->add--->commit。
情景二:在本地当前分支上,修改冲突代码--->add--->commit--->push
注:借用vim或者IDE或者直接找到冲突文件,修改。
4.9 其他命令讲解
1.git fetch 和git pull 的区别
可以简单的概括为:
git fetch
是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中。
而git pull
则是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge
,这样可能会产生冲突,需要手动解决。
2. git cherry-pick
经常需要从一个分支选择性的合并commit到另一个分支,具体可使用cherry-pick实现:
单个commit合并
git cherry-pick commit_id
多个commit
git cherry-pick commit_id commit_idx commit_idy
3. 打tag
切换到需要打tag的分支,然后使用命令:
git tag <name>
4.git diff
比较文件在暂存区和工作区的差异
尚未缓存的改动:git diff 查看已缓存的改动: git diff --cached 查看已缓存的与未缓存的所有改动:git diff HEAD 显示摘要而非整个 diff:git diff --stat # 1、显示暂存区和工作区的差异 $ git diff [file] # 2、显示暂存区和上一次提交(commit)的差异 $ git diff --cached [file] 或 $ git diff --staged [file] # 3、显示两次提交之间的差异 $ git diff [first-branch]...[second-branch]
5. 回滚操作git reset
指定退回某一次提交的版本
命令格式: git reset [--soft | --mixed | --hard] [HEAD] 1、--mixed 为默认,可以不用带该参数,用于重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变。 git reset HEAD 示例: $ git reset HEAD^ # 回退所有内容到上一个版本 $ git reset HEAD^ hello.php # 回退 hello.php 文件的版本到上一个版本 $ git reset 052e # 回退到指定版本 2、--soft 参数用于回退到某个版本 示例: $ git reset --soft HEAD~3 # 回退上上上一个版本 3、--hard 参数撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有信息提交 示例: $ git reset --hard HEAD~3 # 回退上上上一个版本 $ git reset –hard bae128 # 回退到某个版本回退点之前的所有信息。 $ git reset --hard origin/master # 将本地的状态回退到和远程的一样
6. git clone 克隆远程仓库代码到本地
注:选择一个git 客户端可以提高效率,推荐使用TortoiseGit,sourceTree,网上比较多使用教程,可以自行搜索。
二、项目中如何选择分支模式
在项目开发的过程中,选择一个合适的分支模式来管理代码至为重要,那么如何根据这身的业务特点和团队规模来选择合适的分支模式呢?这部分将对几种主流的Git分支模式进行介绍,下边将介绍TBD(主干开发模式)、Git-Flow模式、Github-Flow和Gitlab-Flow模式。
1.TBD(主干开发模式)
TBD,即主干开发模式,所有的开发都在一个开发分支上进行协作开发,只保留一条长期稳定的开发分支,不允许新建任何长期存在的开发分支,任何代码的变更都更新到主干分支上,当需要发布时,建议根据版本号拉一个release分支进行发布,可以通过merge或者cherry pick将代码弄到发布分支上。
TBD模式注意点:
1.因为所有的改动及变更都在主干分支上,所以确保改动足够小,每次的改动都是可控的,能段时间完成验证;
2.每次主干分支上的改动能得到快速验证,有完善的团队协作及自动化测试,随时做好上线的准备,避免引主干上的功能缺陷而影响发布。
因为主干开发要求每次变更提交都要小,并且要快速验证完,保证主干是处在可发布状态。对于一些处在开发过程中的特性,如每次变更提交,并非意味着完整特性的完成,为了隔离“特性半成品”对主干的影响,一般会采用特性开关(Feature Toggle)的方式进行隔离。即频繁的代码变更提交,可以先做集成及验证,但是在发布的角度,通过(Feature Toggle)先隐藏相关特性,只有当特性都完成之后,才打开开关,特性完全透出。
TBD模式优点:
1.分支少,合并冲突小,实践简单;
2.适合持续交付及部署,简单密集需求交付
TBD模式缺点:
1.对团队协作及成熟度合集成测试有很高的要求;
2.不适合开发一些持续时间长的需求及功能复杂的业务;
2.Git-Flow模式
随着敏捷开发的广泛使用,越来越多的团队协作完成某一特性或者分别完成不用的用户故事,根据不同的特性或者用户故事来创建开发分支就应运而生。最有代表性的就是Git-Flow模式。
Git-Flow 模式很好解决了不同特性之间并行开发需要的工作方式。每一个特性都能同时开工,结合敏捷开发的例子,每个迭代开始时从主干分支拉出一个特性分支,命名结构参考feature/xxx-232,所有关于此特性的开发都在此分支上进行,当开发完成后把特性分支合并回主干分支上,测试通过后进行发布。
Git-Flow模式一般有以下分支结构:
- feature分支:开发者进行特性功能开发的分支;
- develop分支:开发主干分支,包含所有的特性功能;
- release分支:版本发布分支;
- master分支:稳定分支,保存最新的已发布代码;
- hotfix分支:线上问题缺陷修复分支;
下面是一些工作流程:
在开发者接到一个开发需求时,从develop分支拉一个feature分支进行开发,最好已ID进行命名,避免重复,为了减少后边合入develop的冲突,最好在开始coding前把develop分支合到feature分支上再进行开发;
当在feature分支完成开发并验证通过后,将feature分支合入develop分支;
develop分支用于集成功能验证,当集成测试成功后将基于develop分支拉一个release版本分支进行发布,如果在release上测试发现bug则在release上修复,之后将代码合入develop,当上线完成后将release合入master分支进行最新上线代码保存;
如果线上发现bug,则基于master拉一条hotfix分支进行修复,修复完成后将hotfix分支合入master进行发布,最后将hotfix代码也同步到develop上。
注意:对一些已完成的feature分支及hotfix分支进行及时删除。
Git-Flow模式的优点
1.特性并行开发,效率高,代码独立;
2.支持复杂业务、大团队协同开发;
3.支持多版本发布;
Git-Flow模式的缺点
1.分支多,合并冲突较为频繁
2.需要进行维护分支,对分支代码进行更新
3.Github-Flow 模式
Github-Flow就是简化版的Git-Flow,更轻量,减少分支。对于 GitHub-Flow 来说,发布应该是持续地,当一个版本准备好,它就可以被部署,feature跟hotfix本质上都是一样的,都属于特性分支,并移除了release分支。
分支情况如下:
在master分支上的代码都是最新的,可部署的;
在特性分支合到master分支时需要发起Pull Request代码评审,评审后方可合入master;
在master上进行持续版本发布。
优点:
1.支持并行开发;
2.分支结构简单,有明确的规则定义,持续集成持续部署
缺点:
1..对测试要求高,一些功能复杂的需求需要持续长的时间验证或者中断则影响整个计划;
2.不能很好的处理一些很紧急的上线需求;
4.Gitlab-Flow模式
GitLab-Flow 相比于 GitHub-Flow 来说,在开发侧的区别不大,只是将 pull request 改成了 merge request,而 merge request 的用法与 pull request 类似,都可以做为代码评审、获取反馈意见的一种沟通方式。
最大的区别体现在发布侧,即引入了对应生产环境的 production 分支和对应预发环境的 pre-production 分支(如果有预发环境的话)。这样,master 分支反映的是部署在集成环境上的代码,pre-production 分支反映的是部署在预发环境的代码,production 分支反映的最新部署在生产环境的代码。
当一个特性开发完成,提交 merge request,将特性开发的代码合并到 master,并部署到集成环境进行验证;当验证通过之后,提交 merge reqeust,合并 master 到 pre-production 分支,并部署到预发环境,进行预发环境上验证;当预发环境验证成功之后,再提交 merge request,将 pre-production 分支上的代码合并到 production 分支上。
三、分支总结
根据每个项目的实际情况的不同选择不同的分支模式:
1.git-flow模式对于开发周期长的项目是比较好的选择,可以很好解决新功能开发,版本发布,线上问题修复等问题;
2.如项目发布周期短,需持续发布维护,功能较为简单,TBD和GitHub-flow是个不错的选择;
3.如果对一些复杂功能的上线前增加一些验证,可选gitlab-flow模式。
还有一些其他的分支策略,比如定义一个主干分支,然后每个成员已自己名字命名的开发分支等等,结合我们的业务需求选择分支策略最为重要。
本文参考:
公众号:阿里技术,作者:张燎原,如何选择Git分支模式?