好久没有写博客了,今年才写了 2 篇,但并不是说我在创作方面懒惰了,反而今年写了一个还算不错的开源项目(后面会写一系列文章记录我在做这个开源项目遇到的技术问题和一些人文)。可能空闲时间的精力倾斜到开源项目,导致博客投入不多吧。Anyway,本文也是我先发在公司 notion 的,先抄自己一篇,不那么费神。
背景
One day,公司要求所有的 repo 都要在 Jenkins 打开 sonar 的配置,这需要我们给 repo 的 Jenkins 文件添加多一个配置,然后提 PR。天呐,我们 team 有 7-80 个 repo ,简直是噩梦。所以,我们需要自动化。automizely!
Flow
先来看看我司的 git flow,就是标准的 github flow。下图所示的 1 – 4 步骤的自动化都比较好实现,不涉及到复杂的逻辑。比较复杂的在于 clone 到本地后,需要自己写代码来批量修改每个 repo 的文件。对于后者,每次的逻辑都不太一样,自行实现即可。
步骤
1. 准备 repo list 文件
- 我们将需要处理的 repo 放到一个文件里,例如
all-repo.txt
,每行是一个 repo 名。
repo-name1
repo-name2
repo-name3
2. 将 all-repo.txt
复制一份为 all-repo-cur.txt
, 等会我们的脚本就用这份 cur
,目的是等会处理过程中可能会因为中间有个 repo 意外失败,需要重新跑,我们就可以把已经成功的 repo 从 cur
文件中移除。注意 cur
文件最后一行必须是空行,避免 shell 命令读取不到最后一行的 repo
2. 装好必备工具
安装 github 的 cli
brew install gh
## 按提示登录
gh auth login
新建一个 index.sh
文件,将以下方法粘贴进去,等会要用
gclone () {
git clone $1
dirname=$(echo -n $1 | awk -F'/' '{print $NF}' | awk -F'.git' '{print $(NF-1)}')
cd $dirname
upstreamUrl=$(echo $1 | sed -r 's|(.*github.com)/.+/(.*)|\1/换成你公司的名/\2|')
git remote add upstream $upstreamUrl
git remote -v
}
nb (){
git fetch upstream
git checkout -b $1 --track upstream/master
}
gp (){
A=`git status | grep "On branch" | sed "s/On branch //" `
git push -u origin $A
}
mkdir -p repos
3. Fork repo to your github account
cd repos
cat ../all-repo-cur.txt | while read line
do
echo $line
gh repo fork 换成你公司的名/${line}
cd -
done
4. Clone all the repos to local
cd repos
cat ../all-repo-cur.txt | while read line
do
echo $line
gclone https://github.com/foamzou/${line}.git # foamzou 替换成你的账号名
cd -
done
5. Modify the file of repos
因为涉及一些需要逻辑判断的地方,shell 不太方便写,就用 js 写了。下边的 demo 是在 Jenkins file 里添加多一个配置。大家按照自己实际需求来编写替换文件即可。
const fs = require('fs');
const execSync = require('child_process').execSync;
const AllRepo = fs.readFileSync('./all-repo-cur.txt').toString().split('\n');
function findFileWithRegex(regex, dir) {
const l = [];
const files = fs.readdirSync(dir);
for (let i = 0; i < files.length; i++) {
const file = files[i];
const filePath = dir + '/' + file;
if (regex.test(file)) {
l.push(filePath);
}
}
return l;
}
// 在 aaa 属性下面新增属性 bbb
function insertPropertyIntoFile(file) {
const content = fs.readFileSync(file).toString();
if (content.indexOf('bbb') > 0) {
return true;
}
const lines = content.split('\n');
const newLines = [];
let isInserted = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.indexOf('aaa') >= 0) {
const find = line.replace('aaa', 'bbb').replace('false', 'true');
newLines.push(line);
newLines.push(find);
isInserted = true;
} else {
newLines.push(line);
}
}
fs.writeFileSync(file, newLines.join('\n'));
return isInserted;
}
AllRepo.forEach(repo => {
const repoPath = `./repos/${repo}`;
try {
const files = findFileWithRegex(/Jenkinsfile*/, repoPath);
files.forEach(file => {
if (!insertPropertyIntoFile(file)) {
console.log(`${file} is not inserted`);
} else {
console.log(`${file} is inserted`);
}
});
} catch (err) {
console.log(err);
}
});
6. Create PR
cd repos
cat ../all-repo-cur.txt | while read line
do
echo "-------------------------------------------------------"
echo "-------------------------------------------------------"
echo $line
cd $line
nb feat/XXX-id-ci-integrate-sonar
git add -A
git commit -am "[XXX-id] ci: integrate sonar"
gp
gh pr create --title "[XXX-id] ci: integrate sonar" -R 换成你公司的名/$line -b ""
cd -
done
The last
为了避免后续可能会用到 RR ID,建议可以将 步骤 6 中的运行结果保存起来 > pr.log
然后可以把 PR 链接集合起来。
cat pr* | grep 'github.com/换成你公司的名/' | grep '/pull/'
因为不太确定改动是否有问题,所以本次 case 我还是把所有 PR 链接提供给 teammate check 改动,手动 merge。
如果大伙在代码里能够 10000% 保证改动是没有问题的,那么完全可以用 gh 命令让有权限 approve 并 merge 的同学用自动化脚本, merge all the pr 。
six