Skip to content

Commit

Permalink
Add the ability to synch Protected Branches
Browse files Browse the repository at this point in the history
  • Loading branch information
jwsloan committed Mar 30, 2018
1 parent 667b4df commit 9b96faf
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 1 deletion.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,34 @@ teams:
permission: admin
- name: docs
permission: push

branches:
- name: 'master'
# https://developer.github.com/v3/repos/branches/#update-branch-protection
# Leaving this empty will disable branch protection for this branch.
protection:
# Required. Require status checks to pass before merging. Set to null to disable
required_status_checks:
# Required. Require branches to be up to date before merging.
strict: true
# Required. The list of status checks to require in order to merge into this branch
contexts: []
# Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable.
enforce_admins: true
# Required. Require at least one approving review on a pull request, before merging. Set to null to disable.
required_pull_request_reviews:
# Specify which users and teams can dismiss pull request reviews. Pass an empty dismissal_restrictions object to disable. User and team dismissal_restrictions are only available for organization-owned repositories. Omit this parameter for personal repositories.
dismissal_restrictions:
users: []
teams: []
# Dismiss approved reviews automatically when a new commit is pushed.
dismiss_stale_reviews: true
# Blocks merge until code owners have reviewed.
require_code_owner_reviews: true
# Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable.
restrictions:
users: []
teams: []
```
**WARNING:** Note that this app inherently _escalates anyone with `push` permissions to the **admin** role_, since they can push config settings to the `master` branch, which will be synced. In a future, we may add restrictions to allow changes to the config file to be merged only by specific people/teams, or those with **admin** access _(via a combination of protected branches, required statuses, and branch restrictions)_. Until then, use caution when merging PRs and adding collaborators.
Expand Down
25 changes: 25 additions & 0 deletions lib/plugins/branches.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = class Branches {
constructor (github, repo, settings) {
this.github = github
this.repo = repo
this.branches = settings
}

sync () {
return Promise.all(
this.branches
.filter(branch => branch.protection !== undefined)
.map((branch) => {
let params = Object.assign(this.repo, { branch: branch.name })

if (Object.keys(branch.protection).length === 0) {
this.github.repos.removeBranchProtection(params)
} else {
Object.assign(params, branch.protection)

this.github.repos.updateBranchProtection(params)
}
})
)
}
}
3 changes: 2 additions & 1 deletion lib/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Settings.PLUGINS = {
repository: require('./plugins/repository'),
labels: require('./plugins/labels'),
collaborators: require('./plugins/collaborators'),
teams: require('./plugins/teams')
teams: require('./plugins/teams'),
branches: require('./plugins/branches')
}

module.exports = Settings
118 changes: 118 additions & 0 deletions test/lib/plugins/branches.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@

const Branches = require('../../../lib/plugins/branches')

describe('Branches', () => {
let github

function configure (config) {
return new Branches(github, {owner: 'bkeepers', repo: 'test'}, config)
}

beforeEach(() => {
github = {
repos: {
updateBranchProtection: jest.fn().mockImplementation(() => Promise.resolve()),
removeBranchProtection: jest.fn().mockImplementation(() => Promise.resolve())
}
}
})

describe('sync', () => {
it('syncs branch protection settings', () => {
const plugin = configure(
[{
name: 'master',
protection: {
required_status_checks: {
strict: true,
contexts: ['travis-ci']
},
enforce_admins: true,
required_pull_request_reviews: {
require_code_owner_reviews: true
}
}
}]
)

return plugin.sync().then(() => {
expect(github.repos.updateBranchProtection).toHaveBeenCalledWith({
owner: 'bkeepers',
repo: 'test',
branch: 'master',
required_status_checks: {
strict: true,
contexts: ['travis-ci']
},
enforce_admins: true,
required_pull_request_reviews: {
require_code_owner_reviews: true
}
})
})
})

describe('when the "protection" config is empty', () => {
it('removes branch protection', () => {
const plugin = configure(
[{
name: 'master',
protection: {}
}]
)

return plugin.sync().then(() => {
expect(github.repos.updateBranchProtection).not.toHaveBeenCalled()
expect(github.repos.removeBranchProtection).toHaveBeenCalledWith({
owner: 'bkeepers',
repo: 'test',
branch: 'master'
})
})
})
})

describe('when the "protection" key is not present', () => {
it('makes no change to branch protection', () => {
const plugin = configure(
[{
name: 'master'
}]
)

return plugin.sync().then(() => {
expect(github.repos.updateBranchProtection).not.toHaveBeenCalled()
expect(github.repos.removeBranchProtection).not.toHaveBeenCalled()
})
})
})

describe('when multiple branches are configured', () => {
it('updates them each appropriately', () => {
const plugin = configure(
[
{
name: 'master',
protection: { enforce_admins: true }
},
{
name: 'other',
protection: { enforce_admins: false }
}
]
)

return plugin.sync().then(() => {
expect(github.repos.updateBranchProtection).toHaveBeenCalledTimes(2)

expect(github.repos.updateBranchProtection).toHaveBeenLastCalledWith({
owner: 'bkeepers',
repo: 'test',
branch: 'other',
enforce_admins: false
})
})
})
})
})
})

0 comments on commit 9b96faf

Please sign in to comment.