diff --git a/README.md b/README.md index 222a558..9a94852 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Now whenever you have a chain of branches, list them in `.pr-train.yml` to tell **Pre-requisite**: Create a `${HOME}/.pr-train` file with a single line which is your GH personal access token (you can create one [here](https://github.com/settings/tokens)). The `repo` scope, with ` Full control of private repositories` is needed. -Run `git pr-train -p --create-prs` to create GitHub PRs with a "content table" section. PR titles are taken from the commit message titles of each branch HEAD. You'll be promted before the PRs are created. +Run `git pr-train -p --create-prs` to create GitHub PRs with a "content table" section. PR titles and descriptions are taken from `title` and `body` values of branch configs in `.yml` file detailed further down, or from commit message titles and bodies of each branch HEAD. You'll be promted before the PRs are created. If you run with `--create-prs` again, `pr-train` will only override the Table of Contents in your PR, it will _not_ change the rest of the PR descriptions. @@ -60,6 +60,8 @@ trains: big billing refactoring: - fred_billing-refactor_frontend_bits - fred_billing-refactor_backend_bits + title: BE changes for billing refactor # optional PR title + body: This refactor... # optional PR description - fred_billing-refactor_tests # # ...config for older trains follows... diff --git a/github.js b/github.js index b3e669e..d4e94a7 100644 --- a/github.js +++ b/github.js @@ -9,15 +9,29 @@ const colors = require('colors'); const emoji = require('node-emoji'); const simpleGit = require('simple-git/promise'); +/** + * @typedef {string | Object.} BranchCfg + */ + /** * * @param {simpleGit.SimpleGit} sg - * @param {string} branch + * @param {string} branchName + * @param {Object.} cfgMap * @return Promise.<{title: string, body: string}> */ -async function constructPrMsg(sg, branch) { - const title = await sg.raw(['log', '--format=%s', '-n', '1', branch]); - const body = await sg.raw(['log', '--format=%b', '-n', '1', branch]); +async function constructPrMsg(sg, branchName, cfgMap) { + let title = await sg.raw(['log', '--format=%s', '-n', '1', branchName]); + let body = await sg.raw(['log', '--format=%b', '-n', '1', branchName]); + + // Take PR title and body from the config if title exists there + if (typeof cfgMap[branchName] !== 'string' && cfgMap[branchName].title) { + title = cfgMap[branchName].title; + // If body was not specified in config, but title was, then body from + // git log is no longer needed. + body = cfgMap[branchName].body || ''; + } + return { title: title.trim(), body: body.trim(), @@ -79,9 +93,10 @@ function upsertNavigationInBody(newNavigation, body) { * @param {simpleGit.SimpleGit} sg * @param {Array.} allBranches * @param {string} combinedBranch + * @param {Object.} cfgMap * @param {string} remote */ -async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_REMOTE) { +async function ensurePrsExist(sg, allBranches, combinedBranch, cfgMap, remote = DEFAULT_REMOTE) { //const allBranches = combinedBranch ? sortedBranches.concat(combinedBranch) : sortedBranches; const octoClient = octo.client(readGHKey()); // TODO: take remote name from `-r` value. @@ -94,12 +109,16 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ /** @type string */ let combinedBranchTitle; if (combinedBranch) { - console.log(); - console.log(`Now I will need to know what to call your "combined" branch PR in GitHub.`); - combinedBranchTitle = await promptly.prompt(colors.bold(`Combined branch PR title:`)); - if (!combinedBranchTitle) { - console.log(`Cannot continue.`.red, `(I need to know what the title of your combined branch PR should be.)`); - process.exit(5); + if (!cfgMap[combinedBranch].title) { + console.log(); + console.log(`Now I will need to know what to call your "combined" branch PR in GitHub.`); + combinedBranchTitle = await promptly.prompt(colors.bold(`Combined branch PR title:`)); + if (!combinedBranchTitle) { + console.log(`Cannot continue.`.red, `(I need to know what the title of your combined branch PR should be.)`); + process.exit(5); + } + } else { + combinedBranchTitle = cfgMap[combinedBranch].title } } @@ -114,7 +133,7 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ await memo; const { title - } = branch === combinedBranch ? getCombinedBranchPrMsg() : await constructPrMsg(sg, branch); + } = branch === combinedBranch ? getCombinedBranchPrMsg() : await constructPrMsg(sg, branch, cfgMap); console.log(` -> ${branch.green} (${title.italic})`); }, Promise.resolve()); @@ -144,7 +163,7 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ const { title, body - } = branch === combinedBranch ? getCombinedBranchPrMsg() : await constructPrMsg(sg, branch); + } = branch === combinedBranch ? getCombinedBranchPrMsg() : await constructPrMsg(sg, branch, cfgMap); const base = index === 0 || branch === combinedBranch ? 'master' : allBranches[index - 1]; process.stdout.write(`Checking if PR for branch ${branch} already exists... `); const prs = await ghRepo.prsAsync({ @@ -195,7 +214,7 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ : branch === combinedBranch ? getCombinedBranchPrMsg() : - await constructPrMsg(sg, branch); + await constructPrMsg(sg, branch, cfgMap); const navigation = constructTrainNavigation(prDict, branch, combinedBranch); const newBody = upsertNavigationInBody(navigation, body); process.stdout.write(`Updating PR for branch ${branch}...`); diff --git a/index.js b/index.js index d475573..6ca17b3 100755 --- a/index.js +++ b/index.js @@ -78,7 +78,7 @@ async function getConfigPath(sg) { } /** - * @typedef {string | Object.} BranchCfg + * @typedef {string | Object.} BranchCfg * @typedef {Object.>} TrainCfg */ @@ -116,6 +116,32 @@ async function getBranchesConfigInCurrentTrain(sg, config) { return key && trains[key]; } +/** + * Returns object where keys are branch names, and values are + * branch configs (with `title`, `body`, `combined` etc.) or branch names + * (stubs for compatibility). + * + * This object is useful for quick check of branch config when looping + * over branch names. + * + * @param {Array.} trainCfg + * @return {Object.} + */ +function getConfigMapFromTrainBranchesConfig(trainCfg) { + const map = {}; + + for (branch of trainCfg) { + if (typeof branch === 'string') { + map[branch] = branch; + } else { + const branchName = Object.keys(branch)[0]; + map[branchName] = branch[branchName]; + } + } + + return map; +} + /** * @param {Array.} branchConfig */ @@ -286,7 +312,10 @@ async function main() { // the PR titles and descriptions). Just push and create the PRs. if (program.createPrs) { await findAndPushBranches(); - await ensurePrsExist(sg, sortedTrainBranches, combinedTrainBranch, program.remote); + + const cfgMap = getConfigMapFromTrainBranchesConfig(trainCfg); + + await ensurePrsExist(sg, sortedTrainBranches, combinedTrainBranch, cfgMap, program.remote); return; }