Gitflow 太繁琐?为什么不自动化呢——Gitlab 版

1,583 阅读5分钟

导读

Gitflow 太繁琐?为什么不自动化呢 一文给出了 git 工作流的原理和每一个场景的命令。在结尾也给出了笔者自己封装的更易用的工具,适用于所有语言的项目。但是,只是对 github 仓库友好,gitlab 还无法使用。这一直是笔者想要完善的地方。终于,又花了小一周的时间搞出了一个适配 gitlab 版的工具,使用方法完全不变。

本文就是在开发过程中的一些经验总结,一是向大家介绍实现原理,二也算是跟大家普及一下 gitlab API 的应用方法,希望有所帮助。

正文

没有 Gitlab-cli 怎么办

要想像 github 版一样利用命令行操作 MR,就需要像 gh 一样的 cli,但是 Gitlab 官方又没有提供,在网上找了一圈,没有用 shell 版实现的,也就是说无法做到【适用于所有语言的项目】这个要求。再加上该脚本中用到的操作 MR 的功能不多,所以笔者决定自己用 shell 实现这些功能。

最开始发现 Gitlab 有 Push Options 这个功能,能够在 git push 的基础上增加参数,实现操作 MR 的目的。但是实验之后的结果表明,Push Options 提供的能力还不足以满足需求。在经过一番挣扎,想通过各种变通来解决这些问题的尝试失败之后,转向了其他方向 —— Gitlab API

Gitlab API

Gitlab API 可以通过 curl 命令来调用,所以兼容性上是没什么问题的,使用起来也很简单,主要是一些前置的准备工作需要处理一下。

Access Token

调用一些增、改、删类型的 API 时,是需要权限控制的。这就要使用到 Access Token,具体生成方法见 这里,我们只需要个人操作 API 权限的 token 即可。

魔鬼藏在细节中,这里就有了一个细节问题。如果封装成脚本之后,这个 token 肯定是存在一个 $token 变量里的,但是每个人的 token 又是不一样的,如果直接写在脚本里,token 泄露不说,每个人用的时候都得自己改,实在太麻烦了。怎么解决呢?

笔者采用的方案是单独生成一个 .git-config 文件存放 token 变量,并且把它加入到 .gitignore 中。脚本通过 source .git-config 的方式引入 token,这样就能够解决这个问题了。缺点就是用户需要手动创建 .git-config 文件,当然这个问题可以靠脚本自动创建来解决。

API 调用

为了写代码更方便,也为了将来与 github 版代码的共享,笔者将 gitlab 的请求封装成了一个方法:

cli() {
  curl -s --header "PRIVATE-TOKEN: $token" "$@"
}

# 新建 MR
cli --request POST -d title="$title" -d description="$(cat $logFile)" -d source_branch="$currentBranch" -d target_branch="$targetBR" "$apiHost"

命令都很好理解,唯一需要展开一下的是 "$apiHost" 这个变量,其实就是 API 的 url 地址。以【修改 MR】 为例,其 url 为:PUT /projects/:id/merge_requests/:merge_request_iid。其中 project 的 id 是需要我们手动写入的,属于 one time 的事情。需要我们自动获取的是 domainmerge_request_iid

API domain 获取

gitOriginUrl=$(grep -E "^\s+url = .+\.git$" .git/config | awk "{print \$3}")
apiDomain=$(echo $gitOriginUrl | sed 's/\(https\{0,1\}:\/\/[^\/]*\)\/.*/\1/')
apiHost="$apiDomain/api/v4/projects/$projectId/merge_requests"

看起来有点复杂,别着急,其实很简单,只是从 .git/config 中读取出 repo 的地址,然后就是对其进行截断。但是这里又有细节。

  1. 要考虑兼容 http 与 https,但是 sed 命令中没有 ?,所以需要用 {0,1} 来实现;
  2. 如果直接写 .*\/ 去匹配 / 前的内容,会触发贪心,获取到最后一个 / 前面的所有内容。又因为 sed 没有 ?,所以只能采用 [^\/]* 的语法启用非贪心。说实话,笔者还没有完全参透这里的逻辑,是直接从网上找的,有兴趣的同学可以自己研究一下;
  3. 各种 \ 转译,即使我第一眼看上去也看不出来是什么意思,写的时候是不带转译的写,然后一个个加的 \ 进行转译,然后就成天书了; 最后,拼上固定字符,就得到了 $apiHost

