diff options
author | Daniel Smith <daniel.smith@qt.io> | 2022-03-17 13:15:41 +0100 |
---|---|---|
committer | Daniel Smith <daniel.smith@qt.io> | 2022-06-21 11:29:13 +0200 |
commit | febdeaca3a7153e6cfd3ded9d962476a45d79e87 (patch) | |
tree | 4fbcdeb6165d60f68049dbebda818409901b0d72 /scripts | |
parent | 0c188283207828ed166a4bd63b6ad5d4d5657ab8 (diff) | |
download | qtqa-febdeaca3a7153e6cfd3ded9d962476a45d79e87.tar.gz |
Add a plugin to monitor changes created by cherry-pick-bot
This plugin monitors for new patchsets and integration failures
among changes owned by the cherry-pick-bot.
When a user who is not the original author uploads a new patchset,
this plugin adds the original author or approving reviewer to the
attention set of the bot-owned cherry-pick.
When a bot-owned cherry-pick fails to integrate, the author
of the most recent patchset or of the original change is
added to the attention set.
This updates the current behavior where the cherry-pick-bot
in most cases forgets about a change when it is successfully
staged or has a merge conflict. This behavior has sometimes
resulted in cherry-picks getting lost, as the cherry-pick-bot
becomes the owner of new cherry-pick changes it creates and
the original author is never notified of problems.
Task-number: QTQAINFRA-4611
Change-Id: I3ab61e38aadb31ff5ce0ab7bf0d13b601340890e
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'scripts')
4 files changed, 199 insertions, 9 deletions
diff --git a/scripts/gerrit/cherry-pick_automation/README.md b/scripts/gerrit/cherry-pick_automation/README.md index 444542f..eaffb20 100644 --- a/scripts/gerrit/cherry-pick_automation/README.md +++ b/scripts/gerrit/cherry-pick_automation/README.md @@ -46,14 +46,6 @@ to restore any in-process items and listeners upon restarting the service. conflicts and should not have any direct dependencies on any other changes. - - -### What this proof-of-concept does NOT (yet) do -1. This does not currently listen for an automatically staged cherry-pick - to pass or fail integration. -2. Branch validation does not currently verify that the target branch - is open to receiving changes. - ### Installation and running #### Pre-requisites diff --git a/scripts/gerrit/cherry-pick_automation/notifier.js b/scripts/gerrit/cherry-pick_automation/notifier.js index cc5993e..598c134 100644 --- a/scripts/gerrit/cherry-pick_automation/notifier.js +++ b/scripts/gerrit/cherry-pick_automation/notifier.js @@ -36,7 +36,7 @@ exports.registerCustomListener = registerCustomListener; function envOrConfig(ID, configFile) { if (process.env[ID]) { return process.env[ID]; - } else if (configFile) { + } else if (configFile && fs.existsSync(configFile)) { const config = require(configFile); return config[ID]; } diff --git a/scripts/gerrit/cherry-pick_automation/plugin_bots/integration_monitor/config.json b/scripts/gerrit/cherry-pick_automation/plugin_bots/integration_monitor/config.json new file mode 100644 index 0000000..6e9b1c7 --- /dev/null +++ b/scripts/gerrit/cherry-pick_automation/plugin_bots/integration_monitor/config.json @@ -0,0 +1,3 @@ +{ + "INTEGRATION_MONITOR_ENABLED" : true +} diff --git a/scripts/gerrit/cherry-pick_automation/plugin_bots/integration_monitor/integration_monitor.js b/scripts/gerrit/cherry-pick_automation/plugin_bots/integration_monitor/integration_monitor.js new file mode 100644 index 0000000..dd6b638 --- /dev/null +++ b/scripts/gerrit/cherry-pick_automation/plugin_bots/integration_monitor/integration_monitor.js @@ -0,0 +1,195 @@ +/* eslint-disable no-unused-vars */ +/**************************************************************************** + ** + ** Copyright (C) 2022 The Qt Company Ltd. + ** Contact: https://www.qt.io/licensing/ + ** + ** This file is part of the qtqa module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and The Qt Company. For licensing terms + ** and conditions see https://www.qt.io/terms-conditions. For further + ** information use the contact form at https://www.qt.io/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 3 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPL3 included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 3 requirements + ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 2.0 or (at your option) the GNU General + ** Public license version 3 or any later version approved by the KDE Free + ** Qt Foundation. The licenses are as published by the Free Software + ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 + ** included in the packaging of this file. Please review the following + ** information to ensure the GNU General Public License requirements will + ** be met: https://www.gnu.org/licenses/gpl-2.0.html and + ** https://www.gnu.org/licenses/gpl-3.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +exports.id = "integration_monitor"; + +const gerritTools = require("../../gerritRESTTools"); + +// This plugin requires no additional config, and relies +// on the base cherry-pick-bot credentials. + +// This plugin listens for failed integrations owned by the cherry-pick +// bot (The owner of a change is the uploader of the first patch set and cannot +// be changed). When such a change fails to integrate, this plugin adds +// the author of the most recent patch set to the attention set and posts +// a basic message. +class integration_monitor { + constructor(notifier) { + this.notifier = notifier; + this.logger = notifier.logger; + this.retryProcessor = notifier.retryProcessor; + this.requestProcessor = notifier.requestProcessor; + + this.handleIntegrationFailed = this.handleIntegrationFailed.bind(this); + this.handlePatchsetCreated = this.handlePatchsetCreated.bind(this); + + function pickbotIsOwner(userEmail) { + return userEmail == "cherrypick_bot@qt-project.org"; + } + + notifier.registerCustomListener(notifier.server, "integration_monitor_failed", + this.handleIntegrationFailed); + notifier.server.registerCustomEvent("integration_monitor_failed", "change-integration-fail", + function (req) { + // The change-integration-fail event doesn't have the patchset author, + // so query gerrit for the full change details. + gerritTools.queryChange(req.uuid, req.fullChangeID, undefined, undefined, + function(success, changeData) { + if (success) { + let author = changeData.revisions[changeData.current_revision].commit.author.email; + let pickbotIsAuthor = author == "cherrypick_bot@qt-project.org"; + if (pickbotIsOwner(req.change.owner.email) && !pickbotIsAuthor) { + // A real user is the author and should be added to the attention set. + notifier.server.emit("integration_monitor_failed", req, author); + } + } else { + logger.log(`Failed to query gerrit for ${req.fullChangeID}`, "error", req.uuid); + } + } + ); + } + ); + + notifier.registerCustomListener(notifier.server, "integration_monitor_patchset-created", + this.handlePatchsetCreated); + notifier.server.registerCustomEvent("integration_monitor_patchset-created", "patchset-created", + function(req) { + if (req.change.status == "MERGED") + return; // The CI created a new patchset upon change merge. + let uploader = req.uploader.email; + let pickbotIsUploader = uploader == "cherrypick_bot@qt-project.org"; + if (pickbotIsOwner(req.change.owner.email) && !pickbotIsUploader) { + // A real user is the uploader and should be added to the attention set. + notifier.server.emit("integration_monitor_patchset-created", req, uploader); + } + } + ); + } + + doAddToAttentionSet(req, user, comment) { + let _this = this; + gerritTools.addToAttentionSet( + req.uuid, req.change, user, undefined, + function (success, msg) { + // No need to do anything after adding. + } + ); + } + + handleIntegrationFailed(req, author) { + let _this = this; + _this.logger.log( + `Received integration failure notification for cherry-picked change ${req.fullChangeID}`, + "info", req.uuid + ); + req.change.fullChangeID = req.fullChangeID // Tack on the full change ID so it gets used + _this.doAddToAttentionSet(req, author); + } + + handlePatchsetCreated(req, uploader) { + let _this = this; + _this.logger.log( + `Received patchset-created by ${uploader} for cherry-picked change ${req.change.project}`, + "info", req.uuid + ); + + // Patchset-created does not include a full change ID. Assemble one. + req.fullChangeID = encodeURIComponent(`${req.change.project}~${req.change.branch}~${req.change.id}`); + req.change.fullChangeID = req.fullChangeID; + // Query the cherry-pick's original branch change to identify the original + // author. + let ReviewRegex = /^Reviewed-by: .+<(.+)>$/m; + let commitMessage = req.change.commitMessage; + let originalApprover = undefined; + try { + originalApprover = commitMessage.match(ReviewRegex)[1]; + } catch { + // Should really never fail, since cherry-picks should always be created + // with the original Review footers intact. + _this.logger.log(`Failed to locate a reviewer from commit message:\n${commitMessage}`, + "error", req.uuid); + } + if (originalApprover && originalApprover != uploader) { + // The approver from the original change should be able to help. + gerritTools.setChangeReviewers(req.uuid, req.change.fullChangeID, [originalApprover], undefined, + function(){}) + _this.doAddToAttentionSet(req, originalApprover, + `Attention set updated: Added original patch Approver: ${originalApprover}`); + return; + } + // This insane regex is the same as used in the commit message sanitizer, + // So it should always find the right footer which references the + // picked-from sha. + let cherryPickRegex = /^\((?:partial(?:ly)? )?(?:cherry[- ]pick|(?:back-?)?port|adapt)(?:ed)?(?: from| of)?(?: commit)? (\w+\/)?([0-9a-fA-F]{7,40})/m; + let originSha = undefined; + try{ + originSha = commitMessage.match(cherryPickRegex)[0]; + } catch { + _this.logger.log(`Failed to match a cherry-pick footer for ${req.change.fullChangeID}`, + "error", req.uuid); + return // No point in continuing. Log the error and move on. + } + gerritTools.queryChange(req.uuid, originSha, undefined, undefined, + function(success, changeData) { + if (success) { + let originalAuthor = changeData.revisions[changeData.current_revision].commit.author.email; + if (uploader != originalAuthor) { + // Add the author of the original change's final patchset to the + // attention set of the cherry-pick. + gerritTools.setChangeReviewers(req.uuid, req.change.fullChangeID, [originalAuthor], + undefined, function(){}) + _this.doAddToAttentionSet(req, originalAuthor, + `Attention set updated: Added original patch author: ${originalAuthor}`); + } else { + // Now we have a problem. The uploader is the original author, but + // they also appear to have self-approved the original patch. + // Try to copy all the reviewers from the original change (hopefully there are some). + // Adding them as a reviewer will also add them to the attention set. + gerritTools.copyChangeReviewers(req.uuid, changeData.id, req.change.fullChangeID); + } + } else { + _this.logger.log(`Failed to query gerrit for ${originSha}`, "error", req.uuid); + } + } + ); + } +} + +module.exports = integration_monitor; |