/* eslint-disable no-unused-vars */ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only exports.id = "singleRequestManager"; const safeJsonStringify = require("safe-json-stringify"); const { findPickToBranches, repoUsesStaging } = require("./toolbox"); const gerritTools = require("./gerritRESTTools"); // The singleRequestManager processes incoming changes linerally. // When start() is called, the request progresses through branch // validation, tries to create a cherry pick, and tries to stage it. class singleRequestManager { constructor(logger, retryProcessor, requestProcessor) { this.logger = logger; this.retryProcessor = retryProcessor; this.requestProcessor = requestProcessor; this.handleValidBranch = this.handleValidBranch.bind(this); this.requestProcessor.addListener( "singleRequest_validBranch", this.handleValidBranch ); this.ltsTargetChecked = this.ltsTargetChecked.bind(this); this.requestProcessor.addListener( "singleRequest_ltsTargetChecked", this.ltsTargetChecked ); this.handleNewCherryPick = this.handleNewCherryPick.bind(this); this.requestProcessor.addListener("singleRequest_newCherryPick", this.handleNewCherryPick); this.handleCherryPickDone = this.handleCherryPickDone.bind(this); this.requestProcessor.addListener("singleRequest_cherryPickDone", this.handleCherryPickDone); this.handleCherrypickReadyForStage = this.handleCherrypickReadyForStage.bind(this); this.requestProcessor.addListener( "singleRequest_cherrypickReadyForStage", this.handleCherrypickReadyForStage ); this.handleStagingDone = this.handleStagingDone.bind(this); this.requestProcessor.addListener("singleRequest_stagingDone", this.handleStagingDone); } start(parentJSON, picks) { let _this = this; function emit(parentCopy, branch) { _this.requestProcessor.emit( "validateBranch", parentCopy, branch, "singleRequest_validBranch" ); } _this.logger.log( `Starting SingleRequest Manager process for ${parentJSON.fullChangeID}`, "verbose", parentJSON.uuid ); Object.keys(picks).forEach(function (branch) { let parentCopy = _this.requestProcessor.toolbox.deepCopy(parentJSON) if (picks[branch].length > 0) { const originalPicks = Array.from(findPickToBranches(parentCopy.uuid, parentCopy.change.commitMessage)); let missing = picks[branch].filter(x => !originalPicks.includes(x)); // Check the target branch itself since it may not be in originalPicks and could have been // added by the bot. if (!originalPicks.includes(branch)) missing.push(branch); if (missing.length > 0) { gerritTools.locateDefaultAttentionUser(parentJSON.uuid, parentCopy, parentJSON.patchSet.uploader.email, function(userInfos) { // userInfos is Array<{user: string, sourceChangeId?: string}> function postComment() { const plural = missing.length > 1; _this.requestProcessor.gerritCommentHandler(parentCopy.uuid, parentCopy.fullChangeID, undefined, `Automatic cherry-picking detected missing Pick-to targets.` +`\nTarget${plural ? 's' : ''} "${missing.join(", ")}"` + ` ${plural ? "have" : "has"} been automatically added to the` + ` cherry-pick for ${branch}.\nPlease review for correctness.`, "OWNER", parentCopy.customGerritAuth); } let actualUserEmails = []; userInfos.forEach(userInfo => { if (userInfo.user === "copyReviewers") { _this.logger.log("`copyReviewers` found by locateDefaultAttentionUser for" + " original change notification, but not applicable here.", "debug", parentJSON.uuid); } else { actualUserEmails.push(userInfo.user); } }); if (actualUserEmails.length > 0) { // Add all located users to attention set and as reviewers to the original change // so they are notified about the comment regarding new pick-to targets. gerritTools.setChangeReviewers(parentJSON.uuid, parentCopy.fullChangeID, actualUserEmails, parentCopy.customGerritAuth, function() { const attentionPromises = actualUserEmails.map(usrEmail => { return new Promise(resolve => { gerritTools.addToAttentionSet( parentJSON.uuid, parentCopy, usrEmail, "Attention for new Pick-to targets", parentJSON.customGerritAuth, function (success, data) { if (!success) { _this.logger.log( `Failed to add "${usr}" to the attention set of` + ` ${parentCopy.fullChangeID} for new pick-to targets.\n` + `Reason: ${safeJsonStringify(data)}`, "warn", parentJSON.uuid ); } resolve(); } ); }); }); Promise.all(attentionPromises).then(postComment); }); } else { // No specific user found to notify, post comment to owner/default. postComment(); } }); } parentCopy.change.commitMessage = parentCopy.change.commitMessage .replace(/^Pick-to:.+$/gm, `Pick-to: ${picks[branch].join(" ")}`); emit(parentCopy, branch); } else { parentCopy.change.commitMessage = parentCopy.change.commitMessage .replace(/^Pick-to:.+$\n/gm, ""); emit(parentCopy, branch); } }); } handleValidBranch(parentJSON, branch, newParentRev) { let _this = this; _this.requestProcessor.emit( "checkLtsTarget", parentJSON, branch, newParentRev, "singleRequest_ltsTargetChecked" ); } ltsTargetChecked(parentJSON, branch, newParentRev) { let _this = this; _this.requestProcessor.emit( "validBranchReadyForPick", parentJSON, branch, newParentRev, null, "singleRequest_newCherryPick" ); } handleNewCherryPick(parentJSON, cherryPickJSON) { let _this = this; _this.requestProcessor.emit( "newCherryPick", parentJSON, cherryPickJSON, "singleRequest_cherryPickDone" ); } handleCherryPickDone(parentJSON, cherryPickJSON) { let _this = this; _this.requestProcessor.emit( "cherryPickDone", parentJSON, cherryPickJSON, "singleRequest_cherrypickReadyForStage" ); } handleCherrypickReadyForStage(parentJSON, cherryPickJSON) { let _this = this; if (repoUsesStaging(parentJSON.uuid, cherryPickJSON)) { _this.requestProcessor.emit( "cherrypickReadyForStage", parentJSON, cherryPickJSON, "singleRequest_stagingDone" ); } else { _this.requestProcessor.emit( "cherrypickReadyForSubmit", parentJSON, cherryPickJSON, "singleRequest_integrationDone" ); } } handleStagingDone(success, data) { let _this = this; // Stub for later expansion. } } module.exports = singleRequestManager;