diff --git a/src/branches.js b/src/branches.js index 39630c5..ea9a5d4 100644 --- a/src/branches.js +++ b/src/branches.js @@ -3,120 +3,124 @@ const u = require("updeep"); const _ = require("lodash"); const fp = require("lodash/fp"); -const log = require('./log'); +const log = require("./log"); const normalizeArray = (value) => - Array.isArray(value) ? value : [value].filter((x) => x); + Array.isArray(value) ? value : [value].filter((x) => x); const initBranch = (config = {}) => ({ - upstream: [], - dependencies: [], - ...config, + upstream: [], + dependencies: [], + ...config, }); const git = Git(); -const currentBranch = _.once(() =>git - .raw("branch", "--show-current") - .then((r) => r.replace("\n", ""))); +const currentBranch = _.once(() => + git.raw("branch", "--show-current").then((r) => r.replace("\n", "")) +); + +const isClean = () => git.status().then(({ files }) => !files.length); const branches = _.once(async () => { - const config = await git.listConfig("local").then(fp.get('all')); + const config = await git.listConfig("local").then(fp.get("all")); - let tree = Object.entries(config).reduce( - (accum, [key, value]) => u.updateIn(key, value, accum), - {} - ); + let tree = Object.entries(config).reduce( + (accum, [key, value]) => u.updateIn(key, value, accum), + {} + ); - let branches = {}; + let branches = {}; - for (const branch in tree.branch) { - const entry = tree.branch[branch]; + for (const branch in tree.branch) { + const entry = tree.branch[branch]; - if (!("mikado-upstream" in entry)) continue; + if (!("mikado-upstream" in entry)) continue; - branches[branch] = initBranch({ - name: branch, - upstream: normalizeArray(entry["mikado-upstream"]), - base: entry["mikado-base"], - done: !!entry["mikado-done"], - }); - } + branches[branch] = initBranch({ + name: branch, + upstream: normalizeArray(entry["mikado-upstream"]), + base: entry["mikado-base"], + done: !!entry["mikado-done"], + }); + } - for (const branch of Object.values(branches)) { - for (const ups of branch["upstream"]) { - if (!branches[ups]) - branches[ups] = initBranch({ - name: ups, - upstream: [], - dependencies: [], - }); + for (const branch of Object.values(branches)) { + for (const ups of branch["upstream"]) { + if (!branches[ups]) + branches[ups] = initBranch({ + name: ups, + upstream: [], + dependencies: [], + }); - branches[ups].dependencies.push(branch.name); - } - } + branches[ups].dependencies.push(branch.name); + } + } - // include the merged info - for (const branch of Object.values(branches)) { - branch.contains = _.compact(await git.raw( 'branch', '--merged', branch.name ) - .then( r => r.replace( /[ *]/g, '' ) ) - .then( r => r.split("\n") )) - } + // include the merged info + for (const branch of Object.values(branches)) { + branch.contains = _.compact( + await git + .raw("branch", "--merged", branch.name) + .then((r) => r.replace(/[ *]/g, "")) + .then((r) => r.split("\n")) + ); + } - const current = await currentBranch(); + const current = await currentBranch(); - if (branches[current]) branches[current].current = true; + if (branches[current]) branches[current].current = true; - return branches; + return branches; }); async function canBeDeleted() { - const allBranches = await branches(); - - return Object.values(allBranches).filter( - fp.get('done') - ).filter( ({name,upstream})=> - upstream.every( u => allBranches[u].contains.includes(name) ) - ).map(fp.get('name')); + const allBranches = await branches(); + return Object.values(allBranches) + .filter(fp.get("done")) + .filter(({ name, upstream }) => + upstream.every((u) => allBranches[u].contains.includes(name)) + ) + .map(fp.get("name")); } async function canBeWorkedOn() { - const allBranches = await branches(); - - return Object.values(allBranches).filter( - ({done}) => !done - ).filter( ({name,dependencies})=> - dependencies.every( u => allBranches[u].done ) - ).map(fp.get('name')); + const allBranches = await branches(); + return Object.values(allBranches) + .filter(({ done }) => !done) + .filter(({ name, dependencies }) => + dependencies.every((u) => allBranches[u].done) + ) + .map(fp.get("name")); } async function needsRebasing() { - const allBranches = await branches(); - - return Object.values(allBranches).filter( - fp.get('base') - ).filter( ({base,name})=> - !allBranches[name].contains.includes(base) - ).map(fp.get('name')); + const allBranches = await branches(); + return Object.values(allBranches) + .filter(fp.get("base")) + .filter(({ base, name }) => !allBranches[name].contains.includes(base)) + .map(fp.get("name")); } async function needsMerging() { - const allBranches = await branches(); + const allBranches = await branches(); - return Object.values(allBranches).filter( - fp.get('done') - ).flatMap( ({upstream,name})=> upstream.map( u => [ name, u ] ) ) - .filter( ([name,up]) => !allBranches[up].contains.includes(name) ) + return Object.values(allBranches) + .filter(fp.get("done")) + .flatMap(({ upstream, name }) => upstream.map((u) => [name, u])) + .filter(([name, up]) => !allBranches[up].contains.includes(name)); } module.exports = { - currentBranch, - branches, - canBeDeleted, - canBeWorkedOn, - needsRebasing, - needsMerging, + currentBranch, + branches, + canBeDeleted, + canBeWorkedOn, + needsRebasing, + needsMerging, + isClean, }; diff --git a/src/commands/done.js b/src/commands/done.js index 0bdee48..e46d525 100644 --- a/src/commands/done.js +++ b/src/commands/done.js @@ -1,15 +1,20 @@ -const Git = require('simple-git'); -const inquirer = require('inquirer'); -const report = require('yurnalist'); -const _ = require('lodash'); -const { currentBranch } = require( '../utils'); +const Git = require("simple-git"); +const inquirer = require("inquirer"); +const report = require("yurnalist"); +const _ = require("lodash"); + +const myGit = require("../branches"); +const log = require("../log"); module.exports = async (yargs) => { + if (!(await myGit.isClean())) { + log.error("workspace not clean, aborting"); + return; + } - const current = await currentBranch(); + const current = await myGit.currentBranch(); - console.log(`marking branch '${current}' as done`); + log.success(`W00t! marking branch '${current}' as done`); - await Git().addConfig( `branch.${current}.mikado-done`, true ); - -} + await Git().addConfig(`branch.${current}.mikado-done`, true); +}; diff --git a/src/commands/status.js b/src/commands/status.js index 6370c79..e96658a 100644 --- a/src/commands/status.js +++ b/src/commands/status.js @@ -7,147 +7,133 @@ const u = require("updeep"); const columnify = require("columnify"); const chalk = require("chalk"); -const myGit = require('../branches'); -const log = require('../log'); +const myGit = require("../branches"); +const log = require("../log"); -const iconFor = ( branch, branches ) => { - if(branch.done ) return "✅"; +const iconFor = (branch, branches) => { + if (branch.done) return "✅"; - for( const dep of branch.dependencies ) { - if( !branches[dep].done ) return "⛔"; - } + for (const dep of branch.dependencies) { + if (!branches[dep].done) return "⛔"; + } - return "🙋"; + return "🙋"; +}; -} +const decorateCurrent = async (branch) => { + if (branch !== (await myGit.currentBranch())) return branch; -const decorateCurrent = async branch => { - if( branch !== await myGit.currentBranch() ) return branch; - - return chalk.underline.bold(branch); -} + return chalk.underline.bold(branch); +}; async function printNeedsRebasing() { - const needsRebasing = await myGit.needsRebasing(); + const needsRebasing = await myGit.needsRebasing(); - if( !needsRebasing.length ) return false; + if (!needsRebasing.length) return false; - log.subtitle(':arrow_heading_up: needs rebasing'); - const b = await Promise.all( - needsRebasing.map( decorateCurrent ) - ); - log.info( "\t", b.join(', ') ); + log.subtitle(":arrow_heading_up: needs rebasing"); + const b = await Promise.all(needsRebasing.map(decorateCurrent)); + log.info("\t", b.join(", ")); - return true; + return true; } async function printNeedsMerging() { - const needsMerging = await myGit.needsMerging(); + const needsMerging = await myGit.needsMerging(); - if(!needsMerging.length) return false; + if (!needsMerging.length) return false; - log.subtitle(':thumbsup: ready to be merged'); - log.info( "\t", needsMerging.map( x => x.join('->') ).join(', ') ); + log.subtitle(":thumbsup: ready to be merged"); + log.info("\t", needsMerging.map((x) => x.join("->")).join(", ")); - return true; + return true; } - async function printActionables() { + log.title(":point_right: What's next?"); - log.title(":point_right: What's next?"); + let something = false; + const accumSomething = (value) => (something ||= value); - let something = false; - const accumSomething = (value) => something ||= value; + accumSomething(await printNeedsMerging()); - accumSomething( await printNeedsRebasing() ); + accumSomething(await printNeedsRebasing()); - accumSomething( await printNeedsMerging() ); + const canBeDeleted = await myGit.canBeDeleted(); - const canBeDeleted = await myGit.canBeDeleted(); + if (canBeDeleted.length) { + something = true; - if( canBeDeleted.length ) { - something = true; + log.subtitle(":recycle: branches that can be deleted"); + log.info("\t", canBeDeleted.join(", ")); + } - log.subtitle(':recycle: branches that can be deleted'); - log.info( "\t", canBeDeleted.join(', ') ); - } + const canBeWorkedOn = await myGit.canBeWorkedOn(); - const canBeWorkedOn = await myGit.canBeWorkedOn(); + if (canBeWorkedOn.length) { + something = true; - if( canBeWorkedOn.length ) { - something = true; - - log.subtitle(':hammer: ready to be worked on'); - const b = await Promise.all( - canBeWorkedOn.map( decorateCurrent ) - ); - log.info( "\t", b.join(', ') ); - } - - if(!something) { - log.info("nothing? :shrug:"); - } + log.subtitle(":hammer: ready to be worked on"); + const b = await Promise.all(canBeWorkedOn.map(decorateCurrent)); + log.info("\t", b.join(", ")); + } + if (!something) { + log.info("nothing? :shrug:"); + } } module.exports = async () => { - const branches = await myGit.branches(); + const branches = await myGit.branches(); - const depColor = (current) => async (dep) => { - let color = branches[dep].done ? chalk.green : chalk.red; + const depColor = (current) => async (dep) => { + let color = branches[dep].done ? chalk.green : chalk.red; - let contains = await Git().raw("branch", "--contains", dep); - contains = contains - .split("\n") - .map((x) => x.trim().replace("* ", "")); + let contains = await Git().raw("branch", "--contains", dep); + contains = contains.split("\n").map((x) => x.trim().replace("* ", "")); - if (contains.includes(current)) color = color.underline; + if (contains.includes(current)) color = color.underline; - return color(dep); - }; + return color(dep); + }; - const upstreamColor = (current) => async (up) => { - let color = branches[up].done ? chalk.green : chalk.red; + const upstreamColor = (current) => async (up) => { + let color = branches[up].done ? chalk.green : chalk.red; - let contains = await Git().raw("branch", "--contains", current); - contains = contains - .split("\n") - .map((x) => x.trim().replace("* ", "")); + let contains = await Git().raw("branch", "--contains", current); + contains = contains.split("\n").map((x) => x.trim().replace("* ", "")); - if (contains.includes(up)) color = color.underline; + if (contains.includes(up)) color = color.underline; - return color(up); - }; + return color(up); + }; - log.title(":chopsticks: Mikado status\n"); + log.title(":chopsticks: Mikado status\n"); - const sorted = _.sortBy(Object.values(branches), ["current"]); + const sorted = _.sortBy(Object.values(branches), ["current"]); - for (const branch of sorted) { - console.log( - iconFor(branch,branches), - chalk.blue(branch.name), - branch.current ? "👷" : ' ' - ); - console.log(`\tbase: ${chalk.magenta(branch.base ?? "")}`); - - if (branch.upstream.length) { - const ups = await Promise.all( - branch.upstream.map(upstreamColor(branch.name)) - ); - console.log("\tupstream:", ups.join(" ")); - } - - if (branch.dependencies.length) { - const deps = await Promise.all( - branch.dependencies.map(depColor(branch.name)) - ); - console.log("\tdependencies:", deps.join(" ")); - } + for (const branch of sorted) { + console.log( + iconFor(branch, branches), + chalk.blue(branch.name), + branch.current ? "👷" : " " + ); + console.log(`\tbase: ${chalk.magenta(branch.base ?? "")}`); + if (branch.upstream.length) { + const ups = await Promise.all( + branch.upstream.map(upstreamColor(branch.name)) + ); + console.log("\tupstream:", ups.join(" ")); } - await printActionables(); + if (branch.dependencies.length) { + const deps = await Promise.all( + branch.dependencies.map(depColor(branch.name)) + ); + console.log("\tdependencies:", deps.join(" ")); + } + } + await printActionables(); }; diff --git a/src/log.js b/src/log.js index f9b8615..26641a4 100644 --- a/src/log.js +++ b/src/log.js @@ -1,12 +1,14 @@ -const emoji = require('node-emoji'); +const emoji = require("node-emoji"); const chalk = require("chalk"); function groomLog(...entries) { - console.log( ...(entries.map(emoji.emojify)) ); + console.log(...entries.map(emoji.emojify)); } module.exports = { - info: groomLog, - title: title => groomLog("\n",chalk.blue.bold(title)), - subtitle: title => groomLog("\n",chalk.blue(title)), -} + info: groomLog, + title: (title) => groomLog("\n", chalk.blue.bold(title)), + subtitle: (title) => groomLog("\n", chalk.blue(title)), + error: (message) => groomLog(":skull:", chalk.red(message)), + success: (message) => groomLog(":sparkles:", chalk.green(message)), +};