为啥我的代码库那么大?聊聊Git使用坏习惯 - 阿里技术

本文作者用幽默又真实的文字总结了开发者日常工作中遇到的那些事儿。

说点真实的

众所周知啊🤔,很多公司的度量系统可以展示你提交了多少次commit,以及每一次提交包含多少行代码。

搞得部分兄弟一天可劲 add commit push。他那一个变更发上线,你打开Git Log,好家伙那一大串都是他。加加减减缝缝补补,最后一共改了20行代码,硬是刷出了200行的功德。

一串Git commit message如下:

  • AoneBuild Merge feautre/777_平台赋能牛逼新特性 to master
  • Fix老板CR中建议
  • fix again
  • fix2
  • fix
  • save
  • update
  • new feature

    典!典中典!

仿佛能看到他信心满满,直接发预发!一刷新, 哦豁,白屏!
哦忘了传这个了,fix,push,部署,玩手机,抬头一刷新,哦豁,白屏!

好活!

下次 OC 再搞脱口秀我提个节目!随机找几个这种上去对着这种 git log 现场即兴模仿表演。

他们欢快而自信的时候,以及受痛苦和绝望所折磨的时候,生活中的痛苦和压迫会像血汗一样,一行行地全写在了他们的Git Log里。

你们好不好奇, 一个代码库几百个文件,为啥能用一两个G?小小一个系统,下个代码都十几分钟?

部署和CI场景可以 depth=1 浅克隆加速

完全没有意义的Git 提交历史,极速膨胀的代码库大小,混乱的分支关系。正在压死你的代码库和项目!

每一个程序员,在遇到一个可爱而热心的代码仓看门大爷前,都会随意的对待自己的commit,因为大家都这么做。没见过怎么best practice,所以就doesn’t matter。

求求你们了!好好写你的Git commit message!squash你的 fixfixfix!删删你已经没用的分支!重构拆掉哪些超大文件和几万行的类!

让Git Log能做到写清楚你到底改了啥!而不是你的草纸!

阿里很多资深Coder都是野生哆啦A梦,技能树包括但不限于:

  1. 手里维护的代码库,托前人的福,一堆方法 JsonObject 出入参,他能如特工,手持密码本相互通信。
  2. 系统日志print全是不带id的sout,也能靠口袋里的见都没见过的神奇道具和监控线条里波纹感应定位故障源头。
  3. 一个代码文件3万行,硬是一眼扫找出在哪儿再加个if else能5分钟hotfix一下线上Bug。

    "线上bug!

    很急,来不及加单测了,帮忙过一下!

    "

  4. 对代码系统机魂, 了如指掌,深谙取悦机魂之道。知道一些外人看来十分玄妙的独特逻辑。掌握很多,“又不是不能用,只是你不会用,要这么这么就能用”的高级黑盒功能。

虽然兄弟们这么用Git “又不是不能用”,但是要我说 “那是你不会用”,“要这么这么用

到这里,聪明的小朋友就要问了。为啥几百个文件,为啥能用一两个G呢?

Git的结构,图文实例解说

众所周知,Git 主要数据结构是一颗树,在你的.git文件夹里,结构是这样的。

.git文件内部结构

其中的 refs中保存有分支的信息。

refs文件内部结构

其中的 heads,本地分支,remotes,远程分支(用git fetch更新), tags 不可变的版本指针,他们都是指向一个commit的指针。

master的内容物

比如 这里, master,就是一个commit id。

你可能注意到了,分支似乎不是一个分支一个文件的。而是按/分割,当成文件夹存储的。

比如feature是不是个分支?其实不是,假设 我新建一个名为 feature/newaCTO的分支,会在feature文件夹里建一个newaCTO, 而不是直接新建一个feature/newaCTO。

小zips, linux文件系统中,文件与文件夹是不能重名的。

*所以, 一旦谁建了一个叫做feature的分支。那么,在第一个小聪明删掉他之前。就没人能拉出任何feature/的分支了。**

分支是指向commit的指针,那让我们更近一步, commit是啥呢?

master节点的详情

这是一个merge 节点。和普通的commit节点不同,merge节点有两个parent。

