Skip to content

Instantly share code, notes, and snippets.

@peterekepeter
Created November 22, 2018 13:01
Show Gist options
  • Save peterekepeter/05f891eda5670c80472a69a5e2ded64f to your computer and use it in GitHub Desktop.
Save peterekepeter/05f891eda5670c80472a69a5e2ded64f to your computer and use it in GitHub Desktop.
Node.js script that cleans up old branches in Git!
const { exec } = require('child_process');
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
})
const exe = (command, success, error, after) => exec(command, (err, stdout, stderr) => {
if (stderr || err) {
if (error != null){
error(stderr || err);
} else {
console.error("FAILED EXECUTIN OF ", command, " BECAUSE ", stderr || err);
}
} else {
if (success != null){
success(stdout);
} else {
console.log(stdout || command);
}
}
if (after != null) {
after();
}
});
function parseGitBranchV(str) {
const regex = /^\s*([^\s]+)\s+([a-f0-9]+)\s+([^\n]+)$/gm;
const results = [];
let m;
while ((m = regex.exec(str)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
const branch = m[1];
const commit = m[2];
const message = m[3];
results.push({branch, commit, message});
}
return results;
}
function getRemoteBranches(done){
exe("git branch -rv", str => {
const data = parseGitBranchV(str);
done(data);
});
}
function extractSingle(regex, str){
let m;
if ((m = regex.exec(str)) !== null) { return m[1]; }
return null;
}
function getCommitDetails(commit, done){
exe(`git show ${commit} -s`, str => {
var obj = {
fullHash : extractSingle(/commit ([0-9a-f]+)/, str),
author: extractSingle(/Author:\s*([^\n]*)/, str),
date: new Date(extractSingle(/Date:\s*([^\n]*)/, str))
}
done(obj);
});
}
function getRemoteBranchesWithDetails(done)
{
const results = [];
let count = 0;
getRemoteBranches(data => {
data.forEach((value, index) => {
count++;
getCommitDetails(value.commit, details => {
data[index].details = details;
results.push({
commit: details.fullHash || value.commit,
message: value.message,
branch: value.branch,
author: details.author,
date: details.date
});
count--;
if (count===0) { done(results); }
});
});
})
}
function cleanup(){
getRemoteBranchesWithDetails(results => {
// 6 months ago
var someTimeAgo = new Date().getTime() - (6 * 30 * 24 * 60 * 60 * 1000);
var oldStuff = results.filter(item => item.date < someTimeAgo);
const remote = "origin";
const toTrim = remote + "/";
console.log("Branches mathcing your filters, sorted from oldest to newest!")
oldStuff.sort((a,b) => a.date - b.date)
const toDelete = [];
oldStuff.forEach((item, index) => {
let branch = item.branch;
if (branch.indexOf(toTrim) == 0) { branch = branch.substr(toTrim.length); }
let cmd = `git push --delete ${remote} ${branch}`;
toDelete.push({cmd, item});
console.log(item.date, cmd, item.author, item.message);
})
console.log(`Filter matched ${toDelete.length} branches!`);
readline.question("\nAre you sure you want to proceed and delete matched branches? (type 'yes')", (awnser) => {
if (({ "yes":true, "y":true })[awnser.toLocaleLowerCase()] !== true){
console.log('Aborting: user changed their mind!');
} else {
scheduleDeletion(toDelete);
}
readline.close();
});
})
}
function scheduleDeletion(list){
console.log('Deletion is scheduled with rate 0.5/sec to avoid throttling! Press CTRL+C to abort');
let index = 0;
function actuallyDelete(){
const item = list[index];
console.log(`${(index+1)}/${list.length} EXE: `, item.cmd, );
setTimeout(timeout => {
exec(item.cmd, (null, null, done => {
index++;
if (index < list.length){
setTimeout(actuallyDelete, 1);
}
else {
console.log("finshed!")
}
}))
}, 2000);
}
if (index < list.length){
setTimeout(actuallyDelete, 2000);
}
}
cleanup();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment