Skip to content

Instantly share code, notes, and snippets.

@mlenkeit
Created October 19, 2018 08:08
Show Gist options
  • Save mlenkeit/0e067f345dc45dffc8813d56ba6bfb81 to your computer and use it in GitHub Desktop.
Save mlenkeit/0e067f345dc45dffc8813d56ba6bfb81 to your computer and use it in GitHub Desktop.
Update Solution Branches
/*eslint no-console:0*/
'use strict';
const assert = require('assert');
const execSync = require('child_process').execSync;
const cmd = process.argv[2];
const validCmds = ['usb', 'fpa', 'pull-all', 'hard-reset-all', 'ls-branches'];
if (!cmd || !validCmds.find(validCmd => validCmd === cmd)) {
console.log('Need to call with valid cmd', validCmds);
console.log('- pull-all: pull all branches from remote repository.');
console.log('- hard-reset-all: hard-reset all branches from remote repository.');
console.log('- usb: update subsequent branches. starts from the current branch, loops over all subsequent solXX branches and applies the commits in there. this does not change anything on the remote repository.');
console.log('- fpa: force-push all branches. this should usually be run after `usb`');
return process.exit(1);
}
const startBranchNo = null; //parseInt(process.argv[2], 10) || null;
const rawBranches = execSync('git branch -a').toString()
.split('\n')
.filter(branchName => branchName.length > 0);
const allBranches = rawBranches.map(branchName => branchName.split(' ').pop())
.filter(branchName => !/HEAD/.test(branchName))
.filter(branchName => branchName.startsWith('remotes'))
.map(branchName => branchName.replace('remotes/origin/', ''));
const currentBranch = rawBranches.find(branchName => branchName.indexOf('*') === 0).replace('* ', '');
const solutionBranchPattern = /^sol(\d\d)/;
const solutionBranches = allBranches
.filter(branchName => solutionBranchPattern.test(branchName))
.filter(branchName => {
if (!startBranchNo) return true;
const matches = solutionBranchPattern.exec(branchName);
const branchNo = parseInt(matches[1], 10);
return branchNo >= startBranchNo && branchNo <= 15;
})
.sort();
const gitStatus = execSync('git status --porcelain').toString()
.split('\n')
.filter(line => !line.startsWith('??'))
.filter(line => line.length > 0);
const hasChanges = gitStatus.length > 0;
if (hasChanges) {
console.error('There are uncommitted changes.');
return process.exit(1);
}
if (cmd === 'ls-branches') {
console.log('Listing branches...');
solutionBranches.forEach(branchName => console.log(`- ${branchName}`));
} else if (cmd === 'pull-all') {
console.log('Pulling latest changes...');
solutionBranches.forEach((branchName, idx, branches) => {
try {
execSync(`git checkout ${branchName}`);
execSync('git pull');
} catch(e) {
throw new Error(`Failed to process branch ${branchName}: ${e.toString()}`);
}
});
} else if (cmd === 'hard-reset-all') {
console.log('Hard-resetting all changes...');
execSync('git fetch -a');
solutionBranches.forEach((branchName, idx, branches) => {
try {
execSync(`git checkout ${branchName}`);
execSync(`git reset --hard origin/${branchName}`);
} catch(e) {
throw new Error(`Failed to process branch ${branchName}: ${e.toString()}`);
}
});
} else if (cmd === 'usb') {
execSync('git fetch -a');
const currentBranchNo = getBranchNo(currentBranch);
solutionBranches.forEach((branchName, idx, branches) => {
const branchNo = getBranchNo(branchName);
if (branchNo <= currentBranchNo) return;
if (idx === 0) return;
try {
console.log(`Updating branch ${branchName}`);
const branchNamePrev = branches[idx - 1];
const branchNoPrev = getBranchNo(branchNamePrev);
// assert(branchNo === (branchNoPrev + 1), 'branch numbers need to be continuous');
const remoteBranchName = `origin/${branchName}`;
const remoteBranchNamePrev = `origin/${branchNamePrev}`;
console.log(`- switching to branch ${branchName}`);
execSync(`git checkout ${branchName}`);
console.log(`- checking if branch ${remoteBranchName} exists`);
execSync(`git rev-parse ${remoteBranchName}`);
console.log(`- checking if previous branch ${remoteBranchNamePrev} exists`);
execSync(`git rev-parse ${remoteBranchNamePrev}`);
console.log('- detecting commits to cherry-pick from remote branches');
const commitIdsToCherryPickRaw = execSync(`git log ${remoteBranchNamePrev}..${remoteBranchName} --pretty=format:"%h" --reverse`).toString();
const commitIdsToCherryPick = commitIdsToCherryPickRaw.split('\n');
const commitIdsToCherryPickStr = commitIdsToCherryPick.join(' ');
console.log(`- found ${commitIdsToCherryPick.length} commits: `, commitIdsToCherryPickStr);
console.log(`- resetting branch to state of previous local branch ${branchNamePrev}`);
execSync(`git reset --hard ${branchNamePrev}`);
if (commitIdsToCherryPickRaw.length > 0) {
console.log('- cherry-picking commits');
execSync(`git cherry-pick ${commitIdsToCherryPickStr}`);
} else {
console.log('- no commits to cherry-picks')
}
} catch (e) {
throw new Error(`Failed to process branch ${branchName}: ${e.toString()}`);
}
});
execSync(`git checkout ${currentBranch}`);
} else if (cmd === 'fpa') {
solutionBranches.forEach((branchName, idx) => {
try {
console.log(`Force-pushing branch ${branchName}`);
console.log(`- switching to branch ${branchName}`);
execSync(`git checkout ${branchName}`);
console.log('- force-pushing branch');
execSync(`git push origin ${branchName}:${branchName} --force`);
} catch(e) {
throw new Error(`Failed to process branch ${branchName}: ${e.toString()}`);
}
});
execSync(`git checkout ${currentBranch}`);
}
//
// return;
//
// console.log('Recursively rebasing and force-pushing...');
// solutionBranches.forEach((branchName, idx, branches) => {
// try {
// execSync(`git checkout ${branchName}`);
// if (idx !== 0) {
// const prevBranch = branches[idx - 1];
// // execSync(`git rebase ${prevBranch}`);
// }
// execSync(`git push origin ${branchName}:${branchName} --force`);
// } catch(e) {
// throw new Error(`Failed to process branch ${branchName}: ${e.toString()}`);
// }
// });
//
// execSync(`git checkout ${currentBranch}`);
// functions
function getBranchNo(branchName) {
const matches = solutionBranchPattern.exec(branchName);
const branchNo = parseInt(matches[1], 10);
return branchNo;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment