pull-request-stash
Version:
send a pull request from cmd line to git of stash
590 lines (575 loc) • 21.7 kB
JavaScript
/**
* Created by liangkuaisheng on 16/4/13.
*/
;
var colors = require('colors');
var Buffer = require('buffer').Buffer;
var _ = require('lodash');
var Promise = require("bluebird");
var request = Promise.promisifyAll(require('request'));
var inquirer = require('inquirer');
var fsextra = require('fs-extra');
var CryptoJS = require("crypto-js");
var git = require("git-promise");
var defaultOpt = {
protocol: 'http',
server: 'localhost',
port: '7990',
username: '',
password: '',
projectKey: '',
repositorySlug: ''
};
var defaultReq = {
url: '',
body: '',
headers: {
'Content-Type': 'application/json'
}
};
var defaultPr = {
title: '',
description: '',
state: 'OPEN',
open: true,
closed: false,
fromRef: {},
toRef: {},
locked: false,
reviewers: []
};
function PullRequestStash (options, langOpt) {
//protocol, server, port, username, password
// http://git.xxx.com/projects/projectKey/repos/repositorySlug/pull-requests?create
var defaultLangOpt = {
SEND_TO_GIT: 'send request to git...',
RES_FROM_GIT: 'response from git...',
CREATE_PR_SUC: 'create pull request success!',
TEST_PR_STATUS: 'test pull request status.',
HAS_CONFLICTS: 'there are conflicts!',
HAS_NO_CONFLICT: 'there is no conflict!',
GET_CONFLICT_INFO_FAIL: 'fail to get test conflict info!',
CANCEL_PR_OR_NOT: 'There are conflicts, do you want to cancel the pull request?',
CANCEL_PR: 'decline pull request.',
CANCEL_PR_SUC: 'success to decline conflict!',
CANCEL_PR_FAIL: 'fail to decline conflict, please do it manually !',
ASK_REVIEWERS: 'Create Pull Request Add Reviewers. If need no reviewer Click Enter ?',
ASK_REVIEWERS_MORE: 'Create Pull Request Add Reviewers(@xxx@yyy). If need no reviewer Click Enter ?',
INPUT_TYPE: 'input should like @xxx@yyy',
ASK_USR_NAME: 'Your Git User Name?',
NOT_EMP: 'Should Not Be Empty!',
USR_NAME_TYPE: 'git username only contain number,letter, . and _',
ASK_PASS: 'Your Git Password?',
ASK_FROM_BR: 'Create Pull Request From Branch ?',
ASK_TO_BR: 'Create Pull Request To Branch ?',
READ_GIT_FAIL: 'warning: when read commit-log of branch , error! Please input description',
ASK_DESCRIPTION: 'Pull Request Description?',
ASK_DESCRIPTION_MORE: 'Pull Request Description. Default (click Enter) set different commits log of two branches to description?',
ASK_TITLE: 'Pull Request Title ?'
};
var Language = defaultLangOpt;
if (langOpt) {
Language = _.assign(defaultLangOpt, langOpt);
}
this.Language = Language;
this.opt = _.assign(defaultOpt, options);
var reviewersAskArr = [];
this.opt.reviewers = this.opt.reviewers || [];
this.opt.reviewers.forEach(function (reviewer) {
reviewersAskArr.push({
name: reviewer.name + ' @' + reviewer.displayName,
value: reviewer,
checked: reviewer.checked
});
});
this.opt.reviewersAskArr = reviewersAskArr;
}
PullRequestStash.prototype.send = function (prInfo, options) {
var Language = this.Language;
var pr = _.assign(_.cloneDeep(defaultPr), prInfo);
var req = _.cloneDeep(defaultReq);
var opt = this.opt;
if (options) {
opt = _.assign(opt, options);
}
req.url = opt.protocol + '://' + opt.server + ':' + opt.port + '/rest/api/1.0' + '/projects/' +
opt.projectKey + '/repos/' + opt.repositorySlug + '/pull-requests';
req.body = JSON.stringify(pr);
req.headers['Content-Length'] = Buffer.byteLength(req.body, 'utf8');
req.headers['Authorization'] = 'Basic ' + new Buffer(opt.username + ':' + opt.password).toString('base64');
console.log(Language.SEND_TO_GIT.blue);
return request.postAsync(req)
.then(function (response) {
console.log(Language.RES_FROM_GIT.blue);
var bodyObj = {};
var url = '';
var prId = 0;
var prVersion = -1;
if (response.statusCode === 201) {
try {
bodyObj = JSON.parse(response.body);
url = bodyObj.links.self[0].href;
prId = bodyObj.id;
prVersion = bodyObj.version;
} catch (err) {
bodyObj = {};
}
opt.password = '';
console.log(Language.CREATE_PR_SUC.green);
console.log(('URL: ' + url).green);
return {
status: 0,
msg: 'success',
data: {
opt: opt,
pr: pr,
url: url,
id: prId,
version: prVersion
}
};
} else {
try {
bodyObj = JSON.parse(response.body);
} catch (err) {
bodyObj = {};
}
return {
status: 1,
data: {
opt: opt,
pr: pr
},
msg: bodyObj.errors
};
}
})
.then(function (prRes) {
if (prRes && prRes.status !== 0) {
return prRes;
}
var prDiffReq = _.cloneDeep(req);
prDiffReq.url += '/' + prRes.data.id + '/diff';
delete prDiffReq.body;
delete prDiffReq.headers['Content-Length'];
console.log(Language.TEST_PR_STATUS.blue);
return request.getAsync(prDiffReq)
.then(function (response) {
if (response.statusCode === 200) {
try {
if (/"line":"<<<<<<<","truncated":false,"conflictMarker":"MARKER"/.test(response.body)) {
console.log(Language.HAS_CONFLICTS.red);
return {
status: 2,
data: prRes.data,
msg: Language.HAS_CONFLICTS + ' url:' + prRes.data.url
};
} else {
console.log(Language.HAS_NO_CONFLICT.green);
return prRes;
}
} catch (err) {
console.log(Language.GET_CONFLICT_INFO_FAIL.red);
return prRes;
}
} else {
console.log(Language.GET_CONFLICT_INFO_FAIL.red);
return prRes;
}
});
})
.then(function (prRes) {
if (prRes && prRes.status !== 2) {
return prRes;
}
return inquirer.prompt([{
type: 'confirm',
name: 'cancelPR',
message: Language.CANCEL_PR_OR_NOT,
}])
.then(function (res) {
if (res.cancelPR) {
return {
status: 3,
data: prRes.data,
msg: Language.HAS_CONFLICTS + ' url:' + prRes.data.url
};
} else {
prRes.status = 0;
return prRes;
}
});
})
.then(function (prRes) {
if (prRes && prRes.status !== 3) {
return prRes;
}
var prCancelReq = _.cloneDeep(req);
prCancelReq.url += '/' + prRes.data.id + '/decline?version=' + prRes.data.version;
prCancelReq.body = JSON.stringify({
pullRequestId: prRes.data.id
});
prCancelReq.headers['Content-Length'] = Buffer.byteLength(prCancelReq.body, 'utf8');
console.log(Language.CANCEL_PR.blue);
return request.postAsync(prCancelReq)
.then(function (response) {
if (response.statusCode === 200) {
console.log(Language.CANCEL_PR_SUC.green);
} else {
console.log(Language.CANCEL_PR_FAIL.red);
}
return prRes;
});
})
.catch(function (err) {
if (err) throw err;
});
};
PullRequestStash.prototype.createAndSend = function (silence) {
var self = this;
var Language = self.Language;
var opt = self.opt;
var askArr = [];
var info = {};
function actions (res) {
info.currentUser = res[0];
info.branch = res[1];
if (opt.reviewersAskArr && opt.reviewersAskArr.length > 0) {
askArr.push({
type: 'checkbox',
name: 'reviewers',
message: Language.ASK_REVIEWERS,
choices: opt.reviewersAskArr,
filter: function (val) {
var resObj = {};
var resArr = [];
val.forEach(function (item) {
if (item.groupType) {
item.users.forEach(function (subItem) {
resObj[subItem.name] = {
user: subItem
};
});
} else {
resObj[item.name] = {
user: item
};
}
});
_.forEach(resObj, function (value, key) {
resArr.push(value);
});
return resArr;
}
});
} else {
askArr.push({
type: 'input',
name: 'reviewers',
message: Language.ASK_REVIEWERS_MORE,
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.INPUT_TYPE;
}
return true;
},
filter: function (val) {
if (val === '' ||
val === null ||
val === undefined) {
return '[]';
}
if (!(/^(@\w+)*$/.test(val))) {
return '';
}
var resObj = {};
var valArr = val.split('@');
var resArr = [];
valArr.forEach(function (item) {
if (item) {
resObj[item] = {
user: {
name: item,
displayName: item
}
};
}
});
_.forEach(resObj, function (value, key) {
resArr.push(value);
});
return JSON.stringify(resArr);
}
});
}
askArr.push({
type: 'input',
name: 'username',
message: Language.ASK_USR_NAME,
default: opt.username || info.currentUser,
when: function (obj) {
var name = opt.username || info.currentUser || '';
return !(/^\w+$/.test(name) && silence);
},
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.NOT_EMP;
}
if (!(/^[\w.]+$/.test(input))) {
return Language.USR_NAME_TYPE;
}
return true;
}
});
askArr.push({
type: 'password',
name: 'password',
message: Language.ASK_PASS,
when: function (obj) {
return !opt.password;
},
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.NOT_EMP;
}
return true;
}
});
askArr.push({
type: 'input',
name: 'fromBranch',
message: Language.ASK_FROM_BR,
default: info.branch,
when: function (obj) {
return !(info.branch && silence);
},
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.NOT_EMP;
}
return true;
}
});
askArr.push({
type: 'input',
name: 'toBranch',
message: Language.ASK_TO_BR,
default: opt.defaultBranch || 'master',
when: function (obj) {
return !(opt.defaultBranch && silence);
},
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.NOT_EMP;
}
return true;
}
});
return inquirer.prompt(askArr)
.then(function (result) {
if (!result.reviewers) {
result.reviewers = [];
} else {
if (_.isString(result.reviewers)) {
result.reviewers = JSON.parse(result.reviewers);
}
}
if (!result.username) {
result.username = opt.username || info.currentUser;
}
if (!result.password) {
result.password = opt.password;
}
if (!result.fromBranch) {
result.fromBranch = info.branch;
}
if (!result.toBranch) {
result.toBranch = opt.defaultBranch || 'master';
}
return git('log origin/' + result.toBranch + '..origin/' + result.fromBranch + ' --pretty=format:"%s" --graph', function (stdout) {
result.defaultDescription = stdout;
result.descriptionMsg = '';
return result;
})
.fail(function (err) {
console.log(Language.READ_GIT_FAIL.yellow);
result.defaultDescription = '';
result.descriptionMsg = Language.ASK_DESCRIPTION;
return result;
});
})
.then(function (resul) {
var askArrNext = [];
var titleStr = resul.fromBranch + ' to ' + resul.toBranch + ' by ' + resul.username;
var descriptionMsg = resul.descriptionMsg || Language.ASK_DESCRIPTION_MORE;
askArrNext.push({
type: 'input',
name: 'title',
message: Language.ASK_TITLE,
default: opt.title || titleStr,
when: function (obj) {
return !silence;
},
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.NOT_EMP;
}
return true;
}
});
askArrNext.push({
type: 'input',
name: 'description',
when: function (obj) {
return !(silence && resul.defaultDescription !== '');
},
message: descriptionMsg
});
return inquirer.prompt(askArrNext)
.then(function (res) {
resul.title = res.title || opt.title || titleStr;
resul.description = res.description || resul.defaultDescription;
resul.description = _.truncate(resul.description, {'length': 500});
delete resul.defaultDescription;
delete resul.descriptionMsg;
return resul;
});
})
.then(function (resu) {
return self.send({
title: resu.title,
description: resu.description,
reviewers: resu.reviewers || [],
fromRef: {
id: resu.fromBranch,
repository: {
slug: opt.repositorySlug,
project: {
key: opt.projectKey
}
}
},
toRef: {
id: resu.toBranch,
repository: {
slug: opt.repositorySlug,
project: {
key: opt.projectKey
}
}
}
}, {
username: resu.username,
password: resu.password
});
});
}
return Promise.all([
git('config --get user.name', function (stdout) {
var nameStr = '';
if (stdout) {
var arr = stdout.match(/^(.+)\n$/);
if (arr && arr.length === 2) {
nameStr = arr[1];
}
}
return nameStr;
}),
git('status -b', function (stdout) {
var branchStr = '';
if (stdout) {
var arr = stdout.match(/^On\sbranch\s(.+)\n/);
if (arr && arr.length > 1) {
branchStr = arr[1];
}
}
return branchStr;
})
])
.then(function (res) {
return actions(res);
})
.catch(function (err) {
var res = ['', '']
return actions(res);
});
};
PullRequestStash.createKey = function (keyFilePath, langOpt) {
var defaultLangOpt = {
SET_NEW_PASS: 'Please Set New Stash Password?',
SET_PASS_AGAIN: 'Please Input Password Again?',
PASS_NOT_EMP: 'Password Canot Be Empty!',
PASS_DIFF: 'Two Password Different,Please Try Again!',
WRITE_OK: 'Write File OK !',
SET_PASS_OK: 'Set New Password Ok!'
};
var Language = defaultLangOpt;
if (langOpt) {
Language = _.assign(defaultLangOpt, langOpt);
}
var passTemp = '';
inquirer.prompt([
{
type: 'password',
name: 'password',
message: Language.SET_NEW_PASS,
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.PASS_NOT_EMP;
}
passTemp = input;
return true;
}
},
{
type: 'password',
name: 'password2',
message: Language.SET_PASS_AGAIN,
validate: function (input) {
if (input === '' ||
input === null ||
input === undefined) {
return Language.PASS_NOT_EMP;
}
if (input !== passTemp) {
return Language.PASS_DIFF;
}
return true;
}
}
])
.then(function (res) {
if (res.password === res.password2) {
fsextra.outputFile(keyFilePath, '{"key": "' + CryptoJS.AES.encrypt(res.password, keyFilePath) + '"}', function (err) {
if (err) throw err;
console.log(Language.WRITE_OK.green);
console.log(Language.SET_PASS_OK.green);
});
} else {
console.log(Language.PASS_DIFF.red);
}
})
.catch(function (err) {
if (err) throw err;
});
};
PullRequestStash.getKey = function (keyFilePath) {
var keyObj = fsextra.readJSONSync(keyFilePath);
var bytes = CryptoJS.AES.decrypt(keyObj.key, keyFilePath);
return bytes.toString(CryptoJS.enc.Utf8);
};
module.exports = PullRequestStash;