f5d62365
Add --label filter to issue list and patch list
a73x 2026-08-09 16:50
Commit message
src/cli.rs
| Old | New | ||
|---|---|---|---|
| @@ -18,12 +18,20 @@ pub trait Listable { | |||
| 18 | fn last_updated(&self) -> &str; | 18 | fn last_updated(&self) -> &str; |
| 19 | fn created_at(&self) -> &str; | 19 | fn created_at(&self) -> &str; |
| 20 | fn title(&self) -> &str; | 20 | fn title(&self) -> &str; |
| 21 | /// Labels this item carries, used for `--label` filtering. Types with no | ||
| 22 | /// label concept return an empty slice, so the filter is a no-op for them. | ||
| 23 | fn labels(&self) -> &[String] { | ||
| 24 | &[] | ||
| 25 | } | ||
| 21 | } | 26 | } |
| 22 | 27 | ||
| 23 | /// Filter by open status, sort by mode, then apply offset/limit pagination. | 28 | /// Filter by open status and labels, sort by mode, then apply offset/limit |
| 29 | /// pagination. `labels` uses OR semantics: an item matches if it carries any | ||
| 30 | /// of the given labels. An empty `labels` slice disables the filter. | ||
| 24 | pub fn filter_sort_paginate<T: Listable>( | 31 | pub fn filter_sort_paginate<T: Listable>( |
| 25 | items: Vec<T>, | 32 | items: Vec<T>, |
| 26 | show_closed: bool, | 33 | show_closed: bool, |
| 34 | labels: &[String], | ||
| 27 | sort: SortMode, | 35 | sort: SortMode, |
| 28 | offset: Option<usize>, | 36 | offset: Option<usize>, |
| 29 | limit: Option<usize>, | 37 | limit: Option<usize>, |
| @@ -31,6 +39,7 @@ pub fn filter_sort_paginate<T: Listable>( | |||
| 31 | let mut filtered: Vec<T> = items | 39 | let mut filtered: Vec<T> = items |
| 32 | .into_iter() | 40 | .into_iter() |
| 33 | .filter(|item| show_closed || item.is_open()) | 41 | .filter(|item| show_closed || item.is_open()) |
| 42 | .filter(|item| labels.is_empty() || item.labels().iter().any(|l| labels.contains(l))) | ||
| 34 | .collect(); | 43 | .collect(); |
| 35 | match sort { | 44 | match sort { |
| 36 | SortMode::Recent => filtered.sort_by(|a, b| b.last_updated().cmp(a.last_updated())), | 45 | SortMode::Recent => filtered.sort_by(|a, b| b.last_updated().cmp(a.last_updated())), |
| @@ -168,6 +177,9 @@ pub enum IssueCmd { | |||
| 168 | /// Sort order: recent (default), created, alpha | 177 | /// Sort order: recent (default), created, alpha |
| 169 | #[arg(long, default_value = "recent")] | 178 | #[arg(long, default_value = "recent")] |
| 170 | sort: SortMode, | 179 | sort: SortMode, |
| 180 | /// Filter by label (repeatable; matches issues carrying any of the given labels) | ||
| 181 | #[arg(long)] | ||
| 182 | label: Vec<String>, | ||
| 171 | }, | 183 | }, |
| 172 | /// Show issue details | 184 | /// Show issue details |
| 173 | Show { | 185 | Show { |
| @@ -330,6 +342,9 @@ pub enum PatchCmd { | |||
| 330 | /// Sort order: recent (default), created, alpha | 342 | /// Sort order: recent (default), created, alpha |
| 331 | #[arg(long, default_value = "recent")] | 343 | #[arg(long, default_value = "recent")] |
| 332 | sort: SortMode, | 344 | sort: SortMode, |
| 345 | /// Filter by label (repeatable; matches patches carrying any of the given labels) | ||
| 346 | #[arg(long)] | ||
| 347 | label: Vec<String>, | ||
| 333 | }, | 348 | }, |
| 334 | /// Show patch details | 349 | /// Show patch details |
| 335 | Show { | 350 | Show { |
src/issue.rs
| Old | New | ||
|---|---|---|---|
| @@ -48,9 +48,10 @@ pub fn list( | |||
| 48 | limit: Option<usize>, | 48 | limit: Option<usize>, |
| 49 | offset: Option<usize>, | 49 | offset: Option<usize>, |
| 50 | sort: SortMode, | 50 | sort: SortMode, |
| 51 | labels: &[String], | ||
| 51 | ) -> Result<Vec<ListEntry>, crate::error::Error> { | 52 | ) -> Result<Vec<ListEntry>, crate::error::Error> { |
| 52 | let issues = load_issues(repo, show_archived)?; | 53 | let issues = load_issues(repo, show_archived)?; |
| 53 | let filtered = cli::filter_sort_paginate(issues, show_closed, sort, offset, limit); | 54 | let filtered = cli::filter_sort_paginate(issues, show_closed, labels, sort, offset, limit); |
| 54 | let entries = filtered | 55 | let entries = filtered |
| 55 | .into_iter() | 56 | .into_iter() |
| 56 | .map(|issue| { | 57 | .map(|issue| { |
| @@ -61,6 +62,7 @@ pub fn list( | |||
| 61 | Ok(entries) | 62 | Ok(entries) |
| 62 | } | 63 | } |
| 63 | 64 | ||
| 65 | #[allow(clippy::too_many_arguments)] | ||
| 64 | pub fn list_to_writer( | 66 | pub fn list_to_writer( |
| 65 | repo: &Repository, | 67 | repo: &Repository, |
| 66 | show_closed: bool, | 68 | show_closed: bool, |
| @@ -68,9 +70,10 @@ pub fn list_to_writer( | |||
| 68 | limit: Option<usize>, | 70 | limit: Option<usize>, |
| 69 | offset: Option<usize>, | 71 | offset: Option<usize>, |
| 70 | sort: SortMode, | 72 | sort: SortMode, |
| 73 | labels: &[String], | ||
| 71 | writer: &mut dyn std::io::Write, | 74 | writer: &mut dyn std::io::Write, |
| 72 | ) -> Result<(), crate::error::Error> { | 75 | ) -> Result<(), crate::error::Error> { |
| 73 | let entries = list(repo, show_closed, show_archived, limit, offset, sort)?; | 76 | let entries = list(repo, show_closed, show_archived, limit, offset, sort, labels)?; |
| 74 | if entries.is_empty() { | 77 | if entries.is_empty() { |
| 75 | writeln!(writer, "No issues found.").ok(); | 78 | writeln!(writer, "No issues found.").ok(); |
| 76 | return Ok(()); | 79 | return Ok(()); |
| @@ -120,9 +123,10 @@ pub fn list_json( | |||
| 120 | show_closed: bool, | 123 | show_closed: bool, |
| 121 | show_archived: bool, | 124 | show_archived: bool, |
| 122 | sort: SortMode, | 125 | sort: SortMode, |
| 126 | labels: &[String], | ||
| 123 | ) -> Result<String, crate::error::Error> { | 127 | ) -> Result<String, crate::error::Error> { |
| 124 | let issues = load_issues(repo, show_archived)?; | 128 | let issues = load_issues(repo, show_archived)?; |
| 125 | let filtered = cli::filter_sort_paginate(issues, show_closed, sort, None, None); | 129 | let filtered = cli::filter_sort_paginate(issues, show_closed, labels, sort, None, None); |
| 126 | Ok(serde_json::to_string_pretty(&filtered)?) | 130 | Ok(serde_json::to_string_pretty(&filtered)?) |
| 127 | } | 131 | } |
| 128 | 132 | ||
src/lib.rs
| Old | New | ||
|---|---|---|---|
| @@ -129,13 +129,14 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 129 | offset, | 129 | offset, |
| 130 | json, | 130 | json, |
| 131 | sort, | 131 | sort, |
| 132 | label, | ||
| 132 | } => { | 133 | } => { |
| 133 | if json { | 134 | if json { |
| 134 | let output = issue::list_json(repo, all, archived, sort)?; | 135 | let output = issue::list_json(repo, all, archived, sort, &label)?; |
| 135 | println!("{}", output); | 136 | println!("{}", output); |
| 136 | return Ok(()); | 137 | return Ok(()); |
| 137 | } | 138 | } |
| 138 | let entries = issue::list(repo, all, archived, limit, offset, sort)?; | 139 | let entries = issue::list(repo, all, archived, limit, offset, sort, &label)?; |
| 139 | if entries.is_empty() { | 140 | if entries.is_empty() { |
| 140 | println!("No issues found."); | 141 | println!("No issues found."); |
| 141 | } else { | 142 | } else { |
| @@ -347,13 +348,14 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 347 | offset, | 348 | offset, |
| 348 | json, | 349 | json, |
| 349 | sort, | 350 | sort, |
| 351 | label, | ||
| 350 | } => { | 352 | } => { |
| 351 | if json { | 353 | if json { |
| 352 | let output = patch::list_json(repo, all, archived, sort)?; | 354 | let output = patch::list_json(repo, all, archived, sort, &label)?; |
| 353 | println!("{}", output); | 355 | println!("{}", output); |
| 354 | return Ok(()); | 356 | return Ok(()); |
| 355 | } | 357 | } |
| 356 | let entries = patch::list(repo, all, archived, limit, offset, sort)?; | 358 | let entries = patch::list(repo, all, archived, limit, offset, sort, &label)?; |
| 357 | if entries.is_empty() { | 359 | if entries.is_empty() { |
| 358 | println!("No patches found."); | 360 | println!("No patches found."); |
| 359 | } else { | 361 | } else { |
src/patch.rs
| Old | New | ||
|---|---|---|---|
| @@ -170,13 +170,14 @@ pub fn list( | |||
| 170 | limit: Option<usize>, | 170 | limit: Option<usize>, |
| 171 | offset: Option<usize>, | 171 | offset: Option<usize>, |
| 172 | sort: SortMode, | 172 | sort: SortMode, |
| 173 | labels: &[String], | ||
| 173 | ) -> Result<Vec<ListEntry>, crate::error::Error> { | 174 | ) -> Result<Vec<ListEntry>, crate::error::Error> { |
| 174 | let patches = if show_archived { | 175 | let patches = if show_archived { |
| 175 | state::list_patches_with_archived(repo)? | 176 | state::list_patches_with_archived(repo)? |
| 176 | } else { | 177 | } else { |
| 177 | state::list_patches(repo)? | 178 | state::list_patches(repo)? |
| 178 | }; | 179 | }; |
| 179 | let filtered = cli::filter_sort_paginate(patches, show_closed, sort, offset, limit); | 180 | let filtered = cli::filter_sort_paginate(patches, show_closed, labels, sort, offset, limit); |
| 180 | let entries = filtered | 181 | let entries = filtered |
| 181 | .into_iter() | 182 | .into_iter() |
| 182 | .map(|patch| { | 183 | .map(|patch| { |
| @@ -187,6 +188,7 @@ pub fn list( | |||
| 187 | Ok(entries) | 188 | Ok(entries) |
| 188 | } | 189 | } |
| 189 | 190 | ||
| 191 | #[allow(clippy::too_many_arguments)] | ||
| 190 | pub fn list_to_writer( | 192 | pub fn list_to_writer( |
| 191 | repo: &Repository, | 193 | repo: &Repository, |
| 192 | show_closed: bool, | 194 | show_closed: bool, |
| @@ -194,9 +196,10 @@ pub fn list_to_writer( | |||
| 194 | limit: Option<usize>, | 196 | limit: Option<usize>, |
| 195 | offset: Option<usize>, | 197 | offset: Option<usize>, |
| 196 | sort: SortMode, | 198 | sort: SortMode, |
| 199 | labels: &[String], | ||
| 197 | writer: &mut dyn std::io::Write, | 200 | writer: &mut dyn std::io::Write, |
| 198 | ) -> Result<(), crate::error::Error> { | 201 | ) -> Result<(), crate::error::Error> { |
| 199 | let entries = list(repo, show_closed, show_archived, limit, offset, sort)?; | 202 | let entries = list(repo, show_closed, show_archived, limit, offset, sort, labels)?; |
| 200 | if entries.is_empty() { | 203 | if entries.is_empty() { |
| 201 | writeln!(writer, "No patches found.").ok(); | 204 | writeln!(writer, "No patches found.").ok(); |
| 202 | return Ok(()); | 205 | return Ok(()); |
| @@ -227,8 +230,9 @@ pub fn list_json( | |||
| 227 | show_closed: bool, | 230 | show_closed: bool, |
| 228 | show_archived: bool, | 231 | show_archived: bool, |
| 229 | sort: SortMode, | 232 | sort: SortMode, |
| 233 | labels: &[String], | ||
| 230 | ) -> Result<String, crate::error::Error> { | 234 | ) -> Result<String, crate::error::Error> { |
| 231 | let entries = list(repo, show_closed, show_archived, None, None, sort)?; | 235 | let entries = list(repo, show_closed, show_archived, None, None, sort, labels)?; |
| 232 | let patches: Vec<&PatchState> = entries.iter().map(|e| &e.patch).collect(); | 236 | let patches: Vec<&PatchState> = entries.iter().map(|e| &e.patch).collect(); |
| 233 | Ok(serde_json::to_string_pretty(&patches)?) | 237 | Ok(serde_json::to_string_pretty(&patches)?) |
| 234 | } | 238 | } |
src/state.rs
| Old | New | ||
|---|---|---|---|
| @@ -253,6 +253,9 @@ impl crate::cli::Listable for IssueState { | |||
| 253 | fn title(&self) -> &str { | 253 | fn title(&self) -> &str { |
| 254 | &self.title | 254 | &self.title |
| 255 | } | 255 | } |
| 256 | fn labels(&self) -> &[String] { | ||
| 257 | &self.labels | ||
| 258 | } | ||
| 256 | } | 259 | } |
| 257 | 260 | ||
| 258 | impl crate::cli::Listable for PatchState { | 261 | impl crate::cli::Listable for PatchState { |
tests/cli_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -278,6 +278,93 @@ fn test_issue_label_shown_in_list() { | |||
| 278 | assert!(out.contains("enhancement")); | 278 | assert!(out.contains("enhancement")); |
| 279 | } | 279 | } |
| 280 | 280 | ||
| 281 | #[test] | ||
| 282 | fn test_issue_list_filters_by_label() { | ||
| 283 | let repo = TestRepo::new("Alice", "alice@example.com"); | ||
| 284 | let bug_id = repo.issue_open("A bug"); | ||
| 285 | repo.issue_open("No label"); | ||
| 286 | repo.run_ok(&["issue", "label", &bug_id, "bug"]); | ||
| 287 | |||
| 288 | let out = repo.run_ok(&["issue", "list", "--label", "bug"]); | ||
| 289 | assert!(out.contains("A bug")); | ||
| 290 | assert!(!out.contains("No label")); | ||
| 291 | } | ||
| 292 | |||
| 293 | #[test] | ||
| 294 | fn test_issue_list_label_or_semantics() { | ||
| 295 | let repo = TestRepo::new("Alice", "alice@example.com"); | ||
| 296 | let bug_id = repo.issue_open("A bug"); | ||
| 297 | let docs_id = repo.issue_open("Docs issue"); | ||
| 298 | repo.issue_open("Unrelated"); | ||
| 299 | repo.run_ok(&["issue", "label", &bug_id, "bug"]); | ||
| 300 | repo.run_ok(&["issue", "label", &docs_id, "docs"]); | ||
| 301 | |||
| 302 | let out = repo.run_ok(&["issue", "list", "--label", "bug", "--label", "docs"]); | ||
| 303 | assert!(out.contains("A bug")); | ||
| 304 | assert!(out.contains("Docs issue")); | ||
| 305 | assert!(!out.contains("Unrelated")); | ||
| 306 | } | ||
| 307 | |||
| 308 | #[test] | ||
| 309 | fn test_issue_list_label_no_match() { | ||
| 310 | let repo = TestRepo::new("Alice", "alice@example.com"); | ||
| 311 | repo.issue_open("Unlabeled"); | ||
| 312 | |||
| 313 | let out = repo.run_ok(&["issue", "list", "--label", "nonexistent"]); | ||
| 314 | assert!(out.contains("No issues found")); | ||
| 315 | } | ||
| 316 | |||
| 317 | #[test] | ||
| 318 | fn test_issue_list_label_composes_with_all_archived() { | ||
| 319 | let repo = TestRepo::new("Alice", "alice@example.com"); | ||
| 320 | let id = repo.issue_open("Closed labeled"); | ||
| 321 | repo.run_ok(&["issue", "label", &id, "bug"]); | ||
| 322 | repo.run_ok(&["issue", "close", &id]); | ||
| 323 | |||
| 324 | // Closed issues are archived, so --label alone (without --all --archived) | ||
| 325 | // must not show it, even though it carries a matching label. | ||
| 326 | let out = repo.run_ok(&["issue", "list", "--label", "bug"]); | ||
| 327 | assert!(!out.contains("Closed labeled")); | ||
| 328 | |||
| 329 | let out = repo.run_ok(&["issue", "list", "--all", "--archived", "--label", "bug"]); | ||
| 330 | assert!(out.contains("Closed labeled")); | ||
| 331 | } | ||
| 332 | |||
| 333 | #[test] | ||
| 334 | fn test_issue_list_label_limit_offset_applies_after_filter() { | ||
| 335 | let repo = TestRepo::new("Alice", "alice@example.com"); | ||
| 336 | let a = repo.issue_open("Alpha"); | ||
| 337 | let _b = repo.issue_open("Beta unlabeled"); | ||
| 338 | let c = repo.issue_open("Gamma"); | ||
| 339 | repo.run_ok(&["issue", "label", &a, "keep"]); | ||
| 340 | repo.run_ok(&["issue", "label", &c, "keep"]); | ||
| 341 | |||
| 342 | // Only "Alpha" and "Gamma" carry the "keep" label. Sorting alphabetically | ||
| 343 | // and requesting a single result with offset 1 should skip "Alpha" and | ||
| 344 | // land on "Gamma" -- proving limit/offset apply to the filtered set, not | ||
| 345 | // the unfiltered one (which would offset past "Beta unlabeled" instead). | ||
| 346 | let out = repo.run_ok(&[ | ||
| 347 | "issue", "list", "--label", "keep", "--sort", "alpha", "--offset", "1", "-n", "1", | ||
| 348 | ]); | ||
| 349 | assert!(out.contains("Gamma")); | ||
| 350 | assert!(!out.contains("Alpha")); | ||
| 351 | assert!(!out.contains("Beta")); | ||
| 352 | } | ||
| 353 | |||
| 354 | #[test] | ||
| 355 | fn test_issue_list_label_json() { | ||
| 356 | let repo = TestRepo::new("Alice", "alice@example.com"); | ||
| 357 | let bug_id = repo.issue_open("A bug"); | ||
| 358 | repo.issue_open("No label"); | ||
| 359 | repo.run_ok(&["issue", "label", &bug_id, "bug"]); | ||
| 360 | |||
| 361 | let out = repo.run_ok(&["issue", "list", "--json", "--label", "bug"]); | ||
| 362 | let parsed: serde_json::Value = serde_json::from_str(&out).unwrap(); | ||
| 363 | let arr = parsed.as_array().unwrap(); | ||
| 364 | assert_eq!(arr.len(), 1); | ||
| 365 | assert_eq!(arr[0]["title"], "A bug"); | ||
| 366 | } | ||
| 367 | |||
| 281 | // =========================================================================== | 368 | // =========================================================================== |
| 282 | // Issue assignees | 369 | // Issue assignees |
| 283 | // =========================================================================== | 370 | // =========================================================================== |
| @@ -505,6 +592,23 @@ fn test_patch_list_filters_by_status() { | |||
| 505 | } | 592 | } |
| 506 | 593 | ||
| 507 | #[test] | 594 | #[test] |
| 595 | fn test_patch_list_label_flag_accepted() { | ||
| 596 | // Patches have no way to carry labels yet (there is no `patch label` | ||
| 597 | // command, unlike `issue label`), so `--label` can never match anything | ||
| 598 | // today. The flag must still parse -- including repeated occurrences -- | ||
| 599 | // and correctly filter every patch out rather than erroring or being | ||
| 600 | // silently ignored. | ||
| 601 | let repo = TestRepo::new("Alice", "alice@example.com"); | ||
| 602 | repo.patch_create("Open patch"); | ||
| 603 | |||
| 604 | let out = repo.run_ok(&["patch", "list"]); | ||
| 605 | assert!(out.contains("Open patch")); | ||
| 606 | |||
| 607 | let out = repo.run_ok(&["patch", "list", "--label", "bug", "--label", "docs"]); | ||
| 608 | assert!(out.contains("No patches found")); | ||
| 609 | } | ||
| 610 | |||
| 611 | #[test] | ||
| 508 | fn test_patch_comment() { | 612 | fn test_patch_comment() { |
| 509 | let repo = TestRepo::new("Alice", "alice@example.com"); | 613 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 510 | let id = repo.patch_create("Review me"); | 614 | let id = repo.patch_create("Review me"); |
tests/collab_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -1253,6 +1253,7 @@ fn capture_issue_list( | |||
| 1253 | limit, | 1253 | limit, |
| 1254 | offset, | 1254 | offset, |
| 1255 | git_collab::cli::SortMode::Recent, | 1255 | git_collab::cli::SortMode::Recent, |
| 1256 | &[], | ||
| 1256 | &mut buf, | 1257 | &mut buf, |
| 1257 | ) | 1258 | ) |
| 1258 | .unwrap(); | 1259 | .unwrap(); |
| @@ -1274,6 +1275,7 @@ fn capture_patch_list( | |||
| 1274 | limit, | 1275 | limit, |
| 1275 | offset, | 1276 | offset, |
| 1276 | git_collab::cli::SortMode::Recent, | 1277 | git_collab::cli::SortMode::Recent, |
| 1278 | &[], | ||
| 1277 | &mut buf, | 1279 | &mut buf, |
| 1278 | ) | 1280 | ) |
| 1279 | .unwrap(); | 1281 | .unwrap(); |
| @@ -1490,7 +1492,7 @@ fn test_issue_list_json_output() { | |||
| 1490 | open_issue(&repo, &bob(), "Issue two"); | 1492 | open_issue(&repo, &bob(), "Issue two"); |
| 1491 | 1493 | ||
| 1492 | let json_str = | 1494 | let json_str = |
| 1493 | git_collab::issue::list_json(&repo, false, false, git_collab::cli::SortMode::Recent) | 1495 | git_collab::issue::list_json(&repo, false, false, git_collab::cli::SortMode::Recent, &[]) |
| 1494 | .unwrap(); | 1496 | .unwrap(); |
| 1495 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | 1497 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); |
| 1496 | let arr = value.as_array().unwrap(); | 1498 | let arr = value.as_array().unwrap(); |
| @@ -1512,14 +1514,14 @@ fn test_issue_list_json_filters_closed() { | |||
| 1512 | 1514 | ||
| 1513 | // Without --all, only open issues | 1515 | // Without --all, only open issues |
| 1514 | let json_str = | 1516 | let json_str = |
| 1515 | git_collab::issue::list_json(&repo, false, false, git_collab::cli::SortMode::Recent) | 1517 | git_collab::issue::list_json(&repo, false, false, git_collab::cli::SortMode::Recent, &[]) |
| 1516 | .unwrap(); | 1518 | .unwrap(); |
| 1517 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | 1519 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); |
| 1518 | assert_eq!(value.as_array().unwrap().len(), 1); | 1520 | assert_eq!(value.as_array().unwrap().len(), 1); |
| 1519 | 1521 | ||
| 1520 | // With --all, both | 1522 | // With --all, both |
| 1521 | let json_str = | 1523 | let json_str = |
| 1522 | git_collab::issue::list_json(&repo, true, false, git_collab::cli::SortMode::Recent) | 1524 | git_collab::issue::list_json(&repo, true, false, git_collab::cli::SortMode::Recent, &[]) |
| 1523 | .unwrap(); | 1525 | .unwrap(); |
| 1524 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | 1526 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); |
| 1525 | assert_eq!(value.as_array().unwrap().len(), 2); | 1527 | assert_eq!(value.as_array().unwrap().len(), 2); |
| @@ -1548,7 +1550,7 @@ fn test_patch_list_json_output() { | |||
| 1548 | create_patch(&repo, &bob(), "Patch two"); | 1550 | create_patch(&repo, &bob(), "Patch two"); |
| 1549 | 1551 | ||
| 1550 | let json_str = | 1552 | let json_str = |
| 1551 | git_collab::patch::list_json(&repo, false, false, git_collab::cli::SortMode::Recent) | 1553 | git_collab::patch::list_json(&repo, false, false, git_collab::cli::SortMode::Recent, &[]) |
| 1552 | .unwrap(); | 1554 | .unwrap(); |
| 1553 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | 1555 | let value: serde_json::Value = serde_json::from_str(&json_str).unwrap(); |
| 1554 | let arr = value.as_array().unwrap(); | 1556 | let arr = value.as_array().unwrap(); |
tests/sort_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -142,7 +142,7 @@ fn test_issue_default_sort_by_recency() { | |||
| 142 | 142 | ||
| 143 | // Default sort = recent: issue A (last_updated=2025-12) should come first | 143 | // Default sort = recent: issue A (last_updated=2025-12) should come first |
| 144 | let entries = | 144 | let entries = |
| 145 | git_collab::issue::list(&repo, true, false, None, None, SortMode::Recent).unwrap(); | 145 | git_collab::issue::list(&repo, true, false, None, None, SortMode::Recent, &[]).unwrap(); |
| 146 | assert_eq!(entries.len(), 2); | 146 | assert_eq!(entries.len(), 2); |
| 147 | assert_eq!(entries[0].issue.title, "Alpha issue"); | 147 | assert_eq!(entries[0].issue.title, "Alpha issue"); |
| 148 | assert_eq!(entries[1].issue.title, "Beta issue"); | 148 | assert_eq!(entries[1].issue.title, "Beta issue"); |
| @@ -168,7 +168,7 @@ fn test_issue_sort_by_created() { | |||
| 168 | 168 | ||
| 169 | // Sort by created: B (2025-06) comes first (descending) | 169 | // Sort by created: B (2025-06) comes first (descending) |
| 170 | let entries = | 170 | let entries = |
| 171 | git_collab::issue::list(&repo, true, false, None, None, SortMode::Created).unwrap(); | 171 | git_collab::issue::list(&repo, true, false, None, None, SortMode::Created, &[]).unwrap(); |
| 172 | assert_eq!(entries.len(), 2); | 172 | assert_eq!(entries.len(), 2); |
| 173 | assert_eq!(entries[0].issue.title, "Beta issue"); | 173 | assert_eq!(entries[0].issue.title, "Beta issue"); |
| 174 | assert_eq!(entries[1].issue.title, "Alpha issue"); | 174 | assert_eq!(entries[1].issue.title, "Alpha issue"); |
| @@ -183,7 +183,8 @@ fn test_issue_sort_alpha() { | |||
| 183 | open_issue_at(&repo, &alice(), "Apple issue", "2025-06-01T00:00:00Z"); | 183 | open_issue_at(&repo, &alice(), "Apple issue", "2025-06-01T00:00:00Z"); |
| 184 | open_issue_at(&repo, &alice(), "Mango issue", "2025-03-01T00:00:00Z"); | 184 | open_issue_at(&repo, &alice(), "Mango issue", "2025-03-01T00:00:00Z"); |
| 185 | 185 | ||
| 186 | let entries = git_collab::issue::list(&repo, true, false, None, None, SortMode::Alpha).unwrap(); | 186 | let entries = |
| 187 | git_collab::issue::list(&repo, true, false, None, None, SortMode::Alpha, &[]).unwrap(); | ||
| 187 | assert_eq!(entries.len(), 3); | 188 | assert_eq!(entries.len(), 3); |
| 188 | assert_eq!(entries[0].issue.title, "Apple issue"); | 189 | assert_eq!(entries[0].issue.title, "Apple issue"); |
| 189 | assert_eq!(entries[1].issue.title, "Mango issue"); | 190 | assert_eq!(entries[1].issue.title, "Mango issue"); |
| @@ -230,7 +231,7 @@ fn test_patch_default_sort_by_recency() { | |||
| 230 | let (_, _) = create_patch_at(&repo, &alice(), "Beta patch", "2025-06-01T00:00:00Z"); | 231 | let (_, _) = create_patch_at(&repo, &alice(), "Beta patch", "2025-06-01T00:00:00Z"); |
| 231 | 232 | ||
| 232 | let entries = | 233 | let entries = |
| 233 | git_collab::patch::list(&repo, true, false, None, None, SortMode::Recent).unwrap(); | 234 | git_collab::patch::list(&repo, true, false, None, None, SortMode::Recent, &[]).unwrap(); |
| 234 | assert_eq!(entries.len(), 2); | 235 | assert_eq!(entries.len(), 2); |
| 235 | assert_eq!(entries[0].patch.title, "Alpha patch"); | 236 | assert_eq!(entries[0].patch.title, "Alpha patch"); |
| 236 | assert_eq!(entries[1].patch.title, "Beta patch"); | 237 | assert_eq!(entries[1].patch.title, "Beta patch"); |
| @@ -253,7 +254,7 @@ fn test_patch_sort_by_created() { | |||
| 253 | let (_, _) = create_patch_at(&repo, &alice(), "Beta patch", "2025-06-01T00:00:00Z"); | 254 | let (_, _) = create_patch_at(&repo, &alice(), "Beta patch", "2025-06-01T00:00:00Z"); |
| 254 | 255 | ||
| 255 | let entries = | 256 | let entries = |
| 256 | git_collab::patch::list(&repo, true, false, None, None, SortMode::Created).unwrap(); | 257 | git_collab::patch::list(&repo, true, false, None, None, SortMode::Created, &[]).unwrap(); |
| 257 | assert_eq!(entries.len(), 2); | 258 | assert_eq!(entries.len(), 2); |
| 258 | assert_eq!(entries[0].patch.title, "Beta patch"); | 259 | assert_eq!(entries[0].patch.title, "Beta patch"); |
| 259 | assert_eq!(entries[1].patch.title, "Alpha patch"); | 260 | assert_eq!(entries[1].patch.title, "Alpha patch"); |
| @@ -268,7 +269,8 @@ fn test_patch_sort_alpha() { | |||
| 268 | create_patch_at(&repo, &alice(), "Apple patch", "2025-06-01T00:00:00Z"); | 269 | create_patch_at(&repo, &alice(), "Apple patch", "2025-06-01T00:00:00Z"); |
| 269 | create_patch_at(&repo, &alice(), "Mango patch", "2025-03-01T00:00:00Z"); | 270 | create_patch_at(&repo, &alice(), "Mango patch", "2025-03-01T00:00:00Z"); |
| 270 | 271 | ||
| 271 | let entries = git_collab::patch::list(&repo, true, false, None, None, SortMode::Alpha).unwrap(); | 272 | let entries = |
| 273 | git_collab::patch::list(&repo, true, false, None, None, SortMode::Alpha, &[]).unwrap(); | ||
| 272 | assert_eq!(entries.len(), 3); | 274 | assert_eq!(entries.len(), 3); |
| 273 | assert_eq!(entries[0].patch.title, "Apple patch"); | 275 | assert_eq!(entries[0].patch.title, "Apple patch"); |
| 274 | assert_eq!(entries[1].patch.title, "Mango patch"); | 276 | assert_eq!(entries[1].patch.title, "Mango patch"); |