Skip to content

Commit 28d706d

Browse files
jamesadevineCopilot
andcommitted
feat(compile): add commit-message filter and on: deserialization tests
- commit-message filter: regex on Build.SourceVersionMessage (e.g., skip [skip-agent] in commit messages) - 3 new tests: commit-message gate step, on: config deserialization (simple + full with schedule/pipeline/pr) - Update schedule-syntax.md to reference on.schedule key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 596f025 commit 28d706d

2 files changed

Lines changed: 90 additions & 1 deletion

File tree

docs/schedule-syntax.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ _Part of the [ado-aw documentation](../AGENTS.md)._
44

55
## Schedule Syntax (Fuzzy Schedule Time Syntax)
66

7-
The `schedule` field supports a human-friendly fuzzy schedule syntax that automatically distributes execution times to prevent server load spikes. The syntax is based on the [Fuzzy Schedule Time Syntax Specification](https://github.com/githubnext/gh-aw/blob/main/docs/src/content/docs/reference/fuzzy-schedule-specification.md).
7+
The `on.schedule` field supports a human-friendly fuzzy schedule syntax that automatically distributes execution times to prevent server load spikes. The syntax is based on the [Fuzzy Schedule Time Syntax Specification](https://github.com/githubnext/gh-aw/blob/main/docs/src/content/docs/reference/fuzzy-schedule-specification.md).
8+
9+
Schedule is configured under the `on:` key:
10+
11+
```yaml
12+
on:
13+
schedule: daily around 14:00
14+
```
815
916
### Daily Schedules
1017

src/compile/pr_filters.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub(super) fn generate_pr_gate_step(filters: &PrFilters) -> String {
8484
generate_author_check(filters, &mut checks);
8585
generate_source_branch_check(filters, &mut checks);
8686
generate_target_branch_check(filters, &mut checks);
87+
generate_commit_message_check(filters, &mut checks);
8788

8889
// Tier 2 filters (REST API)
8990
if has_tier2_filters(filters) {
@@ -263,6 +264,26 @@ fn generate_target_branch_check(filters: &PrFilters, checks: &mut Vec<String>) {
263264
}
264265
}
265266

267+
fn generate_commit_message_check(filters: &PrFilters, checks: &mut Vec<String>) {
268+
if let Some(cm) = &filters.commit_message {
269+
let pattern = shell_escape(&cm.pattern);
270+
checks.push(format!(
271+
concat!(
272+
" # Commit message filter\n",
273+
" COMMIT_MSG=\"$(Build.SourceVersionMessage)\"\n",
274+
" if echo \"$COMMIT_MSG\" | grep -qE '{}'; then\n",
275+
" echo \"Filter: commit-message | Pattern: {} | Result: PASS\"\n",
276+
" else\n",
277+
" echo \"##[warning]PR filter commit-message did not match (pattern: {})\"\n",
278+
" echo \"##vso[build.addbuildtag]pr-gate:commit-message-mismatch\"\n",
279+
" SHOULD_RUN=false\n",
280+
" fi",
281+
),
282+
pattern, pattern, pattern,
283+
));
284+
}
285+
}
286+
266287
// ─── Tier 2 filter generators (REST API) ────────────────────────────────────
267288

268289
/// Generate the REST API preamble that fetches PR metadata.
@@ -1212,4 +1233,65 @@ triggers:
12121233
assert_eq!(filters.build_reason.as_ref().unwrap().include, vec!["PullRequest", "Manual"]);
12131234
assert_eq!(filters.expression.as_ref().unwrap(), "eq(variables['Custom.Flag'], 'true')");
12141235
}
1236+
1237+
#[test]
1238+
fn test_gate_step_commit_message() {
1239+
let filters = PrFilters {
1240+
commit_message: Some(PatternFilter { pattern: "^(?!.*\\[skip-agent\\])".into() }),
1241+
..Default::default()
1242+
};
1243+
let result = generate_pr_gate_step(&filters);
1244+
assert!(result.contains("Build.SourceVersionMessage"), "should check commit message variable");
1245+
assert!(result.contains("skip-agent"), "should include the pattern");
1246+
assert!(result.contains("pr-gate:commit-message-mismatch"), "should tag commit-message failures");
1247+
}
1248+
1249+
#[test]
1250+
fn test_on_config_deserialization_with_schedule() {
1251+
let yaml = r#"
1252+
on:
1253+
schedule: daily around 14:00
1254+
pr:
1255+
filters:
1256+
title:
1257+
match: "\\[review\\]"
1258+
"#;
1259+
let val: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
1260+
let oc: OnConfig = serde_yaml::from_value(val["on"].clone()).unwrap();
1261+
assert!(oc.schedule.is_some(), "should have schedule");
1262+
assert!(oc.pr.is_some(), "should have pr");
1263+
assert!(oc.pipeline.is_none(), "should not have pipeline");
1264+
}
1265+
1266+
#[test]
1267+
fn test_on_config_deserialization_full() {
1268+
let yaml = r#"
1269+
on:
1270+
schedule:
1271+
run: weekly on monday
1272+
branches: [main]
1273+
pipeline:
1274+
name: "Build Pipeline"
1275+
project: "OtherProject"
1276+
branches: [main]
1277+
pr:
1278+
branches:
1279+
include: [main]
1280+
filters:
1281+
title:
1282+
match: "\\[agent\\]"
1283+
commit-message:
1284+
match: "^(?!.*\\[skip-agent\\])"
1285+
"#;
1286+
let val: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
1287+
let oc: OnConfig = serde_yaml::from_value(val["on"].clone()).unwrap();
1288+
let schedule = oc.schedule.unwrap();
1289+
assert_eq!(schedule.expression(), "weekly on monday");
1290+
let pipeline = oc.pipeline.unwrap();
1291+
assert_eq!(pipeline.name, "Build Pipeline");
1292+
let pr = oc.pr.unwrap();
1293+
let filters = pr.filters.unwrap();
1294+
assert_eq!(filters.title.unwrap().pattern, "\\[agent\\]");
1295+
assert_eq!(filters.commit_message.unwrap().pattern, "^(?!.*\\[skip-agent\\])");
1296+
}
12151297
}

0 commit comments

Comments
 (0)