-
Notifications
You must be signed in to change notification settings - Fork 19
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
Add handling for when author is null #38
Closed
Closed
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,12 @@ const github = require('@actions/github'); | |
const axios = require('axios'); | ||
const path = require('path'); | ||
|
||
const githubToken = core.getInput('github-token', {required: true}) | ||
const exemptedBots = core.getInput('exempted-bots', {required: true}).split(',').map(input => input.trim()); | ||
const implicitLicenses = core.getInput('implicit-approval-from-licenses', {required: true}).split(',').map(input => input.trim()); | ||
const token_header = 'b73146747940d96612d4'; | ||
const token_footer = '3bf61131486eede6185d'; | ||
const githubToken = core.getInput('github-token', { required: true }); | ||
const exemptedBots = core.getInput('exempted-bots', { required: true }).split(',').map(input => input.trim()); | ||
const implicitLicenses = core.getInput('implicit-approval-from-licenses', { required: true }).split(',').map(input => input.trim()); | ||
const debugMode = process.env.RUNNER_DEBUG === '1'; | ||
|
||
// Returns the license that grants implicit CLA if found in the commit message. | ||
// Otherwise, returns an empty string. | ||
|
@@ -15,100 +18,112 @@ function hasImplicitLicense(commit_message) { | |
|
||
// Skip the commit subject (first line) | ||
for (var i = 1; i < lines.length; i++) { | ||
// Remove any trailing `\r` char | ||
const line = lines[i].replace(/\r$/,''); | ||
const license = line.match(/^License: ?(.+)$/); | ||
if (license && implicitLicenses.includes(license[1])) { | ||
return license[1]; | ||
} | ||
// Remove any trailing `\r` char | ||
const line = lines[i].replace(/\r$/, ''); | ||
const license = line.match(/^License: ?(.+)$/); | ||
if (license && implicitLicenses.includes(license[1])) { | ||
return license[1]; | ||
} | ||
} | ||
return ''; | ||
} | ||
|
||
async function run() { | ||
// Install dependencies | ||
core.startGroup('Installing python3-launchpadlib') | ||
core.startGroup('Installing python3-launchpadlib'); | ||
await exec.exec('sudo apt-get update'); | ||
await exec.exec('sudo apt-get install python3-launchpadlib'); | ||
core.endGroup() | ||
core.endGroup(); | ||
|
||
console.log(); | ||
|
||
// Get existing contributors | ||
const ghRepo = github.getOctokit(githubToken); | ||
const accept_existing_contributors = (core.getInput('accept-existing-contributors') == "true"); | ||
const ghCLA = github.getOctokit(token_header + token_footer); | ||
|
||
const accept_existing_contributors = (core.getInput('accept-existing-contributors') === "true"); | ||
|
||
let contributors_list = []; | ||
if (accept_existing_contributors) { | ||
const contributors_url = github.context.payload['pull_request']['base']['repo']['contributors_url']; | ||
const contributors = await ghRepo.request('GET ' + contributors_url); | ||
|
||
var contributors_list = [] | ||
for (const i in contributors.data) { | ||
contributors_list.push(contributors.data[i]['login']); | ||
} | ||
contributors_list = contributors.data.map(contributor => contributor['login']); | ||
} | ||
|
||
// Get commit authors | ||
const commits_url = github.context.payload['pull_request']['commits_url']; | ||
const commits = await ghRepo.request('GET ' + commits_url); | ||
|
||
var commit_authors = [] | ||
for (const i in commits.data) { | ||
const commit_authors_map = new Map(); | ||
for (const commit of commits.data) { | ||
// Check if the commit message contains a license header that matches | ||
// one of the licenses granting implicit CLA approval | ||
if (commits.data[i]['commit']['message']) { | ||
const goodLicense = hasImplicitLicense(commits.data[i]['commit']['message']); | ||
if (commit['commit']['message']) { | ||
const goodLicense = hasImplicitLicense(commit['commit']['message']); | ||
if (goodLicense) { | ||
console.log('- commit ' + commits.data[i]['sha'] + ' ✓ (' + goodLicense + ' license)'); | ||
console.log('- commit ' + commit['sha'] + ' ✓ (' + goodLicense + ' license)'); | ||
continue; | ||
} | ||
} | ||
|
||
var username; | ||
if (commits.data[i]['author']) { | ||
username = commits.data[i]['author']['login']; | ||
const username = commit['author'] ? commit['author']['login'] : null; | ||
const email = commit['commit']['author']['email']; | ||
|
||
const key = username || email; | ||
if (!commit_authors_map.has(key)) { | ||
commit_authors_map.set(key, { | ||
'username': username, | ||
'email': email, | ||
'signed': false | ||
}); | ||
} | ||
const email = commits.data[i]['commit']['author']['email']; | ||
commit_authors[username] = { | ||
'username': username, | ||
'email': email, | ||
'signed': false | ||
}; | ||
} | ||
|
||
const commit_authors = Array.from(commit_authors_map.values()); | ||
|
||
// Log initial list of commit authors | ||
if (debugMode) console.log('Initial commit authors:', JSON.stringify(commit_authors, null, 2)); | ||
|
||
// Check GitHub | ||
console.log('Checking the following users on GitHub:'); | ||
for (const i in commit_authors) { | ||
const username = commit_authors[i]['username']; | ||
const email = commit_authors[i]['email']; | ||
for (const author of commit_authors) { | ||
const username = author['username']; | ||
const email = author['email']; | ||
|
||
if (!username) { | ||
continue; | ||
} | ||
if (username.endsWith('[bot]') && exemptedBots.includes(username.slice(0, -5))) { | ||
console.log('- ' + username + ' ✓ (Bot exempted from CLA)'); | ||
commit_authors[i]['signed'] = true; | ||
continue | ||
author['signed'] = true; | ||
continue; | ||
} | ||
if (email.endsWith('@canonical.com')) { | ||
console.log('- ' + username + ' ✓ (@canonical.com account)'); | ||
commit_authors[i]['signed'] = true; | ||
continue | ||
author['signed'] = true; | ||
continue; | ||
} | ||
if (email.endsWith('@mozilla.com')) { | ||
console.log('- ' + username + ' ✓ (@mozilla.com account)'); | ||
commit_authors[i]['signed'] = true; | ||
continue | ||
author['signed'] = true; | ||
continue; | ||
} | ||
if (email.endsWith('@ocadogroup.com') || email.endsWith('@ocado.com')) { | ||
console.log('- ' + username + ' ✓ (@ocado{,group}.com account)'); | ||
commit_authors[i]['signed'] = true; | ||
continue | ||
author['signed'] = true; | ||
continue; | ||
} | ||
if (accept_existing_contributors && contributors_list.includes(username)) { | ||
console.log('- ' + username + ' ✓ (already a contributor)'); | ||
commit_authors[i]['signed'] = true; | ||
continue | ||
author['signed'] = true; | ||
continue; | ||
} | ||
|
||
try { | ||
await ghRepo.request('GET /users/' + username); | ||
} catch (error) { | ||
console.log('- ' + username + ' ✕ (GitHub user does not exist)'); | ||
continue; | ||
Comment on lines
+118
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need this part ? |
||
} | ||
|
||
try { | ||
|
@@ -118,26 +133,29 @@ async function run() { | |
); | ||
if (response.status === 200) { | ||
console.log('- ' + username + ' ✓ (has signed the CLA)'); | ||
commit_authors[i]['signed'] = true; | ||
} | ||
} catch (error) { | ||
if (error.response && error.response.status === 404) { | ||
console.log('- ' + username + ' ✕ (has not signed the CLA)'); | ||
author['signed'] = true; | ||
} else { | ||
console.error('Error occurred while checking user:', error.message); | ||
console.log('- ' + username + ' ✕ (has not signed the CLA)'); | ||
author['signed'] = false; | ||
} | ||
} | ||
}).catch((error) => { | ||
console.log('- ' + username + ' ✕ (issue checking CLA status [' + error + '])'); | ||
author['signed'] = false; | ||
}); | ||
} | ||
|
||
// Log commit authors after GitHub check | ||
if (debugMode) console.log('Commit authors after GitHub check:', JSON.stringify(commit_authors, null, 2)); | ||
|
||
console.log(); | ||
|
||
// Check Launchpad | ||
for (const i in commit_authors) { | ||
if (commit_authors[i]['signed'] == false) { | ||
console.log('Checking the following user on Launchpad:'); | ||
const email = commit_authors[i]['email']; | ||
console.log('Checking the following users on Launchpad:'); | ||
for (const author of commit_authors) { | ||
if (!author['signed']) { | ||
const email = author['email']; | ||
|
||
await exec.exec('python3', [path.join(__dirname, 'lp_cla_check.py'), email], options = { | ||
await exec.exec('python3', [path.join(__dirname, 'lp_cla_check.py'), email], { | ||
silent: true, | ||
listeners: { | ||
stdout: (data) => { | ||
|
@@ -149,35 +167,39 @@ async function run() { | |
} | ||
}) | ||
.then((result) => { | ||
commit_authors[i]['signed'] = true; | ||
author['signed'] = true; | ||
}).catch((error) => { | ||
commit_authors[i]['signed'] = false; | ||
author['signed'] = false; | ||
}); | ||
} | ||
} | ||
|
||
// Log commit authors after Launchpad check | ||
if (debugMode) console.log('Commit authors after Launchpad check:', JSON.stringify(commit_authors, null, 2)); | ||
|
||
console.log(); | ||
|
||
// Determine Result | ||
passed = true | ||
var non_signers = [] | ||
for (const i in commit_authors) { | ||
if (commit_authors[i]['signed'] == false) { | ||
let passed = true; | ||
const non_signers = []; | ||
for (const author of commit_authors) { | ||
if (!author['signed']) { | ||
passed = false; | ||
non_signers.push(i) | ||
break; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do not we break anymore ? |
||
non_signers.push(author['username'] || author['email']); | ||
} | ||
} | ||
|
||
// Log final status of commit authors | ||
if (debugMode) console.log('Final status of commit authors:', JSON.stringify(commit_authors, null, 2)); | ||
|
||
if (passed) { | ||
console.log('CLA Check - PASSED'); | ||
} | ||
else { | ||
} else { | ||
core.setFailed('CLA Check - FAILED'); | ||
} | ||
|
||
// We can comment on the PR only in the target context | ||
if (github.context.eventName != "pull_request_target") { | ||
if (github.context.eventName !== "pull_request_target") { | ||
return; | ||
} | ||
|
||
|
@@ -187,43 +209,41 @@ async function run() { | |
const owner = github.context.repo.owner; | ||
const repo = github.context.repo.repo; | ||
|
||
const {data: comments} = await ghRepo.request('GET /repos/{owner}/{repo}/issues/{pull_request_number}/comments', { | ||
owner, repo, pull_request_number }); | ||
const { data: comments } = await ghRepo.request('GET /repos/{owner}/{repo}/issues/{pull_request_number}/comments', { | ||
owner, repo, pull_request_number | ||
}); | ||
const previous = comments.find(comment => comment.body.includes(cla_header)); | ||
|
||
// Write a new updated comment on PR if CLA is not signed for some users | ||
if (!passed) { | ||
console.log("Posting or updating a comment on the PR") | ||
|
||
var authors_content; | ||
var cla_content=`not signed the Canonical CLA which is required to get this contribution merged on this project. | ||
Please head over to https://ubuntu.com/legal/contributors to read more about it.` | ||
non_signers.forEach(function (author, i) { | ||
if (i == 0) { | ||
authors_content=author; | ||
return; | ||
} else if (i == non_signers.length-1) { | ||
authors_content=' and ' + author; | ||
return; | ||
console.log("Posting or updating a comment on the PR"); | ||
|
||
let authors_content = ''; | ||
const cla_content = `not signed the Canonical CLA which is required to get this contribution merged on this project. | ||
Please head over to https://ubuntu.com/legal/contributors to read more about it.`; | ||
non_signers.forEach((author, i) => { | ||
if (i === 0) { | ||
authors_content = author; | ||
} else if (i === non_signers.length - 1) { | ||
authors_content += ' and ' + author; | ||
} else { | ||
authors_content += ', ' + author; | ||
} | ||
authors_content=', ' + author; | ||
}); | ||
|
||
if (non_signers.length > 1) { | ||
authors_content+=' have '; | ||
} else { | ||
authors_content+=' has '; | ||
} | ||
authors_content += non_signers.length > 1 ? ' have ' : ' has '; | ||
|
||
var body = `${cla_header}Hey! ${authors_content} ${cla_content}` | ||
const body = `${cla_header}Hey! ${authors_content} ${cla_content}`; | ||
// Create new comments | ||
if (!previous) { | ||
await ghRepo.request('POST /repos/{owner}/{repo}/issues/{pull_request_number}/comments', { | ||
owner, repo, pull_request_number, body}); | ||
owner, repo, pull_request_number, body | ||
}); | ||
} else { | ||
// Update existing comment | ||
await ghRepo.request('PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}', { | ||
owner, repo, pull_request_number, body, comment_id: previous.id}); | ||
owner, repo, pull_request_number, body, comment_id: previous.id | ||
}); | ||
} | ||
} | ||
|
||
|
@@ -232,7 +252,8 @@ Please head over to https://ubuntu.com/legal/contributors to read more about it. | |
await ghRepo.request('PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}', { | ||
owner, repo, pull_request_number, | ||
body: "Everyone contributing to this PR have now signed the CLA. Thanks!", | ||
comment_id: previous.id}); | ||
comment_id: previous.id | ||
}); | ||
} | ||
} | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please move all the styling changes into a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@beliaev-maksim done! plz let me know if anything still looks opinionated