Merge branch 'gt9-no-done-no-clean'

main
Yanick Champoux 2021-11-25 14:05:55 -05:00
commit 5fc32f831a
4 changed files with 185 additions and 188 deletions

View File

@ -3,120 +3,124 @@ const u = require("updeep");
const _ = require("lodash"); const _ = require("lodash");
const fp = require("lodash/fp"); const fp = require("lodash/fp");
const log = require('./log'); const log = require("./log");
const normalizeArray = (value) => const normalizeArray = (value) =>
Array.isArray(value) ? value : [value].filter((x) => x); Array.isArray(value) ? value : [value].filter((x) => x);
const initBranch = (config = {}) => ({ const initBranch = (config = {}) => ({
upstream: [], upstream: [],
dependencies: [], dependencies: [],
...config, ...config,
}); });
const git = Git(); const git = Git();
const currentBranch = _.once(() =>git const currentBranch = _.once(() =>
.raw("branch", "--show-current") git.raw("branch", "--show-current").then((r) => r.replace("\n", ""))
.then((r) => r.replace("\n", ""))); );
const isClean = () => git.status().then(({ files }) => !files.length);
const branches = _.once(async () => { 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( let tree = Object.entries(config).reduce(
(accum, [key, value]) => u.updateIn(key, value, accum), (accum, [key, value]) => u.updateIn(key, value, accum),
{} {}
); );
let branches = {}; let branches = {};
for (const branch in tree.branch) { for (const branch in tree.branch) {
const entry = tree.branch[branch]; const entry = tree.branch[branch];
if (!("mikado-upstream" in entry)) continue; if (!("mikado-upstream" in entry)) continue;
branches[branch] = initBranch({ branches[branch] = initBranch({
name: branch, name: branch,
upstream: normalizeArray(entry["mikado-upstream"]), upstream: normalizeArray(entry["mikado-upstream"]),
base: entry["mikado-base"], base: entry["mikado-base"],
done: !!entry["mikado-done"], done: !!entry["mikado-done"],
}); });
} }
for (const branch of Object.values(branches)) { for (const branch of Object.values(branches)) {
for (const ups of branch["upstream"]) { for (const ups of branch["upstream"]) {
if (!branches[ups]) if (!branches[ups])
branches[ups] = initBranch({ branches[ups] = initBranch({
name: ups, name: ups,
upstream: [], upstream: [],
dependencies: [], dependencies: [],
}); });
branches[ups].dependencies.push(branch.name); branches[ups].dependencies.push(branch.name);
} }
} }
// include the merged info // include the merged info
for (const branch of Object.values(branches)) { for (const branch of Object.values(branches)) {
branch.contains = _.compact(await git.raw( 'branch', '--merged', branch.name ) branch.contains = _.compact(
.then( r => r.replace( /[ *]/g, '' ) ) await git
.then( r => r.split("\n") )) .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() { async function canBeDeleted() {
const allBranches = await branches(); 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'));
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() { async function canBeWorkedOn() {
const allBranches = await branches(); const allBranches = await branches();
return Object.values(allBranches).filter(
({done}) => !done
).filter( ({name,dependencies})=>
dependencies.every( u => allBranches[u].done )
).map(fp.get('name'));
return Object.values(allBranches)
.filter(({ done }) => !done)
.filter(({ name, dependencies }) =>
dependencies.every((u) => allBranches[u].done)
)
.map(fp.get("name"));
} }
async function needsRebasing() { async function needsRebasing() {
const allBranches = await branches(); const allBranches = await branches();
return Object.values(allBranches).filter(
fp.get('base')
).filter( ({base,name})=>
!allBranches[name].contains.includes(base)
).map(fp.get('name'));
return Object.values(allBranches)
.filter(fp.get("base"))
.filter(({ base, name }) => !allBranches[name].contains.includes(base))
.map(fp.get("name"));
} }
async function needsMerging() { async function needsMerging() {
const allBranches = await branches(); const allBranches = await branches();
return Object.values(allBranches).filter( return Object.values(allBranches)
fp.get('done') .filter(fp.get("done"))
).flatMap( ({upstream,name})=> upstream.map( u => [ name, u ] ) ) .flatMap(({ upstream, name }) => upstream.map((u) => [name, u]))
.filter( ([name,up]) => !allBranches[up].contains.includes(name) ) .filter(([name, up]) => !allBranches[up].contains.includes(name));
} }
module.exports = { module.exports = {
currentBranch, currentBranch,
branches, branches,
canBeDeleted, canBeDeleted,
canBeWorkedOn, canBeWorkedOn,
needsRebasing, needsRebasing,
needsMerging, needsMerging,
isClean,
}; };

View File

@ -1,15 +1,20 @@
const Git = require('simple-git'); const Git = require("simple-git");
const inquirer = require('inquirer'); const inquirer = require("inquirer");
const report = require('yurnalist'); const report = require("yurnalist");
const _ = require('lodash'); const _ = require("lodash");
const { currentBranch } = require( '../utils');
const myGit = require("../branches");
const log = require("../log");
module.exports = async (yargs) => { 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);
};
}

View File

@ -7,147 +7,133 @@ const u = require("updeep");
const columnify = require("columnify"); const columnify = require("columnify");
const chalk = require("chalk"); const chalk = require("chalk");
const myGit = require('../branches'); const myGit = require("../branches");
const log = require('../log'); const log = require("../log");
const iconFor = ( branch, branches ) => { const iconFor = (branch, branches) => {
if(branch.done ) return "✅"; if (branch.done) return "✅";
for( const dep of branch.dependencies ) { for (const dep of branch.dependencies) {
if( !branches[dep].done ) return "⛔"; if (!branches[dep].done) return "⛔";
} }
return "🙋"; return "🙋";
};
} const decorateCurrent = async (branch) => {
if (branch !== (await myGit.currentBranch())) return branch;
const decorateCurrent = async branch => { return chalk.underline.bold(branch);
if( branch !== await myGit.currentBranch() ) return branch; };
return chalk.underline.bold(branch);
}
async function printNeedsRebasing() { 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'); log.subtitle(":arrow_heading_up: needs rebasing");
const b = await Promise.all( const b = await Promise.all(needsRebasing.map(decorateCurrent));
needsRebasing.map( decorateCurrent ) log.info("\t", b.join(", "));
);
log.info( "\t", b.join(', ') );
return true; return true;
} }
async function printNeedsMerging() { 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.subtitle(":thumbsup: ready to be merged");
log.info( "\t", needsMerging.map( x => x.join('->') ).join(', ') ); log.info("\t", needsMerging.map((x) => x.join("->")).join(", "));
return true; return true;
} }
async function printActionables() { 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; accumSomething(await printNeedsMerging());
const accumSomething = (value) => something ||= value;
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 ) { log.subtitle(":recycle: branches that can be deleted");
something = true; log.info("\t", canBeDeleted.join(", "));
}
log.subtitle(':recycle: branches that can be deleted'); const canBeWorkedOn = await myGit.canBeWorkedOn();
log.info( "\t", canBeDeleted.join(', ') );
}
const canBeWorkedOn = await myGit.canBeWorkedOn(); if (canBeWorkedOn.length) {
something = true;
if( canBeWorkedOn.length ) { log.subtitle(":hammer: ready to be worked on");
something = true; const b = await Promise.all(canBeWorkedOn.map(decorateCurrent));
log.info("\t", b.join(", "));
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:");
}
if (!something) {
log.info("nothing? :shrug:");
}
} }
module.exports = async () => { module.exports = async () => {
const branches = await myGit.branches(); const branches = await myGit.branches();
const depColor = (current) => async (dep) => { const depColor = (current) => async (dep) => {
let color = branches[dep].done ? chalk.green : chalk.red; let color = branches[dep].done ? chalk.green : chalk.red;
let contains = await Git().raw("branch", "--contains", dep); let contains = await Git().raw("branch", "--contains", dep);
contains = contains contains = contains.split("\n").map((x) => x.trim().replace("* ", ""));
.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) => { const upstreamColor = (current) => async (up) => {
let color = branches[up].done ? chalk.green : chalk.red; let color = branches[up].done ? chalk.green : chalk.red;
let contains = await Git().raw("branch", "--contains", current); let contains = await Git().raw("branch", "--contains", current);
contains = contains contains = contains.split("\n").map((x) => x.trim().replace("* ", ""));
.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) { for (const branch of sorted) {
console.log( console.log(
iconFor(branch,branches), iconFor(branch, branches),
chalk.blue(branch.name), chalk.blue(branch.name),
branch.current ? "👷" : ' ' branch.current ? "👷" : " "
); );
console.log(`\tbase: ${chalk.magenta(branch.base ?? "<none>")}`); console.log(`\tbase: ${chalk.magenta(branch.base ?? "<none>")}`);
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(" "));
}
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();
}; };

View File

@ -1,12 +1,14 @@
const emoji = require('node-emoji'); const emoji = require("node-emoji");
const chalk = require("chalk"); const chalk = require("chalk");
function groomLog(...entries) { function groomLog(...entries) {
console.log( ...(entries.map(emoji.emojify)) ); console.log(...entries.map(emoji.emojify));
} }
module.exports = { module.exports = {
info: groomLog, info: groomLog,
title: title => groomLog("\n",chalk.blue.bold(title)), title: (title) => groomLog("\n", chalk.blue.bold(title)),
subtitle: title => groomLog("\n",chalk.blue(title)), subtitle: (title) => groomLog("\n", chalk.blue(title)),
} error: (message) => groomLog(":skull:", chalk.red(message)),
success: (message) => groomLog(":sparkles:", chalk.green(message)),
};