merge_request_iid 获取

currentBranch=$(git rev-parse --abbrev-ref HEAD)
mrId=$(curl -s "$apiHost?state=opened&source_branch=$currentBranch" | sed 's/,/\n/g' | grep "iid" | sed -n '1p' | sed 's/:/ /g' | awk '{print $2}')

说实话,这个命令并不简洁高效,最佳的做法应该是利用非贪心正则,但是实在是懒得写了,于是暂时采用了这个命令。这条命令也有其好处,就是容易懂,我们来看一下。

  1. 获取状态是 opend 且源分支是当前分支的所有 MR;
  2. 因为返回的数据是 JSON,所以将所有 , 替换为换行;
  3. grep 寻找 key 为 iid 的行,并获取第一行;
  4. : 替换为空格,用 awk 获得第二列,即 merge_request_iid。 总结成一句话就是:格式化 JSON 后,找到第一个 key 为 iid 的数据并获取其值。

这里有一个漏洞,暂时还没有想到很好的解决办法。就是当存在多个【源分支是当前分支】的 MR 时,会默认读取第一个的 iid,这不一定是真正的结果。笔者也想过通过 author 过滤,但是这样也不能彻底解决问题,更主要的是别人操作你的 MR 是一个很常见的场景。

目前还没有想到什么好的解决办法,这么看来 gh 也可能有这个问题,等有时间的时候验证一下试试。

封装成脚本

由于笔者之前已经有了规划,所以在 github 版的脚本中已经做了一定的代码设计,只需要像上文一样添加几个变量和方法,再修改一下所有使用了 gh 命令的地方,脚本就完成了。具体可以去 git-workflow 查看,不用看代码,花 2min 看看 README 就可以。当然,要是顺手点个 star 就更好了。

值得一提的是,github 版本的合并用的都是 rebase,而 gitlab 由于默认没有开 rebase 方式,所以无奈用了 merge。【Rebase vs. Merge】的问题就不在这里展开了,反正笔者是偏 rebase 的,如果你觉得不合适,自己改下脚本就行了。也不排除后续增加 rebase 和 merge 的选项。

另外,笔者为了使用方便,还封装了一个 install-gitflow 的脚本,只要运行下列命令就能实现脚本的安装或升级。

curl https://raw.githubusercontent.com/zhaolandelong/git-workflow/main/install-gitflow >install-gitflow && \
echo -e "token="your token here"" >.gitlab-config && \
chmod +x ./install-gitflow && ./install-gitflow gitlab && rm ./install-gitflow

具体代码分析就不写了,不过里面还是有一些很有意思的功能和语法的。比如更新操作时,读取用户已经修改过的变量保存,下载完新脚本后恢复变量值;安装时以交互形式让用户设置各变量名等。最关键的是里面大量用到了 eval 这个命令,逻辑比较绕,比较难写,所以还是等有缘再说吧。

结语

gitflow 的工具现在算是完整了,大面上的东西都全了,剩下的都是小的功能迭代了,有空就搞搞。说实话,搞完之后还是有一定成就感的,这可以算得上笔者第一个规范、完整、实用性强的项目,以后的工作都用的到。

笔者完成的是工具,但是其背后的规范才是更有价值的,尤其是对于技术管理的同学来说,熟悉这些业内的最佳实践是必须的。工具是落地层面的事情,工具可以保证规范有效的落地。所以在推工具之前,一定要让团队的使用者先熟悉和理解规范,然后才能更好的使用工具。还请搞清楚本末因果。

用心认真的折腾是没有风险的。 —— 张一鸣微博