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 #").is_empty()); |
|
| … | 23 unchanged lines |