OXYGENHUB

Closing keywords close every issue they list 1966661b1880

'Closes #1, #2 and #3', 'Fixes: #4 #5' and 'resolves #6 & #7' close
every number in the list, not only the first; another repository's
a/b#N in a list is skipped, and a list ends where the prose starts.
Commit 7a31f384 had closed only #187 of the ten it named.

Closes #226
Author
Andy McCall @andymccall <[email protected]>
via
agent Claude Code <[email protected]> · delegate for changes, until 2026-11-13 19:00Z · key ea5f78160088
When
· 2026-10-15 22:34 UTC
Signature
✔ signed by Claude Code
Hash
1966661b1880eb65b7662730b9e609f06d0cbe16dddba1e9a8be8599f5296079
Depends on
18e381ea88c2 78001759604c
Depended on by
nothing yet
In bookmarks
api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
3 atoms, 0 tree operations, 1 file

Browse the tree at this change

modified crates/hub/src/render/refs.rs +25 −2
…
92 unchanged lines
93 93
}
94 94
95 95
pub fn closes(message: &str) -> Vec<i64> {
96 -
    let re = regex::Regex::new(r"(?i)\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)").unwrap();
97 -
    let mut out: Vec<i64> = re.captures_iter(message).filter_map(|c| c[1].parse().ok()).collect();
96 +
    // A keyword, then a list of numbers (#226): "Closes #1, #2 and #3",
97 +
    // "Fixes: #4 #5", "resolves #6 & #7". Another repository's a/b#N in
98 +
    // the list is not this repository's to close.
99 +
    let re = regex::Regex::new(
100 +
        r"(?i)\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?):?\s+((?:[A-Za-z0-9._-]+/[A-Za-z0-9._-]+)?#\d+(?:(?:\s*,\s*(?:and\s+)?|\s+and\s+|\s*&\s*|\s+)(?:[A-Za-z0-9._-]+/[A-Za-z0-9._-]+)?#\d+)*)",
101 +
    )
102 +
    .unwrap();
103 +
    let item = regex::Regex::new(r"(?:^|[^\w/])#(\d+)").unwrap();
104 +
    let mut out: Vec<i64> =
105 +
        re.captures_iter(message).flat_map(|c| item.captures_iter(&c[1]).filter_map(|i| i[1].parse().ok()).collect::<Vec<i64>>()).collect();
98 106
    out.sort();
99 107
    out.dedup();
100 108
    out
…
4 unchanged lines
105 113
    use super::*;
106 114
107 115
    #[test]
116 +
    fn a_closing_keyword_closes_every_number_it_lists() {
117 +
        // #226: commit 7a31f384's message closed only its first issue.
118 +
        assert_eq!(closes("Polish\n\nCloses #187, #188, #189, #190, #191, #192, #193, #194, #195, #196"), (187..=196).collect::<Vec<_>>());
119 +
        assert_eq!(closes("Closes #1, #2 and #3"), vec![1, 2, 3]);
120 +
        assert_eq!(closes("Closes #1, and #2"), vec![1, 2]);
121 +
        assert_eq!(closes("Fixes: #4 #5"), vec![4, 5]);
122 +
        assert_eq!(closes("resolves #6 & #7"), vec![6, 7]);
123 +
        assert_eq!(closes("Closes #1, closes #2\nFixed #8."), vec![1, 2, 8]);
124 +
        assert_eq!(closes("Closes #9, other/repo#10, #11"), vec![9, 11], "another repository's number is not ours");
125 +
        assert_eq!(closes("Refs #12, closes #13. See #14."), vec![13], "a list ends where the prose starts");
126 +
        assert!(closes("closest #1; disclosed #2").is_empty());
127 +
        assert_eq!(references("Closes #1, #2 and #3; refs #4"), vec![4], "listed closings are not plain references");
128 +
    }
129 +
130 +
    #[test]
108 131
    fn finds_references_but_not_closings_or_other_repositories() {
109 132
        assert_eq!(references("Faster blame (refs #14, #3)\n\nSee #3 again; closes #9; other/repo#5; a#2"), vec![3, 14]);
110 133
        assert!(references("Nothing to see; colour &#35;").is_empty());
…
23 unchanged lines