an agent gets one todo when its pr starts conflicting with the target branch
This commit is contained in:
parent
32155f70aa
commit
11eb7dac9c
2 changed files with 963 additions and 21 deletions
|
|
@ -24,6 +24,12 @@ pub const MAX_AGE: Duration = Duration::from_hours(7 * 24);
|
|||
/// The router's durable consumer on [`STREAM`].
|
||||
pub const ROUTER_CONSUMER: &str = "notify-router";
|
||||
|
||||
/// The event type swarm-controller publishes, on [`subject`], for a pull request
|
||||
/// it has checked against its target branch after a push there. Forgejo sends
|
||||
/// no such delivery. The body is a `pull_request` delivery whose `action` is
|
||||
/// `conflicting` or `mergeable`.
|
||||
pub const MERGEABILITY_EVENT: &str = "pull_request_mergeability";
|
||||
|
||||
/// The subject a delivery of Forgejo event type `event_type` is kept under.
|
||||
#[must_use]
|
||||
pub fn subject(event_type: &str) -> String {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ pub const SOURCE_EVENTS: &[(&str, &str)] = &[
|
|||
"workflow_run_cancelled",
|
||||
"A CI run on a pull request that was cancelled",
|
||||
),
|
||||
(
|
||||
"pull_request_mergeability",
|
||||
"What swarm-controller found about an open pull request after a push to its target \
|
||||
branch: action conflicting, or mergeable",
|
||||
),
|
||||
];
|
||||
|
||||
/// The fields of an event a condition or a recipient can name, with what each
|
||||
|
|
@ -123,6 +128,10 @@ pub const FIELDS: &[(&str, &str)] = &[
|
|||
"todo_holders",
|
||||
"The agents holding a router todo for the issue or pull request (a list)",
|
||||
),
|
||||
(
|
||||
"mergeability_changed",
|
||||
"true when a mergeability event moves the pull request into or out of conflict, else false",
|
||||
),
|
||||
];
|
||||
|
||||
/// The forge account of the review classifier, whose review the built-in
|
||||
|
|
@ -426,6 +435,11 @@ const ACTIONS: &[(&str, &str)] = &[
|
|||
("assigned", "An assignee added"),
|
||||
("unassigned", "An assignee removed"),
|
||||
("synchronized", "New commits pushed to a pull request"),
|
||||
(
|
||||
"conflicting",
|
||||
"A pull request does not merge into its target branch",
|
||||
),
|
||||
("mergeable", "A pull request merges into its target branch"),
|
||||
];
|
||||
|
||||
/// One event field a condition or recipient can name, and what it can hold.
|
||||
|
|
@ -838,6 +852,10 @@ impl TodoSink {
|
|||
/// later run's outcome replaces the earlier one.
|
||||
/// - `ci-cleared`: a push to the PR's branch clears that todo, as the new run
|
||||
/// replaces the old one.
|
||||
/// - `conflict` and `conflict-cleared`: a PR that stops merging into its
|
||||
/// target branch gives its author a todo, and the todo clears when it merges
|
||||
/// again. Both read `mergeability_changed`, so a repeated finding that the
|
||||
/// PR still conflicts writes nothing.
|
||||
#[must_use]
|
||||
pub fn builtins() -> Vec<Flow> {
|
||||
let assignment_key = "forge:{{ repo }}#{{ number }}:assignment";
|
||||
|
|
@ -871,9 +889,47 @@ pub fn builtins() -> Vec<Flow> {
|
|||
head_classifier(),
|
||||
];
|
||||
flows.extend(ci_flows());
|
||||
flows.extend(conflict_flows());
|
||||
flows
|
||||
}
|
||||
|
||||
/// source(`pull_request_mergeability`) → match(`action` and
|
||||
/// `mergeability_changed` = `true`) → the author's `:conflict` todo.
|
||||
fn conflict_flows() -> Vec<Flow> {
|
||||
let flow = |id: &str, action: &str, op, text: &str| {
|
||||
let mut flow = chain(
|
||||
id,
|
||||
&["pull_request_mergeability"],
|
||||
action,
|
||||
TodoSink {
|
||||
to: Recipients::Field("author".to_owned()),
|
||||
op,
|
||||
key: "forge:{{ repo }}#{{ number }}:conflict".to_owned(),
|
||||
prefix: false,
|
||||
text: text.to_owned(),
|
||||
},
|
||||
);
|
||||
for node in &mut flow.nodes {
|
||||
if let NodeKind::Match { when } = &mut node.kind {
|
||||
when.push(Condition {
|
||||
field: "mergeability_changed".to_owned(),
|
||||
equals: "true".to_owned(),
|
||||
});
|
||||
}
|
||||
}
|
||||
flow
|
||||
};
|
||||
vec![
|
||||
flow(
|
||||
"conflict",
|
||||
"conflicting",
|
||||
Op::Upsert,
|
||||
"{{ kind }} {{ repo }}#{{ number }} no longer merges into its target branch: {{ title }} ({{ url }})",
|
||||
),
|
||||
flow("conflict-cleared", "mergeable", Op::Clear, ""),
|
||||
]
|
||||
}
|
||||
|
||||
/// The flows that tell a PR's author about its CI: the todo for a failed run,
|
||||
/// the same key rewritten for a cancelled one, and the clear a push makes.
|
||||
fn ci_flows() -> Vec<Flow> {
|
||||
|
|
@ -1077,6 +1133,42 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Both conflict flows act only when the mergeability event changed the
|
||||
/// PR's state, and write the author's `:conflict` key.
|
||||
#[test]
|
||||
fn the_conflict_flows_read_the_change_and_write_the_conflict_key() {
|
||||
for (id, action, op) in [
|
||||
("conflict", "conflicting", Op::Upsert),
|
||||
("conflict-cleared", "mergeable", Op::Clear),
|
||||
] {
|
||||
let f = builtin(id);
|
||||
assert_eq!(
|
||||
f.node("source").map(|n| &n.kind),
|
||||
Some(&NodeKind::Source {
|
||||
events: vec!["pull_request_mergeability".to_owned()]
|
||||
}),
|
||||
"{id}"
|
||||
);
|
||||
let want = |field: &str, equals: &str| Condition {
|
||||
field: field.to_owned(),
|
||||
equals: equals.to_owned(),
|
||||
};
|
||||
assert_eq!(
|
||||
f.node("match").map(|n| &n.kind),
|
||||
Some(&NodeKind::Match {
|
||||
when: vec![want("action", action), want("mergeability_changed", "true")]
|
||||
}),
|
||||
"{id}"
|
||||
);
|
||||
let Some(NodeKind::Todo(sink)) = f.node("sink").map(|n| &n.kind) else {
|
||||
panic!("{id}: no todo");
|
||||
};
|
||||
assert_eq!(sink.op, op, "{id}");
|
||||
assert_eq!(sink.key, "forge:{{ repo }}#{{ number }}:conflict", "{id}");
|
||||
assert_eq!(sink.to, Recipients::Field("author".to_owned()), "{id}");
|
||||
}
|
||||
}
|
||||
|
||||
/// What the schema offers is what the check accepts, and nothing else is.
|
||||
#[test]
|
||||
fn the_schema_lists_what_a_flow_may_name() {
|
||||
|
|
@ -1519,7 +1611,9 @@ mod tests {
|
|||
"head-classifier",
|
||||
"ci-failed",
|
||||
"ci-cancelled",
|
||||
"ci-cleared"
|
||||
"ci-cleared",
|
||||
"conflict",
|
||||
"conflict-cleared"
|
||||
],
|
||||
"an empty document is the built-ins"
|
||||
);
|
||||
|
|
@ -1541,6 +1635,8 @@ mod tests {
|
|||
"ci-failed",
|
||||
"ci-cancelled",
|
||||
"ci-cleared",
|
||||
"conflict",
|
||||
"conflict-cleared",
|
||||
"triage"
|
||||
]
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue