Skip to content

Instantly share code, notes, and snippets.

@tobius
Created July 11, 2018 22:52
Show Gist options
  • Save tobius/f0ef8b9fcb78a52030f09542682c7d76 to your computer and use it in GitHub Desktop.
Save tobius/f0ef8b9fcb78a52030f09542682c7d76 to your computer and use it in GitHub Desktop.
Find and show all uncommitted Git repo files in $HOME
#!/usr/bin/env node
/**
* Find and show all uncommitted Git repo files in $HOME
*
* @author Toby Miller <[email protected]>
* @license MIT
*/
// import
const
_ = require('lodash'),
fs = require('fs'),
{ promisify } = require('util');
// create a promise shell
const exec = promisify(require('child_process').exec);
/**
* find every projects git folder paths
*
* @todo: add custom ignore rule capabilities
* @todo: add custom project base dir capabilities
*
* @param {Void}
* @return {Promise} paths
*/
async function findGitFolders() {
const { stdout, stderr } = await exec([
`find ${process.env.HOME}`,
'! -path \'* *\'',
'! -path \'*/.rbenv/*\'',
'! -path \'*/.rvm/*\'',
'! -path \'*/node_modules/*\'',
'-type d',
'-name .git',
'-perm -644',
'2>&1 | grep -iv \'permission denied\''
].join(' '));
if (stderr) {
throw stderr;
}
return _.compact(stdout.split(/\n/));
}
/**
* cast git folders to simple projects
*
* @param {Array} gitFolders
* @return {Promise} projects
*/
async function toObjects(gitFolders) {
return _.map(gitFolders, (gitFolder) => {
return {
gitFolder: gitFolder
};
});
}
/**
* append a project folder to each project object
*
* @param {Array} withGitFolders
* @return {Promise} projects
*/
async function appendProjectFolders(withGitFolders) {
return _.map(withGitFolders, (obj) => {
obj.projectFolder = obj.gitFolder.replace(/\/\.git$/, '');
return obj;
});
}
/**
* parse a porcelain status (aka parsable git status message)
*
* note: specifically, ignore untracked files
*
* @param {String} status
* @return {Promise} statusLines
*/
async function parsePorcelainStatus(status) {
let lines = _.compact(status.split(/\n/));
lines = _.map(lines, (line) => {
return line.trim().replace(/^([^ ]+) +/, '$1 ');
});
return _.filter(lines, (line) => {
return /^[^\'\?]/.test(line.trim());
}).sort();
}
/**
* append git statuses to each project object
*
* @param {Array} withProjectFolders
* @return {Promise} projects
*/
async function appendGitStatuses(withProjectFolders) {
return await Promise.all(_.map(withProjectFolders, async (obj) => {
const { stdout, stderr } = await exec(`cd ${obj.projectFolder} && git status --porcelain=v1`);
if (stderr) {
throw stderr;
}
obj.gitStatus = await parsePorcelainStatus(stdout);
return obj;
}));
}
/**
* cast each project status to lines of output text
*
* @param {Object} projects
* @return {Promise} lines
*/
async function toOutputLines(projects) {
let lines = [];
const activeRepos = _.filter(projects, (project) => {
return project.gitStatus.length;
});
let repoCount = projects.length;
let activeRepoCount = activeRepos.length;
_.each(activeRepos, (repo) => {
lines.push(`${repo.projectFolder}`);
_.each(repo.gitStatus, (line) => {
lines.push(` ${line}`);
});
lines.push('');
});
lines.push(`${activeRepoCount} / ${repoCount} project repos have uncommitted changes`);
return lines;
}
// run script
findGitFolders()
.then(toObjects)
.then(appendProjectFolders)
.then(appendGitStatuses)
.then(toOutputLines)
.then((lines) => {
console.log(lines.join('\n'));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment