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

restrict how much lines of code were changed #570

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,22 @@ Needs to be done:
~~~

Currently this check doesn't take configuration.

### Pull Request Size

With the Pull Request Size Restriction check you can block a pull request which is too big. You can restrict three different key metrics from the PR:

- Lines added
- Lines removed
- Files changed

It is configured under `pull-request.size` and supports the following options:

~~~ yaml
pull-request:
size:
max:
additions: 500
deletions: 500
changed_files: 20
~~~
75 changes: 75 additions & 0 deletions server/checks/PullRequestSize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Check, { getPayloadFn } from './Check'
import { PULL_REQUEST } from '../model/GithubEvents'
import { logger } from '../../common/debug'

const CHECK_TYPE = 'pullrequestsize'
const CONTEXT = 'zappr/pr/size'
const info = logger(CHECK_TYPE, 'info')
const error = logger(CHECK_TYPE, 'error')
const debug = logger(CHECK_TYPE)
const createStatePayload = getPayloadFn(CONTEXT)


export default class PullRequestSize extends Check {
static TYPE = CHECK_TYPE;
static CONTEXT = CONTEXT;
static HOOK_EVENTS = [PULL_REQUEST];
static NAME = 'Pull request size check'

constructor(github) {
super()
this.github = github;
}

async checkPRSizeAndSetStatus({pull_request, repository, token, config}) {
const {fullName, name, owner} = repository
const {number, additions, deletions, changed_files} = pull_request
const max_additions = getIn(config, ['pull-request', 'size', 'max', 'additions'], [])
const max_deletions = getIn(config, ['pull-request', 'size', 'max', 'deletions'], [])
const max_changed_files = getIn(config, ['pull-request', 'size', 'max', 'changed_files'], [])

const checkEnabled = max_additions.length > 0 ? max_deletions.length > 0 ? max_changed_files.length > 0 : false : false
let msg, status;

if (checkEnabled) {
if (additions > max_additions) {
info(`${fullName}#${number}: Failed the PR due to more additions than allowed.`);
status = createStatePayload(`PR adds too much lines.`, 'failure');
} else if (deletions > max_deletions) {
info(`${fullName}#${number}: Failed the PR due to more deletions than allowed.`);
status = createStatePayload(`PR deletes too much lines.`, 'failure');
} else if (changed_files > max_changed_files) {
info(`${fullName}#${number}: Failed the PR due to more files changed than allowed.`);
status = createStatePayload(`PR changes too much files.`, 'failure');
} else {
msg = `PR Size restrictions are met.`;
info(`${fullName}#${number}: ${msg}`);
status = createStatePayload(msg);
}
} else {
info(`${fullName}#${number}: No PR size settings set.`);
status = createStatePayload(`No PR size settings set.`);
}

await this.github.setCommitStatus(owner.login, name, pull_request.head.sha, status, token);
info(`${fullName}#${number}: Set status to ${status.state}.`)
}

async execute(config, hookPayload, token) {
const {action, repository, number, pull_request} = hookPayload
const repoOwner = repository.owner.login
const repoName = repository.name
const fullName = repository.full_name
let status;

try {
if (pull_request.state === 'open' && ['opened', 'edited', 'reopened', 'synchronize'].indexOf(action) !== -1) {
await this.checkPRSizeAndSetStatus({pull_request, repository, token, config})
}
} catch (e) {
error(`${fullName}#${number}: Could not execute ${NAME}`, e)
status = createStatePayload(`Error: ${e.message}`, 'error')
await this.github.setCommitStatus(repoOwner, repoName, pull_request.head.sha, status, token);
}
}
}
9 changes: 6 additions & 3 deletions server/checks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import PullRequestMilestone from './PullRequestMilestone'
import PullRequestLabels from './PullRequestLabels'
import PullRequestMergeCommit from './PullRequestMergeCommit'
import PullRequestTasks from './PullRequestTasks'
import PullRequestSize from './PullRequestSize'

const CHECKS = {
[Approval.TYPE]: Approval,
Expand All @@ -15,7 +16,8 @@ const CHECKS = {
[PullRequestMilestone.TYPE]: PullRequestMilestone,
[PullRequestLabels.TYPE]: PullRequestLabels,
[PullRequestMergeCommit.TYPE]: PullRequestMergeCommit,
[PullRequestTasks.TYPE]: PullRequestTasks
[PullRequestTasks.TYPE]: PullRequestTasks,
[PullRequestSize.TYPE]: PullRequestSize
}

export const CHECK_NAMES = {
Expand All @@ -26,7 +28,8 @@ export const CHECK_NAMES = {
[PullRequestMilestone.TYPE]: PullRequestMilestone.NAME,
[PullRequestLabels.TYPE]: PullRequestLabels.NAME,
[PullRequestMergeCommit.TYPE]: PullRequestMergeCommit.NAME,
[PullRequestTasks.TYPE]: PullRequestTasks.NAME
[PullRequestTasks.TYPE]: PullRequestTasks.NAME,
[PullRequestSize.TYPE]: PullRequestSize.NAME
}

export const CHECK_TYPES = Object.keys(CHECKS)
Expand All @@ -35,4 +38,4 @@ export function getCheckByType(type) {
return CHECKS[type]
}

export { Approval, Autobranch, CommitMessage, Specification, PullRequestMilestone, PullRequestLabels, PullRequestMergeCommit, PullRequestTasks }
export { Approval, Autobranch, CommitMessage, Specification, PullRequestMilestone, PullRequestLabels, PullRequestMergeCommit, PullRequestTasks, PullRequestSize }
15 changes: 15 additions & 0 deletions test/server/pullrequestsize.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import sinon from 'sinon'
import { expect } from 'chai'
import { GithubService } from '../../server/service/GithubService'
import PullRequestSize, { generateStatus } from '../../server/checks/PullRequestSize'


describe('Pull Request Size', () => {
describe('#generateStatus', () => {
it('generates failure when there are redundant labels and additional = false', () => {
const status = generateStatus(labels, {size: {max: {additions: 10}}})
expect(status.description).to.equal(`PR has redundant labels: work-in-progress.`)
expect(status.state).to.equal('failure')
})
})
})