Quoting Linus: "I'm an egotistical bastard, and I name all my projects after myself. First 'Linux', now 'Git'".('git' is British slang for "pig headed, think they are always correct, argumentative").
All objects are named by their content, which is
approximated by the SHA1 hash of the object itself. Objects may refer
to other objects (by referencing their SHA1 hash), and so you can build
up a hierarchy of objects.
第三点:A "blob" object is nothing but a binary blob of data, and doesn't
refer to anything else. 简单点说就是: blob 没有任何其他属性,仅仅表示文件的内容。
The "current directory cache" is a simple binary file, which contains an efficient representation of a virtual directory content at some random
time. It does so by a simple array that associates a set of names,
dates, permissions and content (aka "blob") objects together. The cache
is always kept ordered by name, and names are unique at any point in
time, but the cache has no long-term meaning, and can be partially
updated at any time.
序
前半部分属于基础,后半部分属于进阶。从初级到中级再到我都
hold
不住的高级。全文共12000
余字,超干超干的那种。 然而,写完一半的时候,我突然虎躯一震,我是不是在造轮子?随后我悄悄的搜了一下git
。 嗯?这么多git
文章,我滴天呢,我陷入了沉思,我皱着眉头点了几篇文章,有号称封山之作的2万字真理,也有完整详细的git
系列教程。好像有点轮子啊,但是我继续看了下他们的内容后,有种茅厕顿悟般的惊喜,因为我发现我的文章还是很独树一帜的。最后得出一个结论,我没有造轮子,这是一篇高可用高扩展高性能的git
和gerrit
文章。用实战去推动思考,kill
掉大众化的git
知识,从常用的角度去扩展深层的知识,进而抽象出我们可以理解掌握的git
奥秘。不拘泥于API
,不畏惧其他轮子,不要怂,就是干。本文是站在别人的
commit
上去merge
和patch
我自己独具特色的理解,从而生成一个更好的commit
,然后留给大家日后更好的merge
和patch
,技术在一次次patch
中不断进步。开门见山
在实际项目开发中,能灵活的使用
git
和gerrit
是一个很重要的事情,一方面可以提高团队开发效率,另一方面可以把控项目的代码质量。不过对于gerrit
, 可能一些同学没有用过,还有git
的一些你可能没有掌握的技巧,今天就一起分享给大家,分享的同时,自己也有很多即时收获。为什么会出现 git
这里我们用
git
, 我们就应该去了解一下git
出现的背景,具体故事不说了,自行维基。这里我简单说一下git
的出现,在技术层面上的背景。git
是一个开源的 分布式版本控制系统 ,源码也在github
上开源了,可以自行搜索。提到分布式版本控制系统,那应该联想到 集中式版本控制系统 ,具有代表性的比如SVN
,SVN
的全称是Subversion
。SVN:
GIT:
从图中基本可以分析出两者的主要区别。比如:
git
可以离线开发,svn
不能离线git
处理merge
的优势碾压svn
当然其他的区别还有很多,比如
git
的内容更完整,使用了SHA-1
算法,git
可以更加灵活的操作branch
。 等等,这里就不造轮子了,参考下面这篇博客:看到这里,我们可能比较偏向于,
SVN
比Git
差一些的观点,但其实这是两种不同的版本控制系统,不能简单的认为谁好谁坏,这里有一篇为SVN
洗白的博客,挺有趣的,大家可以看看:要是高度总结一下,那就是: SVN 更适用于项目管理, Git 更适用于代码管理。
为什么会出现 gerrit
我们看维基介绍:
言简意赅:从维基上可知,
Gerrit
是一种开源的代码审查软件,专门用来CR
的。版本控制系统
版本控制系统的三板斧
这里说一下版本控制系统的三板斧:
第一板斧: 存储内容
第二板斧: 跟踪内容更改
第三板斧: 与协作者分发内容和历史记录
版本控制系统发展史
现在和将来的前端,一定是和
git
打交道最多的行业之一,上面提到了版本控制系统,那为了扩展版本控制的知识,我们有必要去了解一下版本控制系统的发展历史,历史大致是这样的:博主大佬:Vamei
这篇文章简直把版本控制系统的整个历史解释的堪称完美,从最开始的个人手工
copy
压缩打包,到后面的通过diff
出patch
( 也就是我们常说的 补丁 ),然后通过邮件进行传达patch
。然后继续说了rcs
cvs
svn
git
在说到git
时,解释之精妙,令人佩服。和三国志不同,
VCS
的三国还没有决出最终胜负,或许SVN
会继续在一些重要项目上发挥作用,但是git
最终会一统江山,至少会一统前端江湖。git 和 gerrit 命名的由来
有时候,我们可能对为什么叫
git
、gerrit
不怎么在意。但是很多命名都是有自己的故事的,比如vue
,react
为什么这样命名。可以去查阅资料了解一下,这有助于我们更形象化的理解它们。git 的命名
比如说,
git
一词的由来,可以从维基百科上的一段话可以看出:翻译一下就是:我是一个自负的混蛋,我把自己的所有项目命名为自己。首先是 "Linux" ,现在是 "Git" 。(
git
在英国俚语中是猪头,认为他们总是正确的,有争议的 )。再举个例子,比如
MySQL
中的My
并不是 我的 的意识。MySQL 的命名由来是这样的,维基上有介绍:gerrit 的命名
由于已经说了
git
的命名由来了,这里我就言简意赅点,gerrit
的命名来自于荷兰设计师赫里特·里特费尔德(Gerrit Rietveld
) 的名字首个单词。为什么要用 git
直觉上,我们自然而然的用了,发现也很好用。那我们可以问一下自己,
git
为什么很好用,如果我们看了上面提到的博客,可能我们已经有了答案,其实很多很棒的东西的诞生,都是在诞生的某个维度背景下,解决了大部分同类没有解决掉的痛点。所以现在我们用了
git
,我们也觉得很好用,但是事实上我们好像并不清楚git
的出现,解决了什么样的痛点,我们只知道好用。我说这句话,就是想说明一下,去了解一个东西,最好去了解这个东西诞生时所处的时代背景或者技术背景。哎,好像我没有回答为什么要用git
? 不慌,问题不大,其实答案已经在前面提到了。谈谈 git-flow 流程
网上有很多
git-flow
开发流程的博客,这里不进行讲解了,但是我想讲的就是:如何去制定一个好的 git-flow
目前的代码托管平台主要有:
github
、gitlab
、Coding
、 码云 。 这是我知道的主流的代码托管平台( 排除bitbucket
,因为国内用的不多)。由于最近github
允许个人开发也可以建立私有仓库,那也就说明这四个代码托管平台都可以免费建立私有仓库了,这算是一个重要时刻吧。一个好的开发模式,可以提高团队的开发效率,同时提高团队的代码质量。 ( 这不是废话吗,手动滑稽 )
我们上面提到的,不管是
svn
还是git
, 都是为了优化现有的开发模式。那么,如何去按照本项目的特点去制定属于本项目style
的git-flow
呢?下面我会分享一些我自己的看法。项目背景
目前参与一个前端开发者达到几十人的一个大型项目,使用的是
git
版本控制。本人负责给项目加上gerrit
和 帮助其他开发者平稳过渡到gerrit
开发模式中,说通俗点就是:熊和鱼掌不可兼得
根据我的经验,如果要提高团队的代码质量,那一定会降低团队的开发效率,也就是在平均时间内,工作产出会降低。
拿
V8
引擎来说,V8
对JS
代码的优化,并没有一网打尽似的全部采用JIT
编译器 进行优化,而是针对性对一部分代码使用JIT
优化,对另一部分代码直接生成本地代码。优化的越好,就意味着需要的分析和生成代码的时间就越长。 对
C++
这种编译型,等待的时间长一点可以接受,但是对于JavaScript
来说,哪怕是200ms
,那对于用户体验来说,都是一个考验。就是不能一味的追求质量,而是要把质量和效率结合在一起,去达到一种最优解。
我个人认为,网上标准的
git-flow
模式 对于那些开源的项目可能比较适合,或者公司内部很重要的项目合适,其实git
诞生背景,主要就是为了让开源的代码版本控制变得更强大。github
的出现,让git
变得非常流行。我们看一下网上那一套标准的git-flow
模式,如下图:首先,完全没有必要这么复杂,各位小伙伴不要被这种博客吓到了,吓到都不敢用
git
。虽然上图的git-flow
模式可以说是使用git
进行版本控制的best practice
。但是我认为这并不适合大部分的业务项目。我觉得应该是:在分析项目的时间,和迭代速度后,做出一个既可以控制代码质量和版本管理,又可以让开发过程变的不那么繁琐,从而保证一定的开发效率。这才是一个比较好的
git-flow
,怎么舒服怎么来,自行脑补。
所以当你想学习
git-flow
模式开发的时候,然后去网上搜了一下博客,发现git-flow
模式有点抽象。这个时候请不要害怕,我不认为这种标准抽象的git-flow
就是属于你现在项目的git-flow
。比如通过几个关键性的
branch
来对版本的生命周期进行精细控制,通过branch
来分割各个生命周期的代码,保证版本的各个生命周期代码的纯洁性。举个例子:下个版本的代码,你也开发一半了,那这些代码就不能出现在现在版本的线上代码中,纯洁性就可以这样理解。
比起要学会如何使用
git-flow
, 我们更应该去体会一个很棒的版本控制系统 的解决方法,其背后的思想。当深入理解了思想,那后面用其他版本控制系统的软件,也能游刃有余了。在大型项目中 git-flow 怎么实施
这里以我目前参与的一个大型项目作为例子,说一下如何在实践中,总结出属于本项目的
git-flow
流程。这里介绍一下项目的分支结构,没有所谓的
feature
分支,有develop
分支,但也是简写成dev
( 怎么方便怎么来 )。一个是充当
feature
分支,一个是充当develop
分支。当要发布一个新的版本的时候,就从dev
上切一个dev-xx
系列的分支,用来发布一个版本。嗯,就是这么简单直接。项目代码是托管到内部的
gitlab
上的,项目一开始的时候,是没有CR
的。所有开发者都可以向dev
分支上提交代码。是为了提高开发效率,因为项目处于一个急速开发的阶段,如果太注重质量上的保证的话,就会增加人力成本,降低开发效率,最后和急速开发背道而驰,这也算是符合那句俗语:过早的优化就是地狱。
比如,同事进行了错误的操作,导致代码缺失。我说一下我这边遇到的一个经典案例 ( 简要说一下这一部大片 ) 就是:
你 ( 代表一个同事 ) 在
merge
的时候处理不当,然后成功的把其他同事的很多代码搞没了,但是你并不知情,以为自己操作是对的,然后提交代码到dev
分支 。而此时,commit
时间线又在持续的往前走,走了好久,你才发现,然后突然at
全体人员,然后我们就懂了。然后当你发现的时候,你果断的想自己去处理这个问题,但是你没有考虑到全面,只想到用SourceTree
将代码回滚到merge
错误的索引处,但是你又不小心点错分支了,将dev
分支代码回滚到了上个版本。于是,远端dev
分支,从上个版本到现在这个版本的代码都没了,记录也没了...merge
到另一个分支时,处理不当。commit
,然后你懂的。reset
这种可怕的命令,去操作其他coder
的commit
。reset
错分支了, 导致一个大版本的代码被干掉了,远端记录都没了。第一种: 通过将此次分支回滚到 到
merge
错误之前的commit
。 然后将在错误后面继续提交的那些commit
挨个加进去。这种方式有个问题,由于远端记录都没了,导致只能依靠有相对完整记录的某个开发来做这件事,但是谁也不能保证这个记录就是完整的。第二种: 留给各个产品线自己去认领,自己解决自己的代码丢失,哪里丢失,补哪里,采用责任制。
通过讨论,采用了第二种方案。
第一:远端记录都没了,这点很伤。
第二:相信某个开发的本地记录是不可靠的,最后还得让各个产品线去
CR
自己的代码,看有没有修复完整。这次事故也充分证明了,在提高开发效率的同时,如果不去合理的限制权限,那么在将来就可能会出现你不想看到的事故。
对于这个问题,我个人的观点是这样的:
上
gerrit
就意味着操作复杂度的增加和人力成本的增加,比如对于一个APP
级别的项目,需要腾出更多的人力去CR
。而一般项目刚开始的时候,人力都是紧张的,那么这样做无疑是增加了项目成本。如果大家能通过个人技术素养,保证不会出现代码问题,那就可以先不上CR
机制。在没有上的情况下,项目迭代了很多版本,也没有出现任何版本控制上的问题,从这点也可以说明,有些优化不一定要从一开始就上,还是要结合实际情况去制定符合自己的一套rule
。 但是随着人数越来越多,出错的概率大大增加,然后就出错了(滑稽脸),出错了怎么办,那就上CR
机制吧。CR
机制怎么上,如何去CR
一个APP
级别 ( 参与开发达到几十的规模 ) 的项目,可以继续往下看,下面有专门介绍gerrit
的知识。git 中级 之 git 理论知识 和 git 实战技巧
上面大致是
git
的科普,还有对项目开发过程中遇到的问题的一些思考。我把上面的部分称为git
初级。如果你能够灵活运用
git
知识去解决版本控制过程中的各种问题,那就可以说你是属于中级水平了。我是用的命令行形式去进行
git
操作的,当然也有很多人是用的SourceTree
VsCode
WebStorm
这种软件去操作git
。 不过每个人应该都有主次之分,比如我,就主用命令行,VsCode
我也用。除了我需要去阅读文件,对比文件前后版本,或者查看多个历史版本时,我会用
VsCode
外, 其他操作都统一用命令行解决。PS: 用命令行玩转
git
的话,那基本的linux
知识还是要掌握的,如果有兴趣可以去学学linux
。 推荐书籍:因为生命不止,学习不止。
git 中级之理论知识
很多人只是在记
git
的命令操作,并不清楚这样做的底层原因,从而导致了 知其然不知其所以然,最后就没有办法在一个大的知识层面上对git
进行一个更为抽象和深刻的理解。下面我会站在别人的肩膀上( 不重复劳动 ),根据我所学习的
git
知识来简要分析一下git
的一些中级理论知识。然后我再展示大佬
Vamei
的两张git
分析图( 图 加 文字分析 ):上面三张图分别是一张
API
级别的 图 和 大佬Vamei
的两张git
原理分析图。如果对上面的三张图理解深刻的话,是能从图中就能感受到
git
的设计思想和与众不同的特点。如果能理解深刻,那其实也可以说你已经掌握了中级的理论知识了。git init 干了什么
要想知道
git init
干了什么,我们就要去执行git init
, 然后去分析执行前后的具体变化。我们从新建目录开始,然后初始化一个全新的
git
仓库,具体执行的代码如下:git init
执行完后,如图所示 从上图中,我们能看到执行git init
命令后,在当前目录下新建了一个.git
目录,我们再通过ls -l
可以看到.git
目录下的所有文件和目录,同时包括这些文件和目录的权限。第一个: 在
0112-git-test
空目录下进执行了git init
后,生成的.git
目录下的objects
和refs
目录和他们的子目录都是空目录,很纯洁。第二个:
.git
目录下的HEAD
文件中写了一行代码ref: refs/heads/master
, 我们按照这个路径去找,却发现在refs/heads
目录下并没有master
。我们按照上面分析的步骤去分析非空目录下进行
git init
的操作,会发现.git
目录下没有任何变化。经过两次分析,我们可以看到,在进行
git init
后,不管当前目录有没有文件,.git
目录都是一样的,同时HEAD
默认是指向master
分支,看下图:为什么会
git init
后默认指向master
, 通过上面简单的操作,我们就可以从中级层面去理解这个事情了。我们看箭头处,会发现这个文件是
untrack
,我们结合git init
命令前后的.git
并没有发生任何变化。1.txt
没有被纳入到版本控制系统中,untrack
就代表没有纳入到版本控制中。PS:我们在分析
.git
目录的时候,一定要带着版本控制的思想去分析。我分析了
git init
,那么类推一下,git clone
干了什么呢? 这里留给小伙伴们分析吧。整体分析 .git 目录
上面我们通过
git init
后,生成了一个.git
目录,可能你对.git
目录还比较陌生,如果想掌握好git
的中级理论知识,那么.git
目录是要去征服的。第一:
.git
根目录下,有很多一级子目录和一级子文件。第二:看
hooks
目录,从命名我们联系到react
最新的Hook
特性,万物都是相通的。里面有很多文件,比如pre-commit.sample
文件,这是一个样本文件,我们按照样本文件的写法进行编写代码,然后把.sample
去掉,写成pre-commit
,这样就可以在你执行git commit -m 'xxx'
时,去执行pre-commit
文件中的代码。这就是git
中的生命周期钩子。第三:看
objects
目录,这是一个存放各种数据的目录。我们的项目,不管是什么形式的数据,图片也好,音频也好,代码也好,都会被转换成统一的数据格式存放在objects
目录下。关于
objects
目录的基本信息,可以看下面这篇介绍git-objects
的博客:Git-Internals-Git-Objects
第四:
refs
目录下有heads
和tags
目录。以及子文件HEAD
中写着ref: refs/heads/master
, 这是git
当前指向的分支。我希望在整体分析时,大家能把
.git
目录当成一个前端工程去分析,比如你可以把objects
目录当成前端项目中的dist
目录。其他类推,只要能有助于你去理解,那都是好的类推。PS: 这里是整体分析,没有去深入介绍,整体了解一下就好。
git add 后发生了什么
当我把一个不在版本控制系统中的文件,使用
git add ·
加到暂存区后,我来看一下.git
目录的变化,如图所示:我们会发现在
Object
目录下增加了一个名为60
的目录。该目录下有一个二进制文件。同时.git
根目录下多了一个index
文件,也是一个二进制文件。第一个:
git add
操作会把不在版本控制下的文件纳入到版本控制中,为什么会这样说,从中级角度看,是因为.git
目录有实质性的改变了。第二个:
git add
操作会在objects
目录下生成子目录为60
,文件名为d4a4434d9218d600c186495057bb9b10df98ad
的一个二进制文件。第三个:
git add
操作会在.git
根目录下生成一个命为index
的二进制文件。执行:
执行结果如下图所示:
就输出了一个单词,
blob
。blob
是binary large object
翻译一下就是二进制大对象。那我们可以这样理解,这个文件是一个二进制大对象,OK
,继续往下分析。比如文件
d4a4434d9218d600c186495057bb9b10df98ad
,不理解没关系,继续往前端上去联想,是不是想到了webpack
打包后的文件名,可以在前面加上hash
前缀。有种豁然开朗的感觉了吧,留给大家自行去分析吧。没有执行
git add
的时候,目录下是空的。当git add
后,多了一个blob
,同时生成了一个40
个字符的hash
串,然后目录和文件用hash
表示。也就是说:git add
后生成了一个blob
对象,blobId
为60d4a4434d9218d600c186495057bb9b10df98ad
。看到这你是不是又有点感觉了,记住一句话:
我们平常的各种
git commit -m 'xxxxxx'
其实生成一个commit
对象,同时生成了commitId
也是40位的hash
字符,存在objects
目录下。现在确定的一点是,当用
git add
把文件放到暂存区的时候,index
文件就生成了,这个index
文件是一个二进制文件,我使用下面命令去查看index
的内容:如图所示:
上面图中的那一串数据是
index
文件中的二进制数据。可以看到,
index
文件中包含了很多信息,比如1.txt
,2.txt
,还有TREE
。目前从表现上看,我只能了解到这么多的信息,它们之间肯定有某种联系。其实了解过暂存区的应该可以联想到,index
文件就是一个暂存区。可以看这篇直接给结论的官方文档:
Git-Internals-Plumbing-and-Porcelain
思考时刻
留几个问题给各位小伙伴思考:
为什么会报这个错误提示?
这个做法其内部的道理是什么,这样做是和算法有关系吗,目的是为了更好的性能吗,前端可不可以借鉴这种思想,还是说前端已经有了这种思想,那这种思想是什么?
自行想一想,也许会有一些有趣的收获呢。
如何去理解 git stash
这里我会通过实践去告诉大家,
git stash
在.git
目录是如何表现的。首先我进行一次
commit
, 项目现在只有一个commitId
,如下图所示:这个时候,我使用下面命令:
git add
后,我们看.git
目录,如下图所示:关注一下上面的箭头所指的文件。
点击
ORIG_HEAD
可以看到是一个字符串0991ddc42dbda1176858b89008b8dece5f91093b
对照着在objects
目录下找,发现确实有,我们再用下面命令我们看到了
tree
,tree
也有一个treeId
,treeId
为33b62884583995b8723d4d5ef75e44aa7d07fbf3
再结合
git log
再看下面这张图:
对比两张图, 会发现
ORIG_HEAD
文件中的hash
值 相等于HEAD
中所指向的文件位置中的hash
值。话不能说太透,后面的自行领悟吧。看下图:
图中的左边是我把
2.txt
通过git add
放到暂存区的index
文件的内容。右边是我使用git stash
后的暂存区的index
文件的内容。可以看出,git stash
前后的index
文件差别。请看下面我演示的
gif
图:可以看到,当我
git commit
的时候,refs/heads
目录下的matser
文件中存放的commitId
变成了最新提交的commitId
,而ORIG_HEAD
没有改变。由此可以知道,HEAD
文件存放的路径,其路径下的文件的hash
值是当前目录下最近的一次commit
。可以参考这篇博客:
Git暂存区原理
git merge 和 git rebase
merge
和rebase
的问题大概是git
中最著名的问题了吧,在面试中也是考察的最多的知识点。比如,你知道merge
和rebase
的区别吗?这种类似的问题,不胜枚举。网上教程也一大堆,如果你想深刻了解
git merge
和git rebase
的话,那就请按照我上面的那种分析方法,一步一步去操作,然后观察.git
目录下的各种变化,然后根据重要的变化来去细致的分析其中的原因和道理。比如当前分支为
dev
,然后我执行:一个最关键的一点就是: 要知道
rebase
是变基的意识。rebase master
是以master
为base
,然后把dev
分支的补丁打到master
后面,打的过程中生成的commitId
是新的commitId
,dev
原有的commitId
被丢弃,时间线也就变成了直线。最终,
matser
和我的dev
分支合并,让最新的commmitId
以我的最新提交的为准( 这里就是我在dev
分支上的最新提交 )。所以当我push
后,我提交的代码就成为了基准。rebase
就这么简单。可以看看我的两篇简洁
issues
:git 中的
blob
commit
tag
tree
是怎么串起来的其实这是一个非常关键的问题,很多人都不清除这些
单词
背后的的真理究竟是一种什么样子的美丽。但是我不打算造轮子了,因为好文章太多了,这里我还想放上面的一张图,因为这张图太经典了。
解释已经在图中的文字中了,比如知道了这些,你就知道了我们在给版本打上
tag
的时候,究竟是做了什么。我们不能浮于表面,只知道要打tag
,我们还要知道打tag
背后的原因。只有这样,才能做到知其然知其所以然。其他零碎的知识点
此文件里面写的内容是本地最后一个提交的信息
clone仓库时所有的引用
git 中级之实战技巧
我把在使用
git
进行版本控制过程中,我所用到的所有git
操作高度提炼一下。git
操作问题下面简要分析一下上面各个目的过程中的一些心得。
处理合并,解决冲突
git
处理合并和解决冲突的能力 碾压svn
。比如svn
处理一个冲突,由于是集中式的仓库管理,仓库只有远程一个,可想而知,解决冲突就是一场提交竞赛。按照我的这几个步骤来,基本不会存在任何冲突解决失败的情况。
首先,当我去
pull
远端代码的时候,比如执行执行完之后,我发现的控制台多了很多
conflict
提示,我看了下,很多都是别人代码的冲突,这个时候我怎么会呢?我会毫不犹豫的
git reset --hard
回滚掉这次
merge
,然后我已经知道了这样是不行的,但是我又不能去等着别人把冲突修改掉,怎么呢?我会先在当前分支的基础上新切一个分支相当于备份一下目前本地的代码,
dev-backup
分支用来保存本地代码。然后这时,我切换到
dev
上,切换后,我要怎么办呢,这时我会将dev
分支的代码全部替换成远端的dev
分支:这时,我本地的
dev
分支已经全部采纳远端dev
分支代码了,这个时候我还需要将我本地修改的代码合并进去,但是这个时候我就可以使用一个命令:通过上面的命令,我们就可以将
dev-backup
分支上的xxx
目录下或者xxx
文件的代码单独合并到dev
, 而这部分代码就是我本地自己修改的代码,所以就算有conflict
, 我也可以迅速解决掉,然后安全push
远端仓库上。上面的解决冲突的方法,虽然方式简单,但是是我个人认为可以完美解决掉
git
版本控制中的所有合并和冲突问题。在版本控制系统中,合并一直都是一个核心节点,我们要去理解合并和解决冲突在版本控制系统中究竟占有多大的重要性。
提交代码
提交代码这个应该没什么问题,但其实你把本地代码提交到远端仓库这一步骤,是一个非常重要的时刻,为什么我说非常重要呢?想必你之前听过外国一个程序员因为同事经常
git push -f
而把同事给终结掉了,😂。所以害怕了吧,莫事,不慌,你只要遵守这几个原则就ojbk
了:git push -f
除非你已经做好不想活的准备了。😂commit message
git commit
之前先git status
看一下,检查一下有没有无意间改动了其他文件。其实我个人的感觉就是,如果是自己的业务项目,除了第一点,第二点,第四点需要去注意外,像 第四点,
commit message
这种,开心就好吧,不用很刻意的。提高开发效率
谈到这个,我想大家都有一些自己的总结吧,在用多了
git
后, 慢慢的会发现有一些可以加快使用git
进行版本控制的小技巧。下面我总结一下我自己总结的几点提升开发效率的方法吧。比如我配的有:
gst
代表git status
, 当然你还可以更加简单,开心就好。用好
stash
也是一个既简单又可以提高开发效率的方法,具体用法不说了,我的github
有相关详细资料,它主要是起一个暂存的目的,但是一般大家都是git stash
合理的优化
谈到优化,其实我想说优化是一个相对的概念,如果对
git
控制版本的过程进行优化的话,我个人觉得我目前用到的优化也不多,大概就是以下几个:git rebase -i
对我的一些我都看不下去的commit
进行处理。当自己出现错误操作时,做到快速且正确的处理掉
这个当然是自己蠢了,不小心把东西搞砸了,那就要快速解决掉自己的错误操作,怎么解决,思想也很简单:
比如通过本地切分支快速备份我自己的代码,然后切换回去,快速把自己的错误代码回滚掉,然后
push
到远端仓库,解决远端仓库的代码冲突问题,然后我再继续解决本地我自己代码的问题。帮助同事解决他们的一些
git
操作问题我感觉如果一个项目很大的话,参与者很多的话,随时有新的
coder
参与进来,你是无法保证所有人的git
操作都会很正确的,而这个过程中,一些人可能有进行了错误的git
操作,自己也无法解决,然后会找其他同事寻求帮助,我也帮助过一些同事。我在帮助其他同事处理git
问题的时候,使用的命令还是比较多的,有时候还得使用一些不常用的技巧,比如正则,过滤等,这里就不细说了。实战过程中自己的一些感悟
我觉得,我们没有必要在项目开发过程中把
git
操作复杂化,一些黑科技什么的,也没有必要去关注,有句话是这样说的:能用简单的操作解决复杂的问题才是大牛。所以上面我介绍的实战技巧,可以说没有什么高大深的技巧,当理解的足够深入的时候,通过简单的操作也可以保证项目的有序进行。git 高级 -- 你可能不知道的 git 知识
这里呼应一下文章开始所说的那一句话:
从初级到中级再到我都
hold
不住的高级。为什么我说我都
hold
不住呢?是因为我真的hold
不住。但是我还是去学习了一番,重新简单翻了一遍C
和C++
语言,尝试着去理解一下。简单看一下 github 上的 git 源码
首先把
github
上的git
仓库clone
下来。先看一下 git 项目 代码量
这里我用到一个代码行数分析工具
cloc
,可以通过下面进行安装:安装完毕后,在
.git
目录执行:得到如图所示:
从图中我们可以发现,当前
github
上的git
项目是由很多语言组成的,master
分支的总代码行数大约50
万左右(PO File
不算)。主要语言有C
sh(Bourne Shell)
Perl
C/C++ Header
。给我的感觉有几点:第一点:代码量不算大,50万行左右,与
linux
内核这种千万级别的代码还是有差距的,只能算是一个工具。第二点:涉及到的语言很多,但是核心语言基本就
C
sh
C/C++ Header
这三种。先降维分析
目前由于目录过于复杂,我想到了去看
git
项目第一次commit
的内容,一般来说,第一次commit
的代码量是比较小的。我在github
上找到的截图如下:我进入git 目录 执行了
去看一下第一次
commit
的代码内容,如下图所示:命令行:
VScode 截图:
我好奇的使用
cloc
看了下代码量,下图所示:惊了!只有848行,是不是瞬间有了信心。那就开始终结它吧!
按照惯例,我去
README
中看了下项目介绍:如图所示:
编辑者是 Linux Torvalds
这两句是作者本人对
GIT
的介绍,是本尊无疑了。第一点:所有对象都是用他们自己的内容来命名,通过
SHA1
hash
值来标识自己。对象可以通过引用其他对象的SHA1
hash
来引用其他对象。所以可以建立起一个有层次的对象模型。第二点:对象内容都是用
zlib
进行压缩,同时SHA1
哈希始终是 是压缩后的对象内容的哈希值,而不是原始对象内容的哈希值。第三点:A "blob" object is nothing but a binary blob of data, and doesn't refer to anything else. 简单点说就是:
blob
没有任何其他属性,仅仅表示文件的内容。第四点:当前目录缓存,可以理解为是暂存区,暂存区也是一个二进制文件,它通过一个简单的数组来记录着时间,权限,和对象内容。
第五点:使用了
SHA1
,所以改变和内容是值得信任的。这里我就不造轮子了,找到了一篇文章,基本把第一次
commit
的源码各个文件的作用解释的较透彻。Git源码学习
简单分析一下最新的 git 源码
执行
git checkou master
切到 master 分支从图中我们可以看到,有很多很多东西,一点都不想分析,那就不分析了,都1万多字了,写不动了。就这么愉快的同意啦!开开心心过完年后,再单独写一篇( 嘿嘿嘿 )。
gerrit 原理知识
这个原理知识就不说了,简单点说就是搭一个
gerrit
服务器,然后通过UI
界面去进行代码的CR
,CR
通过,点击submit
就会把代码同步到gitlab
上。gerrit 实战总结
本人负责给项目实施
gerrit
, 并解决同事在过渡到gerrit
方式的过程中出现的各种问题。我在解决各种问题的时候,对整个gerrit
的流程和操作都理解了狠多,下面就分享一下我在帮助同事过渡gerrit
的过程中遇到的问题和总结的一些心得吧。gerrit 基本设置
这个就不说了,基本的像
ssh
认证 、remote
设置、邮箱设置、这种我就不造轮子了,按照网上的基本教程来。提交 gerrit 时提示缺失 Change-Id
问题描述
这个错误,是在过渡到
gerrit
的过程中出现最多的错误,没有之一,几乎都会遇到。错误如下图:
从图中可以看到,提示
[8a5fca6] missing Change-Id in commit message footer
什么意识呢,就是说
commitId
为8a5fca6
的提交没有Change-Id
,所以就提交失败了。同时我们可以看到打印信息里面有给解决这个问题的方法,先执行:
gitdir=$(git rev-parse --git-dir); scp -p -P 29418 name@git.co.com:hooks/commit-msg ${gitdir}/hooks/
再执行:
git commit --amend
但是在解决这个问题的过程中,我发现上面的提示,有时候并不能成功。我总结出了几种情况,下面一一列出。
缺少 Change-Id 的
commitId
是 head 指向的 commitId如果是
head
的话,也就是git log
的第一个commitId
。 那可以直接按照上面提示的命令去执行。这里提一下,在执行
git commit --amend
时,会进入vi
界面,进入后可以不用修改任何东西的,直接保存退出即可,就可以重新刷新head
指向 的commitId
的 值了。缺少 Change-Id 的
commitId
不是 head 指向的 commitId如果不是
head
的话,比如是第 6 个commitId
缺少Change-Id
,那怎么办呢? 针对这种情况,有两种办法:第一种:
git reset --soft
然后就可以
push
成功了。但是美中不足的地方就是,软回滚了其他的commit
。 但是问题不大,如果都是你自己的commit
,那就直接soft
吧,不是的话,可以采用下面第二种方法。第二种:
git rebase -i commitId
注意:
git rebase -i commitId
中的commitId
并不是提示的commitId
。 而是提示中commitId
的前一个commitId
。比如执行git log
:那这个
commitId
就是godkun666
。然后进入
VI
界面,如下面:直接把缺少
Change-Id
的commitId[8a5fca6]
前面的pick
修改为reward
,然后保存退出就好了。这种方法也试用一次性修改多个缺少Change-Id
的commitId
。保存退出后,就可以直接push
了。 对于rebase -i
的相关知识,请自行谷歌百度,这里不做讲解。上述两者方式都试了,还是不行
这种情况出现在一个同事身上了,两个情况的解决方法都试了,还是不行,然后我仔细看了下,在执行:
gitdir=$(git rev-parse --git-dir); scp -p -P 29418 name@git.co.com:hooks/commit-msg ${gitdir}/hooks/
出现了一个报错,由于我没有保存截图,大致意识就是
hook is not directory
可能我这样说出来,感觉很简单啊,但是在过程中,这个提示是很不明显的,后面我进入.git
目录看了下才知道怎么回事,hooks
是一个文件了,不是目录,这也是够秀的,我初步猜测是在复制这个命令的时候,复制的不全,导致生成了hooks
文件 。然后我删除hooks
后,又新建了一个hooks
目录,重新执行了上述命令就好了。commiter email address xxxx does not match your user account
出现这种问题,是因为图中提示的
commitId
其所绑定的邮箱不正确。需要你先设置正确的邮箱,在设置完正确的邮箱后,我们继续其他操作,我总结的有三种方法可以解决这个问题:第一种方法:把这个有问题的
commit
撤销掉,可以使用软回滚git reset --soft commitId
回滚掉。第二种方法:如果这个
commitId
就是head
的指向,那直接git commit --amend
刷新这个commitId
。第三种方法:如果这个
commitId
就是head
的指向, 那通过rebase -i
去reword
这个commitId
。gerrit cannot merge and Submit including parents
不造轮子了,基本操作问题都在下面这篇博客中有提到:
如何解决gerrit代码冲突
但是,没有自己的看法的话,那和咸鱼有什么区别呢?
如何所示:
coder
本地开发后,产生了commit
然后push
到gerrit
上后,CR
者会根据情况进行拒绝,如果拒绝了,但是coder
本地的commit
并没有撤销,那么就会导致后续提交的系列commit
出现上图这种情况,因为现在的commit
依赖前面的几次commit
。但是前面提交的commit
并没有同意。所以就导致了很多CR
问题。coder
和CR
者的commit
时间线不一致。核心是把
commit
时间线做到一致如果还没有出现上述的问题,如何做预防:
当
coder
成功把本地的commit push
到gerrit
上后,记得要reset
掉,如果不放心,那可以软回滚,然后stash
,等CR
,如果拉下来发现没问题,就可以把stash
放弃掉。当
push
后,切新分支进行备份,然后切回去,再把本地的commit reset
掉。这样就不会存在上面图中的各个不能合并的问题的。当CR
后,你pull
,发现代码都对的时候,就可以把备份分支删掉了。如果已经出现上述问题了,怎么办?
核心思路:现在
coder
需要把本地的那些已经被gerrit abandon
掉的那些commit
干掉。直接 重新
git clone
切一个分支进行备份,然后切回去,使用:
放弃本地所有代码,全部采用远端代码。。然后使用
cherry pick
把备份分支的 你需要的commit
合到dev
上。PS: 当然这些只能是本地
coder
去解决这个问题。使用
rebase
去挨个修改或者使用git reset --soft
把前面的很多commit
都回滚掉。 不建议使用第三种方法,操作要求高,容易出错。how to make SourceTree push to Gerrit
git
仓库代码根目录下执行:how to make TortoiseGit push to Gerrit
小乌龟
push gerrit
时会出现这种错误,如下图所示:怎么解决呢?请看下面截图:
用小乌龟推送
gerrit
的时候应该要在remote
前边手动加上refs/for/
参考博客: TortoiseGit推送代码到Gerrit的过程
如何快速高效的 CR ( coder review )
当各个产品线提交的代码都要你来
CR
的时候,你会发现根本没法去CR
,因为你本身就不熟悉他们的代码,怎么CR
呢,最后我决定这样做:各个产品线的
coder
需要CR
的话 群里at
我一下,我在CR
的过程中,有三个原则:第一个原则:我默认相信各个产品线对自己负责的代码做出修改,也就是相信
coder
修改自己负责的代码,责任制。第二个原则:我会严格关注各个
coder
有没有改动其他coder
代码,如果改动,我会去私聊询问,为什么要这样做。第三个原则:我会严格关注各个
coder
有没有改动公共部分的代码,比如登录模块,如果改动,我会去私聊询问,为什么要这样做。只要不符合上诉三个原则,一律
abandon
。git FAQ 传送
发个关于 git FAQ 的链接:https://git.wiki.kernel.org/index.php/GitFaq#Why_the_.27Git.27_name.3F
参考链接
git
的网站备注
交流 + 福利
我把我平常在工作和学习中总结的
git
知识整理了一下,把最常用的,以issues
的形式放在了我的gayhub
上,有需要的小伙伴可以点击下面链接自取:掘金系列技术文章汇总如下,觉得不错的话,点个 star 鼓励一下,一个 star 开心一年(手动滑稽) ,也可以
gayhub
关注我一波,持续输出精品文章。我是源码终结者,欢迎技术交流。