乐趣区

关于前端:CICD必知落后master分支检测

背景

来公司一年多,业务之余始终在参加做 BU 本人的前端公布平台;以前咱们的构建底层(CI/CD)大多依赖于团体的能力,所以常常一个利用某个迭代公布之后,其余迭代再构建部署,就会报错,这时就须要关上团体零碎的构建日志, 会看到相似上面的报错提醒:

master 分支有新的提交,请合并分支后再持续部署

往年因为有新的业务零碎要对接,咱们须要有本人的 CI/CD 底层,而集成这个能力,在最后我也是走了很多弯路,故记录一下。

为什么落后检测很重要

目前少数简略的前端构建部署,都是以分支来打理;比方咱们实现一个需要时:咱们会从 master 骨干拉一个迭代分支,而后咱们会用这个分支在 开发 - 测试 等环境做构建部署,线上公布前,咱们再将更改合并回骨干。

下面的流程,是一个很通用的流程,但有可能也有其余的做法,比方做的特地前沿的:部署分支每次都是从 master 骨干拉取,而后合并迭代分支,而后再部署。这种状况不在明天的探讨范畴之内,因为这种策略不会有这个落后的烦劳。

回到后面,为什么咱们须要做落后检测?因为很多时候,一个利用会存在多集体保护,存在多个迭代(A,B),这里咱们假如 10.10 号这两个分支都是从骨干同一节点拉取。A,B 本人失常开发部署,而后 A 的迭代在 10.22 号上线了,公布完时,代码合并到了 master;然而 B 迭代 10.24 号上线,但并不知道 A 有公布,如果这时部署零碎没有 master 分支落后检测,B 就顺顺利利的用分支 B 上线了,这会造成什么结果?

  • B 上线后,分支合并回骨干报错(大概率),前面的迭代没有这个性能,导致前面会有故障产生;
  • A 迭代上的性能没了,线上事变(重一点,3.25)。这锅算谁的?A 的?B 的?还是平台的?在我看来,这个锅就是平台的

可能你会问,为什么上线前不先合 master,而后再构建部署上线?咱们平台侧,有这样两个思考:

  • 上线前有灰度阶段,如果先合了 master,如果灰度发现有 bug,或者其余迭代要先上,退出灰度就很麻烦,这时骨干分支也会被净化;
  • 从 master 从新拉分支,合并代码构建部署,如果是手动操作,这样对开发者而言,会显得很麻烦;如果平台本人来做这个操作,会有代价,而且都集成这个能力了,就能够间接再往前建设一点,走后面提到的前沿计划

## 咱们的做法

首先要明确,什么状况,咱们称之为落后 master,上个图:


因为 feat/1.0.0 公布后 merge 到了 master,导致 feat/1.0.1 落后 master 分支, 这种落后与 1.0.1 提交多少 commit 无关,只与是否和 master 分支 commit 信息同步相干;

其实去网上查了对于分支比对这方面的材料,发现根本都是 shell 脚本解决, 在 stackoverflow 有一个帖子和我述求基本一致:链接地址

Is there a way to do a diff between my branch and master that excludes changes in master that have not been merged into my branch yet?

外面的高赞答案,就提到了:git diff branch...master,并很善意了给了官网链接解释;

外面提到了一个概念叫merge-base, 等同于两个分支独特的终点,比方下面两个分支,这个点就是 X;

git diff master...feat/1.0.1 is equivalent to git diff $(git merge-base master feat/1.0.1) feat/1.0.1 is equivalent to git diff commitX commitF

所以当运行:git diff feat/1.0.1...master, 能够看到:

从上图的后果能够看到,列出的差别点只有 A / B 两次提交;

所以两次差别比拟就等同于:git diff X B

That’s it, wo got it!!!

但咱们的平台是基于 gitlab API 的,没法间接运行这种命令行,侥幸的是互联网是无所不能的,这个 API 就是:gitlab.Repositories.compare

// Repositories.compare(projectId: string | number, from: string, to: string, options?: Sudo)
const info: any = await gitlab.Repositories.compare(projectId, commitId, 'master');

留神一下 from 和 to 的地位,from 是 feat/1.0.1,to 是 master,这个很重要;

失去的 info 后果长上面这样:

{"commit": {},
  "commits": [],
  "diffs": [],
  "compare_timeout": false,
  "compare_same_ref": false,
  "web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/compare/ae73cb07c9eeaf35924a10f713b364d32b2dd34f...0b4bc9a49b562e85de7cc9e834518ea6828729b9"
}

所以如果分支没有落后 master,即 master 分支没有新的提交,那么 commit 就是一个 null 值,commit 和 diffs 就是一个空数组;另外要留神 compare_timeout 的值,如果分支比对工作量过于宏大,则有可能造成超时,compare_timeout 为 true,那么这时检测也是有效的;


还有一个须要留神的,就是这个 api 第四个参数是个 option,option.straight 为真时,这时的 diff 后果,就不是咱们料想的后果,所以调用时也要留神。

野路子分享

其实事件,并不像下面形容的那样顺利,最后咱们因为工夫紧迫没有发现 compare 这个 API,而是采纳了递归的形式,就是一直去回溯分支节点,试图找到和以后 master 的 commitId 雷同的节点,最大查找范畴为向下 8 层,如果超过 8 层还没找到,那就判断为落后 master 分支,否则就是平安的,我集体感觉这个算法实现不低于 leetCode 的中等题;

在一些简略的迭代分支治理上,下面的算法还能见效,但对于过于简单的分支,要不就是超时,要不就是超过了 8 层;超时是因为如果一个节点不匹配,就须要调 API 拿到下一堆子节点,API 调用过程是十分耗时的:

// 局部代码实现
if (level > MaxLevel || this.globalFinish) {return false;}

const reocords = []
for (let i = 0; i < parentIds.length; i++) {const currentId = parentIds[i];
  if (this.lookedIds.has(currentId)) {continue;}
  this.lookedIds.add(currentId);
  if (currentId === masterId) {
    has = true;
    break;
  }
  const commitInfo = await gitlab.Commits.show(projectId, currentId);
  
  reocords.push({
    gitlab,
    projectId,
    parentIds: commitInfo.parent_ids,
    masterId,
    level: level + 1,
    recursion: true
  });
}

下面这个算法,在平台刚上线时,还运行了一两天,还没遇到什么问题;但在晓得 compare 这个算法时,咱们就果决换了,连夜测试上线,因为官网的 API 更牢靠。

这个分享到此为止,如果你看到了这里,心愿对你有用。

退出移动版