stash-creview
Version:
CLI for creating pull requests with random reviewers in Atlassian Stash
198 lines (188 loc) • 7.23 kB
JavaScript
;
var Promise = require('bluebird'),
fs = Promise.promisifyAll(require('fs')),
path = require('path'),
_ = require('lodash'),
program = require('commander'),
open = require('open'),
utils = require('./utils'),
logger = require('./logger').logger,
git = require('gift'), // bluebird doesn't work with this library
stashApiModule = require('stash-api'),
// stashApiModule = require('../../stash-api/src/app'), // for local development
StashApi = stashApiModule.StashApi,
PullRequest = stashApiModule.models.PullRequest,
stash = new StashApi(process.env['STASH_CREVIEW_PROTOCOL'],
process.env['STASH_CREVIEW_HOST'],
process.env['STASH_CREVIEW_PORT'],
process.env['STASH_CREVIEW_USERNAME'],
process.env['STASH_CREVIEW_PASSWORD']);
var hasError = false,
staticReviewers = (process.env['STASH_CREVIEW_STATIC_REVIEWERS'] || '').split(',');
program
.version('1.0.0')
.option('-t, --ticket [id]', 'id of the ticket this is for. e.g. creview -t TICK-1234')
.option('-m, --message [mes]', 'Message or title of the PR. e.g. creview -m "Fixing it all"')
.option('-s, --sections [secs]', 'Section of code. Options are defined in your .creview-config file. e.g. creview -s UI,QA')
.option('-f, --force', 'Force the PR even if the current branch has uncommited changes. You probably don\'t want to do this.')
.option('-i, --ignore-static-reviewers', 'Ignore any static reviewers specified for this PR only.')
.parse(process.argv);
if (!program.sections) {
program.sections = '';
}
if (program.ignoreStaticReviewers) {
staticReviewers = [];
}
// don't continue if there is any error with the input
if (hasError) {
return;
}
// git repo root path => git rev-parse --show-toplevel
utils.getRepoRoot()
.then(function (repoPath) {
logger.debug('got a repo root', {
root: repoPath
});
return [
fs.readFileAsync(path.join(repoPath + '/.creview-config')).then(function(result) { return JSON.parse(result, 'utf8'); }),
repoPath
];
})
// verify our current status is clean!
.spread(function (repoConfig, repoPath) {
return new Promise(function (resolve, reject) {
git(repoPath)
.status(function (err, status) {
var errorMessage = 'Your current branch is not clean. You can overide this message with the -f flag. Exiting now...';
if (err) {
throw new Error('An error ocurred while getting the status of the repo.');
}
if (!status.clean && !program.force) {
reject(errorMessage);
}
resolve([repoConfig, repoPath]);
});
});
})
// get the default branch
.spread(function (repoConfig, repoPath) {
// branch that the current branch should request a pull from => config file at repo root, the default parent repo?
// can override with CLI param
var defaultBranchPromise = stash.getDefaultBranch(repoConfig.projectKey, repoConfig.slug)
.spread(function (response, body) {
if (response.statusCode !== 200) {
logger.debug('getDefaultBranch', {
response: response
});
throw new Error('Error while getting the default branch');
}
logger.debug('default branch', {
branchJSON: body
});
return body;
});
return [repoConfig, repoPath, defaultBranchPromise];
})
.spread(function (repoConfig, repoPath, defaultBranch) {
// current branch => https://www.npmjs.com/package/gift#repobranchbranch-callback
return new Promise(function (resolve) {
git(repoPath)
.branch(function (err, branch) {
var match;
logger.debug('current branch', {
currentBranch: branch.name
});
// attempt to determine the ticket
// number based on the current branch name
if (!program.ticket) {
if (!repoConfig.parseBranchRegex) {
throw new Error('No ticket number set and the parseBranchRegex was no defined in the .creview-config in your repo root.');
} else {
match = branch.name.match(new RegExp(repoConfig.parseBranchRegex, 'i'));
if (!match) {
throw new Error('No ticket number set and could not infer the ticket based on the branch name and the parseBranchRegex value in the .creview-config.');
}
program.ticket = match[0];
}
}
resolve([repoConfig, repoPath, branch, defaultBranch]);
});
});
})
.spread(function (repoConfig, repoPath, currentBranch, defaultBranch) {
var sections;
// get reviewers
sections = program.sections.split(',');
return [repoConfig, repoPath, currentBranch, defaultBranch, utils.getReviewers(process.env['STASH_CREVIEW_USERNAME'], stash, repoConfig, sections, staticReviewers), sections];
})
.spread(function (repoConfig, repoPath, currentBranch, defaultBranch, reviewers, sections) {
var pr;
logger.debug('reviewers found: ', {
reviewers: reviewers
});
if (!reviewers || reviewers.length < 1) {
throw new Error('No reviewers found for ' + program.sections);
} else if (reviewers && reviewers.length === 1 && reviewers[0] === '__nada__') {
reviewers = [];
}
// add any static reviewers
_.each(staticReviewers, function (sr) {
if (sr) {
reviewers.push({
user: {
name: sr
}
});
}
});
// create PR
pr = new PullRequest();
pr.title = generateTitle(sections, program.ticket, program.message);
pr.reviewers = reviewers;
pr.fromRef = {
id: 'refs/heads/' + currentBranch.name,
repository: {
slug: repoConfig.slug,
project: {
key: repoConfig.projectKey
}
}
};
pr.toRef = {
id: defaultBranch.id,
repository: {
slug: repoConfig.slug,
project: {
key: repoConfig.projectKey
}
}
};
stash.createPullRequest(repoConfig.projectKey, repoConfig.slug, pr)
.spread(function (response, body) {
if (response.statusCode === 201) {
logger.info('successfully created pull request', {
body: body
});
// open chrome on the user's machine to the PR url
open(body.links.self[0].href);
} else {
logger.error('boooooo, error while creating pull request', {
body: body,
statusCode: response.statusCode
});
}
});
})
.catch(function (err) {
logger.error('error in main: ' + err);
});
function generateTitle (sections, ticket, message) {
var result = '';
_.each(sections, function (val) {
if (val) {
result += '[' + val + ']';
}
});
return result + ' ' + ticket + ' - ' + (message || '');
}