除此之外,有author和committer, author是作者,committer是提交人。在本地开发中,这两者基本上是一样的。注意,这里使用的,是gitConfig中的name 和 email。

这里parent指向的是另一个commit, 典型的树状数据结构。

那么Tree中有什么呢。

tree的结构

tree的详情

可以看到展示了一个列表,与GIt Log中展示不同, Tree里其实包含了完整的文件树。树的末端,指向一个文件的oid。

列表中四个字段分别是:

1、mode: 权限(chmod加的东西)

2、type: 类型,tree说明是文件夹,blob则是文件,大部分情况下就是代码,也可能是图片或其他文件。

游戏团队的代码库中就会包含很多美术素材图片。

但是,Git不是文件存储系统,大文件建议使用Git LFS技术,上传OSS,Git仅管理文件链接。

3、revision(oid):每当你提交commit时,你在这个commit中改动的文件,git会把文件的二进制数据加个信息头,然后算个hash,从而产生一个新的oid。然后这个commit会指向他。

所以,每当你commit,就算提交一行的改动。就会存整个文件!(把代码文件,把类拆小点吧!不小心摸一下触摸板,代码就不知道去哪儿找了!)

然后你在每一改个两行,就来个commit,存储膨胀能不快么!

就不谈没维护好 gitIgonre,胡乱git add .把编译文件,摸鱼刷的leetcode代码或者其他奇奇怪怪的东西提交到Git中的情况了。

你可能会问,我这的文件已经都删掉了,为啥.git还有这么大?你们是不是有Bug?

因为Git会保存你历史的所有版本!除非没有一个任何一个commit指向这个blob,才会成为游离节点,在Git Gc的时候被处理掉。

当然Git也没那么呆,是可以存增量的。

你在Git pc的时候会执行repack,repack会压缩一部分到增量,不过一般没人这么做,所以一般都是存了完整的离散文件。

git gc是个好课题,存储未来可能可以智能化的全自动gc,但是现在还不行。

因此!没事删删分支!不小心提上来的大东西,得把对应的commit squash掉!

本地也可以允许git gc清理掉游离节点释放空间。

我们并不是说要squash到一个变更一个commit,或者一个feature分支就一个commit。就像游戏存档一样。你可以在关中频繁存档,但是一大关打完了,你可以存一个然后把关中的存档都删了。 只留关键节点,这样万一你后悔选了这条命运线,可以读档重来。

这也是为什么那些大库需要严格执行主干开发的原因。要是我们公司几万研发都用一个大库,还都是用Git记日记的憨批, 我觉得我们会被迫在存储技术上卷死OSS。坏了,顺手把多版本云盘做成主营业务了。以后新人来了先发一块移动硬盘好了, 微服务那么多系统,下代码不得先下个半个月?

总结

一是git log和commit message是很重要的信息来源,要保持整洁,用的正确,这个比发布文档还清晰。

二是代码存储膨胀问题是很现实的, 随着系统发展,代码库一个G你还能下下,再大点咋办呢。

虽然我们存代码不收费,说到这儿dataworks的兄弟们可能有同感, 不要钱就使劲造,狠角色拿git当oss用的都有几个。。

主要影响的,还是可见的未来,不治理的话,clone代码越来越慢。

Best practice:

1、Code Owner要建立 代码库统一的commit messgae 格式规范,例如 Feature(commit): write an article to introduce git

2、打完Boss,单测通过,squash掉你之前上厕所或者测试时候的commit!化零为整但也不要矫枉过正!团队应该根据自己业务情况探索合适commit的大小和规范。

3、重构掉“巨石类”!他们早就不够内聚了!把能拆掉功能拆出来吧!我先替管存代码的那个亘古和其他用这个类的开发先感谢你!

4、大文件用Git LFS!

5、及时维护GitIgonre!误提交的文件一定要清理掉!

6、用心维护主干分支的Git Log!让改动清晰可见!

7、删掉没用的分支!Later Equals Never!当机立断舍离!

8、Readme要持续更新!怎么启动,格式规范都可以写在readme里!

Bad Practice:

1、不要建 feature,release这两个分支,虽然git没有保留关键字,但是后人发现为啥建不出来分支的时候肯定会问候你的。

10