0. 参照文档格式
责任编辑中大部分文本源自下列四个文档格式,想深入细致介绍某两个主轴的,提议细细查阅原文档格式。
(详尽):ProGit(英文版)(进阶):Git-Recipes1. 基本原理
1.1 文档状况
已修正(modified):相关联组织工作区(Working directory),主要包括追加和修正的文档;已存贮(staged):相关联存贮区(Staging area);已递交(committed):相关联递交发展史(Git directory)。单纯认知其相关联:
已修正 – add -> 已存贮 – commit -> 已递交 – checkout -> 已修正
1.2 递交与组成部分
递交(Commit)和组成部分(Branch)的亲密关系,能等效二叉树与操作符。Git 中的组成部分,其本质上是个对准 commit 第一类的气门操作符。
如下表所示图右图,递交发展史共计3次递交历史记录,每一场递交能等效为二叉树中的两个结点,结点包涵对准下两个结点(上一场递交)的操作符重要信息。
除此之外除了四个特定的操作符,master和testing是四个组成部分,能等效为四个对准二叉树头的操作符;HEAD是两个特定的操作符,用作命令现阶段组织工作区所处的边线。
如下表所示图右图,是在testing组成部分上组织工作后,展开了一场递交。master依然对准原本的递交,testing对准新一代的递交,而虽然现阶段在testing组成部分上组织工作,HEAD随著testing一同往前终端了一步棋。
如下表所示图右图,假如切换到master组成部分上,因此又展开了一场递交,则master和HEAD一同往前终端,对准87ab2这次递交。
综上所属,组成部分是两个操作符,而不是两个容器。
在 Git 的概念里,组成部分是两个很轻量化的概念,新建、修正和移除组成部分的代价都不大。因此也推荐保持组成部分的短期性,两个组成部分专注于一项特定的任务,即来即走。
1.3 合并(merge)
合并操作的第一类都是组成部分,主要包括:
源组成部分:包涵追加文本的组成部分;目标组成部分:需要并入追加文本,会发生改变的组成部分。合并有两种形式:
快速往前合并(Fast-forward merge)三方合并(Three-way merge)1.3.1 快速往前合并
当目标组成部分是源组成部分的直接上游时,合并时会采用快速往前的方式,并出现“Fast forward”提示。
操作实质:把目标组成部分的操作符直接往前终端到源组成部分的操作符所对准的边线。
如下表所示图右图,包涵master、hotfix、iss53四个组成部分,虽然当前master是hotfix的直接上游,将采用 Fast-forward 的合并方式。
合并结果如下表所示图右图,合并之后master和hotfix组成部分对准同两个递交。此时hotfix组成部分已经完成发展史使命,可以删除了。
1.3.2 三方合并
然后回到iss53组成部分展开开发组织工作,又展开了一场递交后,需要将改动文本合并入master,而此时,这四个组成部分已经分岔了。Git 会用四个组成部分的末端(C4 和 C5)以及它们的共同祖先(C2)展开一场三方合并计算。
Git对三方合并后的结果重新做两个新的快照,并自动创建两个对准它的递交第一类(C6),如下表所示图右图。此时iss53组成部分已经完成发展史使命,也能删除了。
1.3.3 冲突解决
并不是每一场合并操作都非常顺利,有时可能会产生冲突。
<重要原则>:快速往前合并从原理上就不会产生冲突,冲突只会发生在三方合并的时候。
冲突产生的原因:单纯认知,能认为是C4的递交,和C3-C5的递交,修正了文档的同两个部分。Git无法自动合并,必须由人来裁决。
冲突解决的步骤:解决冲突的步骤与展开一场新的递交的步骤几乎一样。(1)修正冲突的代码,(2)将修正过的文档加入缓冲区(add操作),(3)展开一场合并递交(commit操作)。
1.4 衍合(rebase)
1.4.1 基本原理
衍合是除了合并操作以外,另一种把两个组成部分中的修正整合到另两个组成部分中的办法。
回到四个组成部分展开了各自的递交而导致分岔的情况下,如下表所示图右图。
现象这样的场景:
当你在experiment组成部分上组织工作时,有其他人向master组成部分上推送了新的文本。
你想把master中的更新同步到experiment组成部分中,假如采用 merge 操作会产生两个额外的合并递交。
而 rebase 的操作逻辑是:把在 C3 里产生的变化补丁在 C4 的此基础上重新打一遍。操作结果如下表所示图右图。
此时,你的experiment组成部分包涵了master组成部分上的新一代文本,能以此为此基础继续开发。(默认master组成部分上的文本都是稳定的,需要所有人向其兼容的)。
假如需要把experiment组成部分上的文本也并入master组成部分,能执行安全的快速往前合并。结果如下表所示图右图。
1.4.2 重要守则
git rebase是 Git 操作中的黑魔法,用好了能化腐朽为神奇,用不好会带来灾难性后果。
<重要原则>:绝不要在公共的组成部分上采用它!!!
用更白话一点的说法:从分岔点开始往后的递交,假如已经 push 过,那就已经是公共的递交了,这个组成部分就是公共组成部分,必须假设其他人的组织工作会依赖于这些公共递交,也就不能再用 rebase 操作了。
因为衍合的过程改变了组成部分的发展史,原本的C3变成了C3,假如之前C3已经发布到了远程,则在本地变更为C3后,远程组成部分与本地组成部分不一致了,会导致后续的push操作无法展开。
因为在展开push操作的时候,将本地组成部分推送到关联的远程分支时,其本质上也是两个 Fast-forward 模式的合并操作。
1.5 远程服务器
虽然只在本地组织工作也能采用 Git,但为了协作,一般会有两个代码托管的远程服务器,运行两个 Git 服务。
我们的服务器就是基于 Gitea 这个工具搭建的 Git 代码托管服务。
1.5.1 远程组成部分和本地组成部分
其本质上,每两个 Git 库的副本,都有自己的一套组成部分,而不同副本之间的组成部分能展开关联追踪。
在本地执行git branch -a,有如下表所示显示(部分删节):
前四个是本地组成部分,带remotes前缀的是远程组成部分,origin代表服务器名。
执行git branch -vv查阅组成部分的追踪亲密关系,有如下表所示显示(部分删节):
一些有效但不那么准确的认知:
远程组成部分和本地组成部分是不同的组成部分;具有追踪亲密关系的远程/本地组成部分之间,通过 pull/push 操作展开同步,合并方式限定为Fast-forward。2. Git 组织工作流
采用两个简化的Git Flow组织工作流。
2.1 Git 组成部分类型
将会采用master、develop、feature三种组成部分,暂时不采用hotfix、release组成部分。
master组成部分:仓库的主组成部分,包涵最近发布的可稳定采用的版本;一般由仓库管理员从develop组成部分展开合并,不能直接向master展开推送;master组成部分始终存在,不可删除。master的每两个 commit 都应该打上标签,作为对外发布的版本号。develop组成部分:主要开发组成部分,基于master创建,始终包涵新一代完成功能的代码以及bug修复后的代码;接受从feature发起的合并请求,不能直接向develop展开推送;develop组成部分始终存在,不可删除。feature组成部分:基于develop创建,用作某两个特定的新功能/新特性开发;feature组成部分可同时存在很多个,用作多个功能同时开发;feature组成部分属于临时组成部分,当合并入develop并。因此,每一场develop向master组成部分的合并,都应该是 Fast-forward 模式的。
feature基于develop创建,并最终合并入develop,在此过程中,develop可能会合并入其他feature组成部分的文本,如何保证递交PR时不产生合并冲突,在下一节详尽讨论。2.2 基本原则
几项 Git 协作的基本原则:
重要:在远程服务器上,只会展开 Fast-forward 模式的合并,因此是通过 Pull-Request 展开。在递交 PR 之前,需要确认目标组成部分(一般是develop)是源组成部分(一般是feature)的直接上游。能通过递交图展开确认。
重要:所有的冲突都在本地解决,在远程没有解决冲突的途径。 保持每一场 Commit 有明确的意义,必要时通过交互式的 rebase 操作,对 Commit 展开整理。 保证组成部分命名规范且有意义,保证 commit-message 包涵简明且充分的重要信息。保持合适的推送频率(Push 操作,非 Commit 操作) 针对正在执行的任务,假如3天以上都没有Push,无法让其他人同步进度,则说明递交频率过低;假如在两个时段内连续提交,疯狂刷屏,则说明推送频率过高。
提议针对某两个具体的开发任务,一天推送1至2次是两个合适的频率;能在每天组织工作结束前,整理当天的 Commit,并执行一场 Push 操作。2.3 典型组织工作流程
2.3.1 仓库初始化
2.3.2 合并流程
本地feature_0组成部分有了四个新的递交(C3&C4),在此同时,远程的origin/develop接受了feature_1组成部分的PR,往前推进到了C5。如下表所示图右图:
不合理的做法:
此时,假如直接将本地的feature_0组成部分推送到远程,则origin/develop和origin/feature_0处于分岔的状况。如下表所示图右图:
按照一般 PR 的规则,无法将origin/feature_0合并入origin/develop,因为目标组成部分并不是源组成部分的直接上游。假如强行合并,若刚好四个组成部分没有修正过同两个文档,则能侥幸合并成功。如下表所示图右图:
但并不推荐这么做,因为违反了 PR 只做 Fast-forward 操作的原则。且很多时候会产生冲突,在网页上无法处理。
合理的做法:
在推送feature_0组成部分之前,先将本地的develop组成部分和远程的origin/develop展开同步(通过git pull操作)。如下表所示图右图:
在本地展开衍合操作,将feature_0组成部分的递交,更新到develop组成部分之前。
假如发生冲突,则按照命令行的提示手动解决冲突。正确 rebase 后的结果,如下表所示图所示:
在此此基础上,本地组成部分继续开发,又往前推进了一场递交,然后同步到远程的origin/feature_0组成部分上。此时,origin/develop是origin/feature_0的直接上游,能实现 Fast-forward 模式的合并。(假设在此期间origin/develop没有再接受新的 PR,如有,则重复上面的流程)。结果如下表所示图右图:
2.3.3 衍合的注意事项
回到最初分岔的阶段,如下表所示图所示:
假设已经将feature_0组成部分推送到远程,与origin/feature_0同步过一场。
若此时想基于origin/develop新一代递交的此基础,展开继续开发,先将更新拉取到本地,如下表所示图右图:
在本地执行之前一样的衍合操作,将feature_0组成部分添加到develop组成部分的头部,会发现本地的feature_0和远程的origin/feature_0不一致了,此时无法再执行 Push 操作,因为本地和远程发生了冲突。如下表所示图右图:
假如已经将递交推送至远程,有两种后续的解决方案:
(1)通过 merge 而非 rebase 展开合并,在本地执行三方合并
同时要求在本地解决可能的合并冲突。结果如下表所示图右图:
此时将feature_0推送到远程,与origin/feature_0展开同步。这时,origin/develop是origin/feature_0的直接上游,符合 PR 的规则,能正常递交合并请求。结果如下表所示图右图:
(2)假如本地已经采用了 rebase 操作展开合并,能强制推送展开同步
强制推送git push -f是另两个比较危险的操作。
<重要原则>:一定只在自己的组织工作组成部分上采用!!!一定不能向其他人的组织工作组成部分执行强制推送!!!
除非万不得已,不要采用强制推送。在推送前,需要反复确认本地组成部分包涵了所有需要的组织工作文本。
推送后,远程组成部分和本地组成部分已经一致,因此origin/develop是origin/feature_0的直接上游。结果如下表所示图右图:
2.3.4 再谈合并与衍合
git rebase 和git merge 做的事其实是一样的,它们都被设计来将两个组成部分的更改并入另两个组成部分,只是实现方式不同,最终的文档结果是一致的。
git merge的好处是,不会对发展史展开任何更改,是安全的;缺点是,每次合并都会引入两个额外的递交(比如上图的C6),假如上游组成部分非常活跃,一定程度会污染本地的开发组成部分,形成两个非常复杂的开发发展史。
git rebase的好处是,开发发展史会非常整洁和线性,没有不必要的合并递交;缺点是,这个操作包涵一定的风险性,会变更开发发展史,所以必须严格遵守衍合的操作守则——绝不要在公共的组成部分上采用它!!!任何衍合操作都不应该更改已经同步到远程服务器的递交。
git rebase的两个额外的好处是,能用来清理递交发展史,让发展史中的每一场递交都包涵明确的开发意义,便于回顾和追溯。 这意味着,在开发过程中,本地能展开相对随意的 Commit,在整体 Push 之前,通过git rebase -i对开发发展史展开整理,合并部分 Commit,修正 commit-message。2.3.5 推荐的操作方法
综上所述,两个推荐的基于git协作的开发流程:
将本地的develop组成部分与远程的origin/develop展开同步,基于新一代的develop,新建本地的开发组成部分feature;在feature组成部分上组织工作,正常展开git commit操作;(可选)开发过程中关注origin/develop的更新,假如更新的文本与feature开发的文本相关,则拉取到本地,通过git rebase操作,合并入feature;在执行git push前,如有必要,通过git rebase -i对递交发展史展开整理,注意只清理还未推送到远程的递交;在执行git push前,必须确认origin/develop的状况,执行一场git pull操作,假如有更新,则拉取到本地,通过git rebase操作,合并入feature;确认feature在合并origin/develop的新一代更新后,依然能够按照预想的方式正常运行,假如有问题则修正;执行git push操作,将feature推送到远程,与origin/feature展开同步。3. Git配置
在采用 Git 前,对其展开有效的配置,可以达到事半功倍的效果。
3.1 配置层级
Git 的配置包涵四个层级,每一级别的配置会覆盖上层的相同配置:
系统配置:/etc/gitconfig文档,设置时配合–system选项; 用户配置:~/.gitconfig文档,设置时配合–global选项; 项目配置:./git/config文档。3.2 必须配置
设置名字和邮箱,用作识别用户,会历史记录到每一场commit的重要信息中。
3.3 提议配置
3.3.1 文本编辑器
3.3.2 commit-message模板
采用 commit-message 模板能减少每次 commit 操作时需要固定输入的文本。
设置模板文档:
在$HOME/.gitmessage.txt中或新建任意文责任编辑档,写入需要的模板;
启用模板文档:
3.3.3 自动换行符
Windows采用回车和换行四个字符来结束一行(CRLF),而Mac和Linux只采用换行两个字符(LF)。
4. 提议
采用命令行操作,减少对 IDE 内置工具的依赖。
掌握命令行操作,能够让你更好地认知 Git 的组织工作原理;IDE 简化了操作,但也增加了误操作的可能。