UNPKG

backport

Version:

A CLI tool that automates the process of backporting commits

173 lines 7.63 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.waitForCherrypick = void 0; const chalk_1 = __importDefault(require("chalk")); const lodash_1 = require("lodash"); const entrypoint_api_1 = require("../../entrypoint.api"); const ora_1 = require("../../lib/ora"); const author_1 = require("../author"); const child_process_promisified_1 = require("../child-process-promisified"); const env_1 = require("../env"); const git_1 = require("../git"); const commitFormatters_1 = require("../github/commitFormatters"); const logger_1 = require("../logger"); const prompts_1 = require("../prompts"); const getCommitsWithoutBackports_1 = require("./getCommitsWithoutBackports"); async function waitForCherrypick(options, commit, targetBranch) { const spinnerText = `Cherry-picking: ${chalk_1.default.greenBright((0, commitFormatters_1.getFirstLine)(commit.sourceCommit.message))}`; const cherrypickSpinner = (0, ora_1.ora)(options.interactive, spinnerText).start(); const commitAuthor = (0, author_1.getCommitAuthor)({ options, commit }); const { hasCommitsWithConflicts } = await cherrypickAndHandleConflicts({ options, commit, commitAuthor, targetBranch, cherrypickSpinner, }); // At this point conflict are resolved (or committed if `commitConflicts: true`) and files are staged // Now we just need to commit them (user may already have done this manually) try { // Run `git commit` in case conflicts were not manually committed await (0, git_1.commitChanges)({ options, commit, commitAuthor }); cherrypickSpinner.succeed(); return { hasCommitsWithConflicts }; } catch (e) { cherrypickSpinner.fail(); throw e; } } exports.waitForCherrypick = waitForCherrypick; async function cherrypickAndHandleConflicts({ options, commit, commitAuthor, targetBranch, cherrypickSpinner, }) { const mergedTargetPullRequest = commit.targetPullRequestStates.find((pr) => pr.state === 'MERGED' && pr.branch === targetBranch); let conflictingFiles; let unstagedFiles; let needsResolving; try { ({ conflictingFiles, unstagedFiles, needsResolving } = await (0, git_1.cherrypick)({ options, sha: commit.sourceCommit.sha, mergedTargetPullRequest, commitAuthor, })); // no conflicts encountered if (!needsResolving) { return { hasCommitsWithConflicts: false }; } // cherrypick failed due to conflicts cherrypickSpinner.fail(); } catch (e) { cherrypickSpinner.fail(); throw e; } const repoPath = (0, env_1.getRepoPath)(options); // resolve conflicts automatically if (options.autoFixConflicts) { const autoResolveSpinner = (0, ora_1.ora)(options.interactive, 'Attempting to resolve conflicts automatically').start(); const didAutoFix = await options.autoFixConflicts({ files: conflictingFiles.map((f) => f.absolute), directory: repoPath, logger: logger_1.logger, targetBranch, }); // conflicts were automatically resolved if (didAutoFix) { autoResolveSpinner.succeed(); return { hasCommitsWithConflicts: false }; } autoResolveSpinner.fail(); } // commits with conflicts should be committed and pushed to the target branch if (!options.interactive && options.commitConflicts) { await (0, git_1.gitAddAll)({ options }); await (0, git_1.commitChanges)({ options, commit, commitAuthor }); return { hasCommitsWithConflicts: true }; } const conflictingFilesRelative = conflictingFiles .map((f) => f.relative) .slice(0, 50); let commitsWithoutBackports; try { commitsWithoutBackports = await (0, getCommitsWithoutBackports_1.getCommitsWithoutBackports)({ options, commit, targetBranch, conflictingFiles: conflictingFilesRelative, }); } catch (e) { commitsWithoutBackports = []; if (e instanceof Error) { logger_1.logger.warn(`Unable to fetch commits without backports: ${e.message}`); } } if (!options.interactive) { throw new entrypoint_api_1.BackportError({ code: 'merge-conflict-exception', commitsWithoutBackports, conflictingFiles: conflictingFilesRelative, }); } (0, logger_1.consoleLog)(chalk_1.default.bold('\nThe commit could not be backported due to conflicts\n')); (0, logger_1.consoleLog)(`Please fix the conflicts in ${repoPath}`); if (commitsWithoutBackports.length > 0) { (0, logger_1.consoleLog)(chalk_1.default.italic(`Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to "${targetBranch}":`)); (0, logger_1.consoleLog)(`${commitsWithoutBackports.map((c) => c.formatted).join('\n')}\n\n`); } /* * Commit could not be cleanly cherrypicked: Initiating conflict resolution */ if (options.editor) { await (0, child_process_promisified_1.spawnPromise)(options.editor, [repoPath], options.cwd, true); } // list files with conflict markers + unstaged files and require user to resolve them await listConflictingAndUnstagedFiles({ retries: 0, options, conflictingFiles: conflictingFiles.map((f) => f.absolute), unstagedFiles, }); return { hasCommitsWithConflicts: false }; } async function listConflictingAndUnstagedFiles({ retries, options, conflictingFiles, unstagedFiles, }) { const hasUnstagedFiles = !(0, lodash_1.isEmpty)((0, lodash_1.difference)(unstagedFiles, conflictingFiles)); const hasConflictingFiles = !(0, lodash_1.isEmpty)(conflictingFiles); if (!hasConflictingFiles && !hasUnstagedFiles) { return; } // add divider between prompts if (retries > 0) { (0, logger_1.consoleLog)('\n----------------------------------------\n'); } const header = chalk_1.default.reset(`Fix the following conflicts manually:`); // show conflict section if there are conflicting files const conflictSection = hasConflictingFiles ? `Conflicting files:\n${chalk_1.default.reset(conflictingFiles.map((file) => ` - ${file}`).join('\n'))}` : ''; const unstagedSection = hasUnstagedFiles ? `Unstaged files:\n${chalk_1.default.reset(unstagedFiles.map((file) => ` - ${file}`).join('\n'))}` : ''; const didConfirm = await (0, prompts_1.confirmPrompt)(`${header}\n\n${conflictSection}\n${unstagedSection}\n\nPress ENTER when the conflicts are resolved and files are staged`); if (!didConfirm) { throw new entrypoint_api_1.BackportError({ code: 'abort-conflict-resolution-exception' }); } const MAX_RETRIES = 100; if (retries++ > MAX_RETRIES) { throw new Error(`Maximum number of retries (${MAX_RETRIES}) exceeded`); } const [_conflictingFiles, _unstagedFiles] = await Promise.all([ (0, git_1.getConflictingFiles)(options), (0, git_1.getUnstagedFiles)(options), ]); await listConflictingAndUnstagedFiles({ retries, options, conflictingFiles: _conflictingFiles.map((file) => file.absolute), unstagedFiles: _unstagedFiles, }); } //# sourceMappingURL=waitForCherrypick.js.map