From be11c0840a6e3435b0619b40468cfc830686205e Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Sun, 24 Sep 2023 12:05:29 +0300 Subject: [PATCH] feat(git-node): improve backport workflow (#732) Checkout from staging branch and ask to update if needed. Fixes: https://github.com/nodejs/node-core-utils/issues/731 --- lib/backport_session.js | 77 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/lib/backport_session.js b/lib/backport_session.js index 849f787c..de385d12 100644 --- a/lib/backport_session.js +++ b/lib/backport_session.js @@ -61,7 +61,7 @@ export default class BackportSession extends Session { cli.stopSpinner(`${file} does not exist in current working tree`, cli.SPINNER_STATUS.WARN); continue; - }; + } if (ancestors.length === 0) { cli.stopSpinner(`Cannot find ancestor commits of ${file}`, cli.SPINNER_STATUS.INFO); @@ -155,12 +155,33 @@ export default class BackportSession extends Session { cli.log(` - ${commit.sha} ${commit.title}`); } + if (!this.isLocalBranchExists(this.stagingBranch)) { + const shouldCreateStagingBranch = await cli.prompt( + `It seems like ${this.stagingBranch} is missing locally, ` + + 'do you want to create it locally to get ready for backporting?', { + defaultAnswer: true + }); + + if (shouldCreateStagingBranch) { + this.syncBranchWithUpstream(this.stagingBranch); + } + } else if (!this.isBranchUpToDateWithUpstream(this.stagingBranch)) { + const shouldSyncBranch = await cli.prompt( + `It seems like your ${this.stagingBranch} is behind the ${this.upstream} remote` + + 'do you want to sync it?', { defaultAnswer: true }); + + if (shouldSyncBranch) { + this.syncBranchWithUpstream(this.stagingBranch); + } + } + const newBranch = `backport-${this.prid}-to-${this.target}`; const shouldCheckout = await cli.prompt( `Do you want to checkout to a new branch \`${newBranch}\`` + ' to start backporting?', { defaultAnswer: false }); + if (shouldCheckout) { - await runAsync('git', ['checkout', '-b', newBranch]); + await runAsync('git', ['checkout', '-b', newBranch, this.stagingBranch]); } const shouldAnalyze = await cli.prompt( @@ -226,4 +247,56 @@ export default class BackportSession extends Session { }; }); } + + getCurrentBranch() { + return runSync('git', + ['rev-parse', '--abbrev-ref', 'HEAD'] + ).trim(); + } + + updateUpstreamRefs(branchName) { + runSync('git', + ['fetch', this.upstream, branchName] + ); + } + + getBranchCommit(branch) { + return runSync('git', + ['rev-parse', branch] + ).trim(); + } + + isBranchUpToDateWithUpstream(branch) { + this.updateUpstreamRefs(branch); + + const localCommit = this.getBranchCommit(branch); + const upstreamCommit = this.getBranchCommit(`${this.upstream}/${branch}`); + + return localCommit === upstreamCommit; + }; + + isLocalBranchExists(branch) { + try { + // will exit with code 1 if branch does not exist + runSync('git', + ['rev-parse', '--verify', '--quiet', branch] + ); + return true; + } catch (e) { + return false; + } + } + + syncBranchWithUpstream(branch) { + const currentBranch = this.getCurrentBranch(); + + runSync('git', + [ + currentBranch !== branch ? 'fetch' : 'pull', + this.upstream, + `${branch}:${branch}`, + '-f' + ] + ); + } }