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

fix: handle error when branch already exists #48

Merged
merged 12 commits into from
Jan 26, 2019
8 changes: 4 additions & 4 deletions src/tasks/processIssueComment/OptionsConfig/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ const ALL_CONTRIBUTORS_RC = '.all-contributorsrc'

const { addContributorWithDetails } = require('all-contributors-cli')

const { AllContributorBotError } = require('../utils/errors')

class OptionsConfig {
constructor({ repository, commentReply }) {
constructor({ repository }) {
this.repository = repository
this.commentReply = commentReply
this.options
this.originalOptionsSha
}
Expand All @@ -22,12 +23,11 @@ class OptionsConfig {
return optionsConfig
} catch (error) {
if (error instanceof SyntaxError) {
this.commentReply.reply(
throw new AllContributorBotError(
`This project's configuration file has malformed JSON: ${ALL_CONTRIBUTORS_RC}. Error:: ${
error.message
}`,
)
error.handled = true
}
throw error
}
Expand Down
104 changes: 61 additions & 43 deletions src/tasks/processIssueComment/Repository/index.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
const { ResourceNotFoundError } = require('../utils/errors')
const {
BranchNotFoundError,
ResourceNotFoundError,
} = require('../utils/errors')

class Repository {
constructor({ repo, owner, github }) {
constructor({ repo, owner, github, defaultBranch }) {
this.github = github
this.repo = repo
this.owner = owner
this.defaultBranch = defaultBranch
this.baseBranch = defaultBranch
}

getFullname() {
return `${this.owner}/${this.repo}`
}

setBaseBranch(branchName) {
this.baseBranch = branchName
}

async getFile(filePath) {
// https://octokit.github.io/rest.js/#api-Repos-getContents
let file
try {
file = await this.github.repos.getContents({
// https://octokit.github.io/rest.js/#api-Repos-getContents
const file = await this.github.repos.getContents({
owner: this.owner,
repo: this.repo,
path: filePath,
ref: this.baseBranch,
})
// Contents can be an array if its a directory, should be an edge case, and we can just crash
const contentBinary = file.data.content
const content = Buffer.from(contentBinary, 'base64').toString()
return {
content,
sha: file.data.sha,
}
} catch (error) {
if (error.status === 404) {
throw new ResourceNotFoundError(filePath, this.full_name)
} else {
throw error
}
}

// Contents can be an array if its a directory, should be an edge case, and we can just crash
const contentBinary = file.data.content
const content = Buffer.from(contentBinary, 'base64').toString()
return {
content,
sha: file.data.sha,
}
}

async getMultipleFiles(filePathsArray) {
Expand All @@ -61,17 +69,23 @@ class Repository {
return multipleFilesByPath
}

async getHeadRef(defaultBranch) {
const result = await this.github.git.getRef({
owner: this.owner,
repo: this.repo,
ref: `heads/${defaultBranch}`,
})
return result.data.object.sha
async getRef(branchName) {
try {
const result = await this.github.git.getRef({
owner: this.owner,
repo: this.repo,
ref: `heads/${branchName}`,
})
return result.data.object.sha
} catch (error) {
if (error.status === 404) {
throw new BranchNotFoundError(branchName)
}
}
}

async createBranch({ branchName, defaultBranch }) {
const fromSha = await this.getHeadRef(defaultBranch)
async createBranch(branchName) {
const fromSha = await this.getRef(this.defaultBranch)

// https://octokit.github.io/rest.js/#api-Git-createRef
await this.github.git.createRef({
Expand Down Expand Up @@ -140,27 +154,33 @@ class Repository {
await Promise.all(createOrUpdateFilesMultiple)
}

async createPullRequest({ title, body, branchName, defaultBranch }) {
const result = await this.github.pulls.create({
owner: this.owner,
repo: this.repo,
title,
body,
head: branchName,
base: defaultBranch,
maintainer_can_modify: true,
})
return result.data.html_url
async createPullRequest({ title, body, branchName }) {
try {
const result = await this.github.pulls.create({
owner: this.owner,
repo: this.repo,
title,
body,
head: branchName,
base: this.defaultBranch,
maintainer_can_modify: true,
})
return result.data.html_url
} catch (error) {
if (error.status === 422) {
console.log('Pull request already open') // eslint-disable-line no-console
return error.data.html_url
} else {
throw error
}
}
}

async createPullRequestFromFiles({
title,
body,
filesByPath,
branchName,
defaultBranch,
}) {
await this.createBranch({ branchName, defaultBranch })
async createPullRequestFromFiles({ title, body, filesByPath, branchName }) {
const branchNameExists = branchName === this.baseBranch
if (!branchNameExists) {
await this.createBranch(branchName)
}

await this.createOrUpdateFiles({
filesByPath,
Expand All @@ -171,9 +191,7 @@ class Repository {
title,
body,
branchName,
defaultBranch,
})

return pullRequestURL
}
}
Expand Down
63 changes: 48 additions & 15 deletions src/tasks/processIssueComment/probot-processIssueComment.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const parseComment = require('./utils/parse-comment')

const {
AllContributorBotError,
BranchNotFoundError,
ResourceNotFoundError,
} = require('./utils/errors')

Expand All @@ -20,7 +21,7 @@ async function processAddContributor({
optionsConfig,
who,
contributions,
defaultBranch,
branchName,
}) {
const { name, avatar_url, profile } = await getUserDetails({
github: context.github,
Expand Down Expand Up @@ -49,31 +50,52 @@ async function processAddContributor({
originalSha: optionsConfig.getOriginalSha(),
}

const safeWho = getSafeRef(who)
const pullRequestURL = await repository.createPullRequestFromFiles({
title: `docs: add ${who} as a contributor`,
body: `Adds @${who} as a contributor for ${contributions.join(
', ',
)}.\n\nThis was requested by ${commentReply.replyingToWho()} [in this comment](${commentReply.replyingToWhere()})`,
filesByPath: filesByPathToUpdate,
branchName: `all-contributors/add-${safeWho}`,
defaultBranch,
branchName,
})

commentReply.reply(
`I've put up [a pull request](${pullRequestURL}) to add @${who}! :tada:`,
)
}

async function probotProcessIssueComment({ context, commentReply }) {
async function setupRepository({ context, branchName }) {
const defaultBranch = context.payload.repository.default_branch
const repository = new Repository({
...context.repo(),
github: context.github,
defaultBranch,
})

try {
await repository.getRef(branchName)
context.log.info(
`Branch: ${branchName} EXISTS, will work from this branch`,
)
repository.setBaseBranch(branchName)
} catch (error) {
if (error instanceof BranchNotFoundError) {
context.log.info(
`Branch: ${branchName} DOES NOT EXIST, will work from default branch`,
)
} else {
throw error
}
}

return repository
}

async function setupOptionsConfig({ repository }) {
const optionsConfig = new OptionsConfig({
repository,
commentReply,
})

try {
await optionsConfig.fetch()
} catch (error) {
Expand All @@ -84,18 +106,31 @@ async function probotProcessIssueComment({ context, commentReply }) {
}
}

return optionsConfig
}

async function probotProcessIssueComment({ context, commentReply }) {
const commentBody = context.payload.comment.body
const parsedComment = parseComment(commentBody)
const { who, action, contributions } = parseComment(commentBody)

if (action === 'add') {
const safeWho = getSafeRef(who)
const branchName = `all-contributors/add-${safeWho}`

const repository = await setupRepository({
context,
branchName,
})
const optionsConfig = await setupOptionsConfig({ repository })

if (parsedComment.action === 'add') {
await processAddContributor({
context,
commentReply,
repository,
optionsConfig,
who: parsedComment.who,
contributions: parsedComment.contributions,
defaultBranch: context.payload.repository.default_branch,
who,
contributions,
branchName,
})
return
}
Expand All @@ -105,7 +140,7 @@ async function probotProcessIssueComment({ context, commentReply }) {
`Basic usage: @all-contributors please add @jakebolam for code, doc and infra`,
)
commentReply.reply(
`For other usage see the [documentation](https://github.com/all-contributors/all-contributors-bot#usage)`,
`For other usages see the [documentation](https://github.com/all-contributors/all-contributors-bot#usage)`,
)
return
}
Expand All @@ -115,9 +150,7 @@ async function probotProcessIssueCommentSafe({ context }) {
try {
await probotProcessIssueComment({ context, commentReply })
} catch (error) {
if (error.handled) {
context.log.debug(error)
} else if (error instanceof AllContributorBotError) {
if (error instanceof AllContributorBotError) {
context.log.info(error)
commentReply.reply(error.message)
} else {
Expand Down
8 changes: 8 additions & 0 deletions src/tasks/processIssueComment/utils/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class ResourceNotFoundError extends AllContributorBotError {
}
}

class BranchNotFoundError extends AllContributorBotError {
constructor(branchName) {
super(`${branchName} does not exist`)
this.name = this.constructor.name
}
}

class UserNotFoundError extends AllContributorBotError {
constructor(username) {
super(`Could not find the user ${username} on github.`)
Expand All @@ -23,6 +30,7 @@ class UserNotFoundError extends AllContributorBotError {

module.exports = {
AllContributorBotError,
BranchNotFoundError,
ResourceNotFoundError,
UserNotFoundError,
}
5 changes: 0 additions & 5 deletions test/tasks/processIssueComment/OptionsConfig/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ describe('ContentFiles', () => {
repo: 'all-contributors-bot-test',
owner: 'all-contributors',
}
const mockCommentReply = {}

test(`Add's new contributor`, async () => {
const optionsConfig = new OptionsConfig({
repository: mockRepository,
commentReply: mockCommentReply,
})

optionsConfig.options = {
Expand Down Expand Up @@ -40,7 +38,6 @@ describe('ContentFiles', () => {
test(`Add's new contributions for contributor`, async () => {
const optionsConfig = new OptionsConfig({
repository: mockRepository,
commentReply: mockCommentReply,
})

optionsConfig.options = {
Expand Down Expand Up @@ -81,7 +78,6 @@ describe('ContentFiles', () => {
test(`If profile URL is missing protocol, add it for them`, async () => {
const optionsConfig = new OptionsConfig({
repository: mockRepository,
commentReply: mockCommentReply,
})
optionsConfig.init()

Expand All @@ -108,7 +104,6 @@ describe('ContentFiles', () => {
test(`Inits the contributor file`, () => {
const optionsConfig = new OptionsConfig({
repository: mockRepository,
commentReply: mockCommentReply,
})

expect(optionsConfig.options).toBeUndefined()
Expand Down
2 changes: 1 addition & 1 deletion test/tasks/processIssueComment/Repository/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('Repository', () => {
repo: 'all-contributors-bot',
owner: 'all-contributors',
github: mockGithub,
defaultBranch: 'master',
})

const verifyBody = body => {
Expand Down Expand Up @@ -95,7 +96,6 @@ describe('Repository', () => {
},
},
branchName: 'all-contributors/add-jakebolam',
defaultBranch: 'master',
})
expect(pullRequestNumber).toEqual(
'https://github.com/all-contributors/all-contributors-bot/pull/1347',
Expand Down
Loading