Skip to content

Instantly share code, notes, and snippets.

@sawyerh
Forked from timaschew/github-helper.js
Created February 3, 2018 20:37
Show Gist options
  • Save sawyerh/30aa65983c8c246515f505a9ea672750 to your computer and use it in GitHub Desktop.
Save sawyerh/30aa65983c8c246515f505a9ea672750 to your computer and use it in GitHub Desktop.
automate a pull request flow via GitHub API - fork, create branch or update from upstream, commit changes, do pull request and optional merge
'use strict'
const Octokat = require('octokat')
const Promise = require('bluebird')
const ORIGIN_USERNAME = 'username-or-organisation'
const ORIGIN_REPO = 'the-origin-repo'
const ORIGIN_BRANCH = 'master'
const WAIT_FOR_FORK = 5
const WAIT_FOR_MERGE = 5
// support nodejs and browser runtime
var base64Encode = function(content) {
if (typeof btoa !== 'undefined') {
return btoa(content)
} else {
return new Buffer(content).toString('base64')
}
}
const init = function(octo) {
const helper = {}
const originRepo = octo.repos(ORIGIN_USERNAME, ORIGIN_REPO)
helper.push = Promise.coroutine(function*(username, branchName, commitMessage, prBody, changeSetArray, autoMerge) {
const fork = yield helper.forkRepo(username)
yield helper.createNewBranch(fork, branchName)
yield helper.commitChanges(fork, branchName, changeSetArray, commitMessage)
return yield helper.doPullRequestAndMerge(branchName, username, commitMessage, prBody, autoMerge)
})
helper.forkRepo = Promise.coroutine(function*(username) {
let fork = null
yield originRepo.forks.create()
var tryCounter = 0
while (fork == null && tryCounter < WAIT_FOR_FORK) {
console.log('waiting until repo is forked')
yield Promise.delay(tryCounter * 1000)
fork = yield octo.repos(username, ORIGIN_REPO).fetch()
tryCounter++
}
if (fork == null) {
console.error('could not fork the origin repo')
return null
}
return fork
})
helper.createNewBranch = Promise.coroutine(function*(fork, branchName) {
var forkCommits = yield fork.commits.fetch({sha: 'master'})
var originCommits = yield originRepo.commits.fetch({sha: 'master'})
if (originCommits[0].sha != forkCommits[0].sha) {
console.log('master branch of fork is not in sync, force updating from upstream')
yield fork.git.refs('heads/master').update({
force: true,
sha: originCommits[0].sha
})
}
var allBranches = yield fork.git.refs.fetch()
var branch = allBranches.filter(function(item) {
var name = item.ref.split('/')[2] // refs/heads/master -> master
return name === branchName
})[0]
if (branch == null) {
console.log('creating branch')
var branch = yield fork.git.refs.create({
ref: 'refs/heads/' + branchName,
sha: originCommits[0].sha // recent commit SHA
})
}
return originCommits[0].sha
})
helper.commitChanges = Promise.coroutine(function*(fork, branchName, changeSetArray, commitMessage) {
const changed = {added: 0, updated: 0, deleted: 0}
for (var i=0; i<changeSetArray.length; i++) {
var changeset = changeSetArray[i]
var config = {
message: commitMessage + '(' + (i+1) + '/' + changeSetArray.length + ')',
content: changeset.delete ? undefined : base64Encode(changeset.payload),
branch: branchName
}
try {
console.log('try to fetching sha from', changeset.path)
var meta = yield fork.contents(changeset.path).fetch({ref: branchName})
config.sha = meta.sha
} catch (err) {
// file seems to be new, so there is no SHA1
}
if (changeset.delete) {
yield fork.contents(changeset.path).remove(config)
changed.deleted++
console.log('deleted file', changeset.path)
} else {
yield fork.contents(changeset.path).add(config)
if (changeset.new) {
changed.added++
console.log('added new file', changeset.path)
} else {
changed.updated++
console.log('updated file', changeset.path)
}
}
}
return
})
helper.mergePullRequest = Promise.coroutine(function*(pullRequest, counter, max) {
if (counter >= max) {
throw new Error('could not merge pull request')
}
try {
return yield originRepo.pulls(pullRequest.number).merge.add({
// commitMessage: 'optional message',
sha: pullRequest.head.sha
})
} catch (err) {
// seems that GitHub is not ready for the merge, just wait and try again
// https://github.com/githubteacher/welcome-june/issues/127
if (err.json != null && err.json.message === 'Base branch was modified. Review and try the merge again.') {
return null
}
throw err
}
})
helper.doPullRequestAndMerge = Promise.coroutine(function*(branchName, username, commitMessage, prBody, autoMerge) {
console.log('checking pulll requests')
var pullRequestArray = yield originRepo.pulls.fetch({head: username + ':' + branchName})
if (pullRequestArray.length === 0) {
console.log('creating pulll request')
const pullRequest = yield originRepo.pulls.create({
title: commitMessage,
body: prBody,
head: username + ":" + branchName,
base: ORIGIN_BRANCH
})
var merged = null
var message = null
var mergeResult = null
if (autoMerge) {
// MERGE BUTTON
var counter = 0
var mergeResult = null
while (mergeResult == null) {
console.log('try to merge pull request', pullRequest.number, pullRequest.head.sha)
yield Promise.delay(1000 * counter)
mergeResult = yield mergePullRequest(pullRequest, counter++, WAIT_FOR_MERGE)
}
if (mergeResult.merged) {
merged = true,
message = 'merged'
} else {
merged = false,
message = mergeResult.message
}
}
return {
ok: true,
created: true,
merged: merged,
message: message,
pr: pullRequest,
}
} else {
return {
created: false,
pr: pullRequestArray
}
}
})
return helper
}
module.exports = function(token) {
return init(new Octokat({token: token}))
}
module.exports.init = init
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment