Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

demo #2

Merged
merged 5 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
uses: actions/checkout@v2
with:
ref: main
- uses: webiny/action-post-run@2.0.1
with:
run: rm -rf *.patch
- name: Intall Node JS
uses: actions/setup-node@v2
with:
Expand All @@ -35,4 +38,5 @@ jobs:
- name: Install Dependency
run: npm install
- run: npm run start

env:
COMMAND_FOR_TESTING:
43 changes: 0 additions & 43 deletions .github/workflows/test.yml

This file was deleted.

34 changes: 34 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Auto Merge Approved Pull Request
author: klay-ke
description: Auto merge approved pull request if maintainer comments /merge.
inputs:
dingtalk-access-token:
description: Dingtalk bot access token.
required: true
dingtalk-secret:
description: Dingtalk secret.
required: true
maintainer-team-name:
description: Name of maintainer team.
required: true
gh-token:
description: Github token.
required: true
ci-command:
description: The command to use for running test.
required: false
outputs:
merge-info:
description: Final merge info.
error-log:
description: Error log if build failed with the command passed in inputs.
pass-log:
description: Log if build passed with the command passed in inputs.
merged:
description: Boolean, true if any pr merged.
branding:
color: green
icon: git-pull-request
runs:
using: node12
main: 'index.js'
178 changes: 178 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const github = require('@actions/github');
const repo = github.context.repo;
const exec = require('@actions/exec');
const core = require('@actions/core');
const q = require('q');
const striptags = require('striptags');
const async = require("async");
const { ChatBot } = require('dingtalk-robot-sender');
const fs = require('fs');

// const repoName = repo.repo;
// const ownerName = repo.owner;
// const octokit = github.getOctokit(core.getInput('gh-token'));
// const maintainerTeamName = core.getInput('maintainer-team-name');
// const dingtalkAccessToken = core.getInput('dingtalk-access-token');
// const dingtalkSecret = core.getInput('dingtalk-secret');

const octokit = github.getOctokit('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');
const repoName = "nebula";
const ownerName = "vesoft-inc";
const maintainerTeamName = "nebula-force";
const dingtalkAccessToken = "93b071fc90528b2ecc09a4702692c8b630f0622d7447ab9d957399c2a0043c32";
const dingtalkSecret = 'SECaea7282b5526b290528d6d3149c8a2b73fb1c4ea64e08cdb79d68d5f99b809e1';

const robot = new ChatBot({
webhook: `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkAccessToken}`,
secret: dingtalkSecret
});

let mergeablePr = {};
let failedToMerge = [];

function main() {
q.all([getAllMaintainers(),getAllOpenPrs()])
.then(getMergeablePrs)
.then(() => {
if (Object.keys(mergeablePr).length) {
getAllPatchesAndApply()
.then(runTest)
.then(mergeValidPr)
.then(sendMergeInfoToDingtalk)
} else {
core.setOutput("no mergeable pr");
}
});
}

if (require.main === module) {
main();
}

async function getAllMaintainers() {
return octokit.rest.teams.listMembersInOrg({
org: ownerName,
team_slug: maintainerTeamName,
role: 'maintainer'
}).then(res => {
let maintainerList = [];
res.data.forEach(maintainer => maintainerList.push(maintainer.login));
return maintainerList;
});
}

async function getAllOpenPrs() {
return octokit.rest.search.issuesAndPullRequests({
q: `is:pr+is:open+repo:${ownerName}/${repoName}+review:approved`
}).then(res => {
return res.data.items;
});
}

async function mergeValidPr() {
return async.eachOf(mergeablePr, (pr, prNum) => {
return octokit.rest.pulls.merge({
owner: ownerName,
repo: repoName,
merge_method: 'squash',
pull_number: prNum
}).then((response) => {
if (response.status != '200') {
failedToMerge.push(pr.html_url);
delete mergeablePr[prNum];
}
})
});
}

async function getMergeablePrs(res) {
const maintainerList = res[0];
const prs = res[1];
async.each(prs, pr => {
return octokit.request('GET ' + pr.comments_url)
.then(comments => {
const mergeable = false;
comments.data.forEach(comment => {
const body = striptags(comment.body).trim();
if (body === "/merge" && maintainerList.includes(comment.body.login)) {
mergeable = true;
} else if (body === "/wait a minute" && maintainerList.includes(comment.body.login)) {
mergeable = false;
}
});
if (mergeable) {
mergeablePr[pr.number] = {number: pr.number, html_url: pr.html_url, patch_url: pr.patch_url};
}
});
});
}

async function runTest() {
let defer = q.defer();

let output = '';
let error = '';

const options = {};
options.listeners = {
stdout: (data) => {
output += data.toString();
},
stderr: (data) => {
error += data.toString();
}
};

const returnCode = false;
while (!returnCode) {
returnCode = await exec.exec(process.env.COMMAND_FOR_TESTING);
if (returnCode != 0) {
const kickout = getRandomInt(Object.keys(mergeablePr).length);
const pr = mergeablePr[Object.keys(mergeablePr)[kickout]];
await exec.exec(`git apply -R ${pr.number}.patch`);
failedToMerge.push(pr.html_url);
delete mergeablePr[pr.number];
console.log("build failed with error:");
console.log(error);
}
}
console.log("build passed!");
console.log(output);
defer.resolve();
return defer.promise;
}

async function sendMergeInfoToDingtalk() {
let succeedToMerge = [];
for (const [key, value] of Object.entries(mergeablePr)) {
succeedToMerge.push(value.html_url);
}
if (succeedToMerge.length > 0 || failedToMerge.length > 0) {
let title = "merge info";
let text = "## merge info\n" +
"> merge successfully:\n" +
"> " + succeedToMerge.join() + "\n\n" +
"> failed to merge: \n" +
"> " + failedToMerge.join() + "\n";
let at = {
// "atMobiles": phone,
"isAtAll": false
};
core.setOutput("merge successfully:\n" + succeedToMerge.join() + "\n\n" + "failed to merge: \n" + failedToMerge.join() + "\n");
return robot.markdown(title,text,at);
}
}

async function getAllPatchesAndApply() {
async.eachOf(mergeablePr, (pr, prNum) => {
return octokit.request(`GET ${pr.patch_url}`).then(async response => {
fs.writeFileSync(`${prNum}.patch`, response.data);
mergeablePr[prNum]["patchFile"] = `${prNum}.patch`;
const returnCode = await exec.exec(`git apply ${prNum}.patch`, [], options);
if (returnCode != 0) {
failedToMerge.push(pr.html_url);
delete mergeablePr[pr.number];
}
});
});
}
19 changes: 12 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
{
"name": "git_action_test",
"version": "1.0.0",
"description": "nebula pr merge management",
"main": "src/main.js",
"description": "",
"main": "index.js",
"dependencies": {
"@octokit/core": "^3.5.1",
"async": "^3.2.1",
"dingtalk-robot-sender": "github:x-cold/dingtalk-robot",
"axios": "^0.21.4",
"dingtalk-robot-sender": "^1.2.0",
"follow-redirects": "^1.14.3",
"iconv-lite": "^0.4.24",
"q": "^1.5.1",
"shelljs": "^0.8.4",
"striptags": "^3.2.0",
"safer-buffer": "^2.1.2",
"tunnel": "^0.0.6",
"urlencode": "^1.1.0"
},
"devDependencies": {},
"scripts": {
"start": "node src/main.js"
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/klay-ke/Git_Action_Test.git"
},
"author": "Lingyun Ke",
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/klay-ke/Git_Action_Test/issues"
Expand Down
Loading