Created
July 11, 2018 22:52
-
-
Save tobius/f0ef8b9fcb78a52030f09542682c7d76 to your computer and use it in GitHub Desktop.
Find and show all uncommitted Git repo files in $HOME
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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