summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Smith <daniel.smith@qt.io>2023-08-31 12:06:41 +0200
committerDaniel Smith <Daniel.Smith@qt.io>2024-01-15 08:20:52 +0000
commit8bc1c59ca73337771a98c1871b31a754b9e3afc9 (patch)
tree9b700625a7e52792db9c7cd8b7b051194c614252
parent54614874c05223138990df15a62e5e6e6d3a3330 (diff)
Ensure that the correct parent is chosen in relation chains
Fixup for da2b640d7e6c2157bb7af0bac7b37f66ca4a25fa When examining heridity for a change in a relation chain, the "parent" field of the newly merged change cannot be trusted due to gerrit breaking relation chains on merge. Instead, use the previously fetched relation chain data for the current-1 patchset to select a parent change ID, then query for the current revision of the parent. Also contains a fix for picking when the preferred parent is not ready. Fixes: QTQAINFRA-5796 Fixes: QTQAINFRA-5849 Change-Id: I2a0dc214ae3669e83ff525d21ae46505d2acad51 Reviewed-by: Daniel Smith <Daniel.Smith@qt.io>
-rw-r--r--scripts/gerrit/cherry-pick_automation/relationChainManager.js32
-rw-r--r--scripts/gerrit/cherry-pick_automation/requestProcessor.js248
2 files changed, 174 insertions, 106 deletions
diff --git a/scripts/gerrit/cherry-pick_automation/relationChainManager.js b/scripts/gerrit/cherry-pick_automation/relationChainManager.js
index 6cd4d82b..3efe3166 100644
--- a/scripts/gerrit/cherry-pick_automation/relationChainManager.js
+++ b/scripts/gerrit/cherry-pick_automation/relationChainManager.js
@@ -232,14 +232,28 @@ class relationChainManager {
if (parentPickBranches.has(branch)) {
// The parent is suitable, just needs to be merged so it can be
- // cherry-picked.
- toolbox.setupListener(
- _this.requestProcessor, `merge_${detail.unmergedChangeID}`, undefined, undefined,
- 48 * 60 * 60 * 1000, undefined, undefined,
- undefined, undefined,
- undefined,
- undefined, currentJSON.uuid, true, "relationChain"
- );
+ // cherry-picked as handled above. But if the parent becomes abandoned,
+ // we need to pick to the nearest available parent anyway.
+ pickToNearestParent = true;
+ gerritMessage = `A dependent to this change also had a cherry-pick footer for`
+ + ` ${sanitizedBranch}, but this change is still ${detail.error}.`
+ + `\n If this change is picked to ${branch} in the next`
+ + ` 48 hours and retains the same Change-Id, the dependent change's cherry-pick will`
+ + ` be reparented automatically if it has not yet been staged/merged.`
+ + `\n\nDependent change information:`
+ + `\nSubject: ${currentJSON.change.subject}`
+ + `\nChange Number: ${currentJSON.change.number}`
+ + `\nLink: ${currentJSON.change.url}`;
+
+ gerritMessageOnTimeout = `An automatic pick request for a dependent of this change to`
+ + ` ${sanitizedBranch} has expired.\nPlease process the cherry-pick manually if required.`
+ + `\n\nDependent change information:`
+ + `\nSubject: ${currentJSON.change.subject}`
+ + `\nChange Number: ${currentJSON.change.number}`
+ + `\nLink: ${currentJSON.change.url}`;
+ messageTriggerEvent = `mergeConflict_${targetPickID}`;
+ messageCancelTriggerEvent = `staged_${targetPickID}`;
+
toolbox.setupListener(
_this.requestProcessor, `abandon_${detail.unmergedChangeID}`, undefined, undefined,
48 * 60 * 60 * 1000, undefined, undefined,
@@ -308,7 +322,7 @@ class relationChainManager {
"relationChain_targetParentNotPicked",
true
);
- }, 8000);
+ }, 10000);
} else if (detail.isRetry) {
// We already retried once. The target isn't going to exist
// now if didn't on the first retry. Post a comment on gerrit.
diff --git a/scripts/gerrit/cherry-pick_automation/requestProcessor.js b/scripts/gerrit/cherry-pick_automation/requestProcessor.js
index 32128b09..6ebed37d 100644
--- a/scripts/gerrit/cherry-pick_automation/requestProcessor.js
+++ b/scripts/gerrit/cherry-pick_automation/requestProcessor.js
@@ -476,109 +476,163 @@ class requestProcessor extends EventEmitter {
);
}
+ function queryParent(immediateParent) {
+ // Query for the immediate parent to get its change ID.
+ gerritTools.queryChange(
+ currentJSON.uuid, immediateParent, undefined, currentJSON.customGerritAuth,
+ function (exists, data) {
+ if (exists) {
+ let targetPickParent = `${encodeURIComponent(currentJSON.change.project)}~${
+ encodeURIComponent(branch)}~${data.change_id}`;
+ _this.logger.log(
+ `Set target pick parent for ${branch} to ${targetPickParent}`,
+ "debug", currentJSON.uuid
+ );
+ // Success - Found the parent (change ID) of the current change.
+ if (data.status == "ABANDONED") {
+ // The parent is an abandoned state. Send the error signal.
+ _this.logger.log(
+ `Immediate parent (${immediateParent}) for ${
+ currentJSON.fullChangeID} is in state: ${data.status}`,
+ "warn", currentJSON.uuid
+ );
+ _this.emit(
+ errorSignal, currentJSON, branch,
+ { error: data.status, parentJSON: data, isRetry: isRetry }
+ );
+ } else if (["NEW", "STAGED", "INTEGRATING"].some((element) => data.status == element)) {
+ // The parent has not yet been merged.
+ // Fire the error signal with the parent's state.
+ _this.logger.log(
+ `Immediate parent (${immediateParent}) for ${
+ currentJSON.fullChangeID} is in state: ${data.status}`,
+ "verbose", currentJSON.uuid
+ );
+ _this.emit(errorSignal, currentJSON, branch, {
+ error: data.status,
+ unmergedChangeID: `${
+ encodeURIComponent(currentJSON.change.project)}~${
+ encodeURIComponent(data.branch)}~${data.change_id}`,
+ targetPickParent: targetPickParent, parentJSON: data, isRetry: isRetry
+ });
+ } else {
+ // The status of the parent should be MERGED at this point.
+ // Try to see if it was picked to the target branch.
+ _this.logger.log(
+ `Immediate parent (${immediateParent}) for ${
+ currentJSON.fullChangeID} is in state: ${data.status}`,
+ "debug", currentJSON.uuid
+ );
+ gerritTools.queryChange(
+ currentJSON.uuid, targetPickParent, undefined, currentJSON.customGerritAuth,
+ function (exists, targetData) {
+ if (exists) {
+ _this.logger.log(
+ `Target pick parent ${
+ targetPickParent} exists and will be used as the the parent for ${branch}`,
+ "debug", currentJSON.uuid
+ );
+ // Success - The target exists and can be set as the parent.
+ _this.emit(
+ responseSignal, currentJSON, branch,
+ { target: targetData.current_revision, isRetry: isRetry }
+ );
+ } else if (targetData == "retry") {
+ // Do nothing. This callback function will be called again on retry.
+ retryThis();
+ } else {
+ // The target change ID doesn't exist on the branch specified.
+ _this.logger.log(
+ `Target pick parent ${targetPickParent} does not exist on ${branch}`,
+ "debug", currentJSON.uuid
+ );
+ toolbox.addToCherryPickStateUpdateQueue(
+ currentJSON.uuid,
+ { branch: branch, statusDetail: "parentMergedNoPick" },
+ "verifyParentPickExists",
+ function () {
+ _this.emit(
+ errorSignal, currentJSON, branch,
+ {
+ error: "notPicked",
+ parentChangeID: data.id,
+ parentJSON: data, targetPickParent: targetPickParent, isRetry: isRetry
+ }
+ );
+ }
+ );
+ }
+ }
+ );
+ } // End of target pick parent queryChange call
+ } else if (data == "retry") {
+ // Do nothing. This callback function will be called again on retry.
+ retryThis();
+ } else {
+ fatalError(data);
+ }
+ }
+ ); // End of parent change queryChange call
+ }
+
// Query for the current change to get a list of its parents.
gerritTools.queryChange(
currentJSON.uuid, currentJSON.fullChangeID, undefined, currentJSON.customGerritAuth,
function (exists, data) {
if (exists) {
- // Success - Locate the parent revision (SHA) to the current change.
- let immediateParent = data.revisions[data.current_revision].commit.parents[0].commit;
- gerritTools.queryChange(
- currentJSON.uuid, immediateParent, undefined, currentJSON.customGerritAuth,
- function (exists, data) {
- if (exists) {
- let targetPickParent = `${encodeURIComponent(currentJSON.change.project)}~${
- encodeURIComponent(branch)}~${data.change_id}`;
- _this.logger.log(
- `Set target pick parent for ${branch} to ${targetPickParent}`,
- "debug", currentJSON.uuid
- );
- // Success - Found the parent (change ID) of the current change.
- if (data.status == "ABANDONED") {
- // The parent is an abandoned state. Send the error signal.
- _this.logger.log(
- `Immediate parent (${immediateParent}) for ${
- currentJSON.fullChangeID} is in state: ${data.status}`,
- "warn", currentJSON.uuid
- );
- _this.emit(
- errorSignal, currentJSON, branch,
- { error: data.status, parentJSON: data, isRetry: isRetry }
- );
- } else if (["NEW", "STAGED", "INTEGRATING"].some((element) => data.status == element)) {
- // The parent has not yet been merged.
- // Fire the error signal with the parent's state.
- _this.logger.log(
- `Immediate parent (${immediateParent}) for ${
- currentJSON.fullChangeID} is in state: ${data.status}`,
- "verbose", currentJSON.uuid
- );
- _this.emit(errorSignal, currentJSON, branch, {
- error: data.status,
- unmergedChangeID: `${
- encodeURIComponent(currentJSON.change.project)}~${
- encodeURIComponent(data.branch)}~${data.change_id}`,
- targetPickParent: targetPickParent, parentJSON: data, isRetry: isRetry
- });
- } else {
- // The status of the parent should be MERGED at this point.
- // Try to see if it was picked to the target branch.
- _this.logger.log(
- `Immediate parent (${immediateParent}) for ${
- currentJSON.fullChangeID} is in state: ${data.status}`,
- "debug", currentJSON.uuid
- );
- gerritTools.queryChange(
- currentJSON.uuid, targetPickParent, undefined, currentJSON.customGerritAuth,
- function (exists, targetData) {
- if (exists) {
- _this.logger.log(
- `Target pick parent ${
- targetPickParent} exists and will be used as the the parent for ${branch}`,
- "debug", currentJSON.uuid
- );
- // Success - The target exists and can be set as the parent.
- _this.emit(
- responseSignal, currentJSON, branch,
- { target: targetData.current_revision, isRetry: isRetry }
- );
- } else if (targetData == "retry") {
- // Do nothing. This callback function will be called again on retry.
- retryThis();
- } else {
- // The target change ID doesn't exist on the branch specified.
- _this.logger.log(
- `Target pick parent ${targetPickParent} does not exist on ${branch}`,
- "debug", currentJSON.uuid
- );
- toolbox.addToCherryPickStateUpdateQueue(
- currentJSON.uuid,
- { branch: branch, statusDetail: "parentMergedNoPick" },
- "verifyParentPickExists",
- function () {
- _this.emit(
- errorSignal, currentJSON, branch,
- {
- error: "notPicked",
- parentChangeID: data.id,
- parentJSON: data, targetPickParent: targetPickParent, isRetry: isRetry
- }
- );
- }
- );
- }
- }
- );
- } // End of target pick parent queryChange call
- } else if (data == "retry") {
- // Do nothing. This callback function will be called again on retry.
- retryThis();
- } else {
- fatalError(data);
- }
+ let immediateParent;
+ // Success - Locate the parent revision (SHA) to the current change.
+ // If the current change is part of a relation chain and is not the last change
+ // in the list, use the previous change in the chain as the parent.
+ // The retrieve the current revision of that parent.
+ if (currentJSON.relatedChanges.length > 0) {
+ let next = [...currentJSON.relatedChanges].reverse().findIndex((i) =>
+ i.change_id === currentJSON.change.id) - 1;
+ if (next >= 0) {
+ _this.logger.log(
+ `This change is part of a relation chain. Using the previous change in the chain as the parent.`,
+ "verbose", currentJSON.uuid
+ );
+ const repo_branch = currentJSON.fullChangeID.split('~').slice(0, 2).join('~');
+ let parentChangeId = `${repo_branch}~${currentJSON.relatedChanges[next].change_id}`;
+ // Query for the parent change ID to get the current revision.
+ gerritTools.queryChange(
+ currentJSON.uuid, parentChangeId, undefined, currentJSON.customGerritAuth,
+ function (exists, data) {
+ if (exists) {
+ _this.logger.log(
+ `Found the parent change ID for ${currentJSON.fullChangeID}: ${
+ data.current_revision}`,
+ "debug", currentJSON.uuid
+ );
+ immediateParent = data.current_revision;
+ queryParent(immediateParent);
+ } else if (data == "retry") {
+ // Do nothing. This callback function will be called again on retry.
+ retryThis();
+ } else {
+ fatalError(data);
+ }
+ }); // End of parent change queryChange call
+ } else {
+ _this.logger.log(
+ `This change is part of a relation chain, but is the first change in the chain.`
+ + ` Using the latest patchset's parent as the parent.`,
+ "verbose", currentJSON.uuid
+ );
+ immediateParent = data.revisions[data.current_revision].commit.parents[0].commit;
+ queryParent(immediateParent);
}
- ); // End of parent change queryChange call
- } else if (data == "retry") {
+ } else {
+ _this.logger.log(
+ `This change is not part of a relation chain.`
+ + ` Using the latest patchset's parent as the parent.`,
+ "verbose", currentJSON.uuid
+ );
+ immediateParent = data.revisions[data.current_revision].commit.parents[0].commit;
+ queryParent(immediateParent);
+ }
+ } else if (data == "retry") {
// Do nothing. This callback function will be called again on retry.
retryThis();
} else {