702a1e6f
Run cargo fmt over the whole tree
a73x 2026-09-05 17:13
Commit message
src/abbrev.rs
| Old | New | ||
|---|---|---|---|
| @@ -181,10 +181,7 @@ impl Abbrev { | |||
| 181 | /// Ids are ASCII hex, so bytes and characters coincide and slicing at a byte | 181 | /// Ids are ASCII hex, so bytes and characters coincide and slicing at a byte |
| 182 | /// index is always on a character boundary. | 182 | /// index is always on a character boundary. |
| 183 | fn common_prefix_len(a: &str, b: &str) -> usize { | 183 | fn common_prefix_len(a: &str, b: &str) -> usize { |
| 184 | a.bytes() | 184 | a.bytes().zip(b.bytes()).take_while(|(x, y)| x == y).count() |
| 185 | .zip(b.bytes()) | ||
| 186 | .take_while(|(x, y)| x == y) | ||
| 187 | .count() | ||
| 188 | } | 185 | } |
| 189 | 186 | ||
| 190 | /// Read `collab.abbrev`, or `None` to use the automatic width. | 187 | /// Read `collab.abbrev`, or `None` to use the automatic width. |
| @@ -320,7 +317,14 @@ mod tests { | |||
| 320 | #[test] | 317 | #[test] |
| 321 | fn every_printed_prefix_matches_exactly_one_id() { | 318 | fn every_printed_prefix_matches_exactly_one_id() { |
| 322 | let all = ids(&[ | 319 | let all = ids(&[ |
| 323 | "aaaaaaaa1", "aaaaaaaa2", "aaaaaaab", "bbbbbbbb", "bbbbbbbc", "c", "d", "e", | 320 | "aaaaaaaa1", |
| 321 | "aaaaaaaa2", | ||
| 322 | "aaaaaaab", | ||
| 323 | "bbbbbbbb", | ||
| 324 | "bbbbbbbc", | ||
| 325 | "c", | ||
| 326 | "d", | ||
| 327 | "e", | ||
| 324 | ]); | 328 | ]); |
| 325 | let a = Abbrev::new(all.clone()); | 329 | let a = Abbrev::new(all.clone()); |
| 326 | for id in &all { | 330 | for id in &all { |
src/body.rs
| Old | New | ||
|---|---|---|---|
| @@ -150,7 +150,11 @@ mod tests { | |||
| 150 | fn both_options_together_are_rejected() { | 150 | fn both_options_together_are_rejected() { |
| 151 | let args = BodyArgs::new(Some("a"), Some("b")); | 151 | let args = BodyArgs::new(Some("a"), Some("b")); |
| 152 | let err = format!("{}", resolve_required(&args, "", "comment").unwrap_err()); | 152 | let err = format!("{}", resolve_required(&args, "", "comment").unwrap_err()); |
| 153 | assert!(err.contains("--body") && err.contains("--body-file"), "{}", err); | 153 | assert!( |
| 154 | err.contains("--body") && err.contains("--body-file"), | ||
| 155 | "{}", | ||
| 156 | err | ||
| 157 | ); | ||
| 154 | } | 158 | } |
| 155 | 159 | ||
| 156 | #[test] | 160 | #[test] |
| @@ -201,5 +205,4 @@ mod tests { | |||
| 201 | let args = BodyArgs::new(Some(""), None); | 205 | let args = BodyArgs::new(Some(""), None); |
| 202 | assert_eq!(resolve_optional(&args).unwrap(), Some(String::new())); | 206 | assert_eq!(resolve_optional(&args).unwrap(), Some(String::new())); |
| 203 | } | 207 | } |
| 204 | |||
| 205 | } | 208 | } |
src/build_provenance.rs
| Old | New | ||
|---|---|---|---|
| @@ -165,10 +165,7 @@ fn in_our_own_checkout() -> bool { | |||
| 165 | let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") else { | 165 | let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") else { |
| 166 | return false; | 166 | return false; |
| 167 | }; | 167 | }; |
| 168 | match ( | 168 | match (fs::canonicalize(&toplevel), fs::canonicalize(&manifest_dir)) { |
| 169 | fs::canonicalize(&toplevel), | ||
| 170 | fs::canonicalize(&manifest_dir), | ||
| 171 | ) { | ||
| 172 | (Ok(a), Ok(b)) => a == b, | 169 | (Ok(a), Ok(b)) => a == b, |
| 173 | _ => false, | 170 | _ => false, |
| 174 | } | 171 | } |
src/cli.rs
| Old | New | ||
|---|---|---|---|
| @@ -360,7 +360,6 @@ pub enum HookCmd { | |||
| 360 | /// argument for the flag. | 360 | /// argument for the flag. |
| 361 | const BODY_FILE_HELP: &str = "Read the body from a file, or from stdin with '-'"; | 361 | const BODY_FILE_HELP: &str = "Read the body from a file, or from stdin with '-'"; |
| 362 | 362 | ||
| 363 | |||
| 364 | // --------------------------------------------------------------------------- | 363 | // --------------------------------------------------------------------------- |
| 365 | // `--json` is per-command rather than a global flag, and the four `wants_json` | 364 | // `--json` is per-command rather than a global flag, and the four `wants_json` |
| 366 | // implementations below are the price of that. | 365 | // implementations below are the price of that. |
src/dag.rs
| Old | New | ||
|---|---|---|---|
| @@ -229,13 +229,13 @@ pub fn reconcile( | |||
| 229 | return Ok((local_oid, ReconcileOutcome::AlreadyCurrent)); | 229 | return Ok((local_oid, ReconcileOutcome::AlreadyCurrent)); |
| 230 | } | 230 | } |
| 231 | 231 | ||
| 232 | let merge_base = repo.merge_base(local_oid, remote_oid).map_err(|e| { | 232 | let merge_base = |
| 233 | Error::DisjointHistories { | 233 | repo.merge_base(local_oid, remote_oid) |
| 234 | local_ref: local_ref.to_string(), | 234 | .map_err(|e| Error::DisjointHistories { |
| 235 | remote_ref: remote_ref.to_string(), | 235 | local_ref: local_ref.to_string(), |
| 236 | detail: e.message().to_string(), | 236 | remote_ref: remote_ref.to_string(), |
| 237 | } | 237 | detail: e.message().to_string(), |
| 238 | })?; | 238 | })?; |
| 239 | 239 | ||
| 240 | if merge_base == remote_oid { | 240 | if merge_base == remote_oid { |
| 241 | // Remote is ancestor of local — local is ahead | 241 | // Remote is ancestor of local — local is ahead |
| @@ -350,7 +350,9 @@ fn commit_message(action: &Action) -> String { | |||
| 350 | Action::IssueComment { .. } => "issue: comment".to_string(), | 350 | Action::IssueComment { .. } => "issue: comment".to_string(), |
| 351 | Action::IssueClose { .. } => "issue: close".to_string(), | 351 | Action::IssueClose { .. } => "issue: close".to_string(), |
| 352 | Action::IssueReopen => "issue: reopen".to_string(), | 352 | Action::IssueReopen => "issue: reopen".to_string(), |
| 353 | Action::IssueCommitLink { commit } => format!("issue: commit link {}", &commit[..commit.len().min(7)]), | 353 | Action::IssueCommitLink { commit } => { |
| 354 | format!("issue: commit link {}", &commit[..commit.len().min(7)]) | ||
| 355 | } | ||
| 354 | Action::PatchCreate { title, .. } => format!("patch: create \"{}\"", title), | 356 | Action::PatchCreate { title, .. } => format!("patch: create \"{}\"", title), |
| 355 | Action::PatchLabel { ref label } => format!("patch: label \"{}\"", label), | 357 | Action::PatchLabel { ref label } => format!("patch: label \"{}\"", label), |
| 356 | Action::PatchUnlabel { ref label } => format!("patch: unlabel \"{}\"", label), | 358 | Action::PatchUnlabel { ref label } => format!("patch: unlabel \"{}\"", label), |
| @@ -361,7 +363,10 @@ fn commit_message(action: &Action) -> String { | |||
| 361 | Action::PatchInlineComment { ref file, line, .. } => { | 363 | Action::PatchInlineComment { ref file, line, .. } => { |
| 362 | format!("patch: inline comment on {}:{}", file, line) | 364 | format!("patch: inline comment on {}:{}", file, line) |
| 363 | } | 365 | } |
| 364 | Action::PatchCommentResolve { ref comment, revision } => match revision { | 366 | Action::PatchCommentResolve { |
| 367 | ref comment, | ||
| 368 | revision, | ||
| 369 | } => match revision { | ||
| 365 | Some(n) => format!("patch: resolve comment {:.8} at r{}", comment, n), | 370 | Some(n) => format!("patch: resolve comment {:.8} at r{}", comment, n), |
| 366 | None => format!("patch: resolve comment {:.8}", comment), | 371 | None => format!("patch: resolve comment {:.8}", comment), |
| 367 | }, | 372 | }, |
src/editor.rs
| Old | New | ||
|---|---|---|---|
| @@ -319,7 +319,10 @@ mod tests { | |||
| 319 | let out = compose_with("", &script).unwrap(); | 319 | let out = compose_with("", &script).unwrap(); |
| 320 | assert_eq!(out.trim_end_matches('\n'), content.trim_end_matches('\n')); | 320 | assert_eq!(out.trim_end_matches('\n'), content.trim_end_matches('\n')); |
| 321 | assert!(out.starts_with("# Heading"), "no stripping of leading #"); | 321 | assert!(out.starts_with("# Heading"), "no stripping of leading #"); |
| 322 | assert!(out.contains('\t') && out.contains('\r'), "no whitespace tidying"); | 322 | assert!( |
| 323 | out.contains('\t') && out.contains('\r'), | ||
| 324 | "no whitespace tidying" | ||
| 325 | ); | ||
| 323 | assert!(out.contains('\u{301}'), "non-ASCII preserved"); | 326 | assert!(out.contains('\u{301}'), "non-ASCII preserved"); |
| 324 | } | 327 | } |
| 325 | 328 | ||
| @@ -335,9 +338,8 @@ mod tests { | |||
| 335 | /// that from silently returning the unedited seed. | 338 | /// that from silently returning the unedited seed. |
| 336 | #[test] | 339 | #[test] |
| 337 | fn test_compose_survives_an_editor_that_saves_by_rename() { | 340 | fn test_compose_survives_an_editor_that_saves_by_rename() { |
| 338 | let script = fake_editor( | 341 | let script = |
| 339 | "#!/bin/sh\nprintf 'renamed in\\n' > \"$1.new\"\nmv \"$1.new\" \"$1\"\n", | 342 | fake_editor("#!/bin/sh\nprintf 'renamed in\\n' > \"$1.new\"\nmv \"$1.new\" \"$1\"\n"); |
| 340 | ); | ||
| 341 | let out = compose_with("seed", &script).unwrap(); | 343 | let out = compose_with("seed", &script).unwrap(); |
| 342 | assert_eq!(out, "renamed in\n"); | 344 | assert_eq!(out, "renamed in\n"); |
| 343 | } | 345 | } |
src/error.rs
| Old | New | ||
|---|---|---|---|
| @@ -20,7 +20,10 @@ pub enum Error { | |||
| 20 | #[error("verification error: {0}")] | 20 | #[error("verification error: {0}")] |
| 21 | Verification(String), | 21 | Verification(String), |
| 22 | 22 | ||
| 23 | #[error("no signing key found — run `{} init-key` to generate one", crate::BINARY_NAME)] | 23 | #[error( |
| 24 | "no signing key found — run `{} init-key` to generate one", | ||
| 25 | crate::BINARY_NAME | ||
| 26 | )] | ||
| 24 | KeyNotFound, | 27 | KeyNotFound, |
| 25 | 28 | ||
| 26 | #[error("untrusted key: {0}")] | 29 | #[error("untrusted key: {0}")] |
src/event.rs
| Old | New | ||
|---|---|---|---|
| @@ -49,9 +49,7 @@ pub enum Action { | |||
| 49 | #[serde(rename = "issue.reopen")] | 49 | #[serde(rename = "issue.reopen")] |
| 50 | IssueReopen, | 50 | IssueReopen, |
| 51 | #[serde(rename = "issue.commit_link")] | 51 | #[serde(rename = "issue.commit_link")] |
| 52 | IssueCommitLink { | 52 | IssueCommitLink { commit: String }, |
| 53 | commit: String, | ||
| 54 | }, | ||
| 55 | /// A patch, and the commit and tree revision 1 stands at. | 53 | /// A patch, and the commit and tree revision 1 stands at. |
| 56 | /// | 54 | /// |
| 57 | /// The variant name `PatchCreate` and the `head_commit` spelling of the | 55 | /// The variant name `PatchCreate` and the `head_commit` spelling of the |
src/hooks.rs
| Old | New | ||
|---|---|---|---|
| @@ -89,7 +89,10 @@ pub fn hook_script(exe: &Path) -> String { | |||
| 89 | 89 | ||
| 90 | /// The line someone with their own `commit-msg` hook adds to it by hand. | 90 | /// The line someone with their own `commit-msg` hook adds to it by hand. |
| 91 | pub fn shim_line() -> String { | 91 | pub fn shim_line() -> String { |
| 92 | format!("git-collab {} \"$1\" >/dev/null 2>&1 || true", SHIM_SUBCOMMAND) | 92 | format!( |
| 93 | "git-collab {} \"$1\" >/dev/null 2>&1 || true", | ||
| 94 | SHIM_SUBCOMMAND | ||
| 95 | ) | ||
| 93 | } | 96 | } |
| 94 | 97 | ||
| 95 | // --------------------------------------------------------------------------- | 98 | // --------------------------------------------------------------------------- |
| @@ -321,7 +324,10 @@ pub fn report_install(outcome: &InstallOutcome) { | |||
| 321 | #[derive(Debug, Clone)] | 324 | #[derive(Debug, Clone)] |
| 322 | pub enum Target { | 325 | pub enum Target { |
| 323 | /// Exactly one open patch records this branch: the only case that stamps. | 326 | /// Exactly one open patch records this branch: the only case that stamps. |
| 324 | One { branch: String, patch: Box<PatchState> }, | 327 | One { |
| 328 | branch: String, | ||
| 329 | patch: Box<PatchState>, | ||
| 330 | }, | ||
| 325 | /// No open patch records this branch. | 331 | /// No open patch records this branch. |
| 326 | NoPatch { branch: String }, | 332 | NoPatch { branch: String }, |
| 327 | /// Several do. Ambiguous, so nothing is stamped — see [`target`]. | 333 | /// Several do. Ambiguous, so nothing is stamped — see [`target`]. |
| @@ -411,10 +417,7 @@ pub fn run_commit_msg(repo: &Repository, file: &Path) { | |||
| 411 | /// author's nor ours; a rename either happened or did not. | 417 | /// author's nor ours; a rename either happened or did not. |
| 412 | fn replace_atomically(file: &Path, contents: &str) -> std::io::Result<()> { | 418 | fn replace_atomically(file: &Path, contents: &str) -> std::io::Result<()> { |
| 413 | let dir = file.parent().unwrap_or_else(|| Path::new(".")); | 419 | let dir = file.parent().unwrap_or_else(|| Path::new(".")); |
| 414 | let tmp = dir.join(format!( | 420 | let tmp = dir.join(format!(".git-collab-commit-msg.{}", std::process::id())); |
| 415 | ".git-collab-commit-msg.{}", | ||
| 416 | std::process::id() | ||
| 417 | )); | ||
| 418 | std::fs::write(&tmp, contents)?; | 421 | std::fs::write(&tmp, contents)?; |
| 419 | if let Err(e) = std::fs::rename(&tmp, file) { | 422 | if let Err(e) = std::fs::rename(&tmp, file) { |
| 420 | let _ = std::fs::remove_file(&tmp); | 423 | let _ = std::fs::remove_file(&tmp); |
| @@ -595,7 +598,10 @@ pub fn status(repo: &Repository) -> Result<(), Error> { | |||
| 595 | println!("HEAD is not on a branch, so nothing would be stamped") | 598 | println!("HEAD is not on a branch, so nothing would be stamped") |
| 596 | } | 599 | } |
| 597 | Target::Unreadable(e) => { | 600 | Target::Unreadable(e) => { |
| 598 | println!("Patches could not be read ({}), so nothing would be stamped", e) | 601 | println!( |
| 602 | "Patches could not be read ({}), so nothing would be stamped", | ||
| 603 | e | ||
| 604 | ) | ||
| 599 | } | 605 | } |
| 600 | } | 606 | } |
| 601 | Ok(()) | 607 | Ok(()) |
| @@ -678,7 +684,8 @@ mod tests { | |||
| 678 | #[test] | 684 | #[test] |
| 679 | fn a_patch_trailer_below_the_scissors_does_not_count_as_present() { | 685 | fn a_patch_trailer_below_the_scissors_does_not_count_as_present() { |
| 680 | // It is part of a diff, not of the message, and git throws it away. | 686 | // It is part of a diff, not of the message, and git throws it away. |
| 681 | let input = "subject\n# ------------------------ >8 ------------------------\n+Patch: notmine\n"; | 687 | let input = |
| 688 | "subject\n# ------------------------ >8 ------------------------\n+Patch: notmine\n"; | ||
| 682 | assert!(stamp(input, '#', "abc123").is_some()); | 689 | assert!(stamp(input, '#', "abc123").is_some()); |
| 683 | } | 690 | } |
| 684 | 691 | ||
src/issue.rs
| Old | New | ||
|---|---|---|---|
| @@ -73,7 +73,15 @@ pub fn list_to_writer( | |||
| 73 | labels: &[String], | 73 | labels: &[String], |
| 74 | writer: &mut dyn std::io::Write, | 74 | writer: &mut dyn std::io::Write, |
| 75 | ) -> Result<(), crate::error::Error> { | 75 | ) -> Result<(), crate::error::Error> { |
| 76 | let entries = list(repo, show_closed, show_archived, limit, offset, sort, labels)?; | 76 | let entries = list( |
| 77 | repo, | ||
| 78 | show_closed, | ||
| 79 | show_archived, | ||
| 80 | limit, | ||
| 81 | offset, | ||
| 82 | sort, | ||
| 83 | labels, | ||
| 84 | )?; | ||
| 77 | if entries.is_empty() { | 85 | if entries.is_empty() { |
| 78 | writeln!(writer, "No issues found.").ok(); | 86 | writeln!(writer, "No issues found.").ok(); |
| 79 | return Ok(()); | 87 | return Ok(()); |
src/lib.rs
| Old | New | ||
|---|---|---|---|
| @@ -328,7 +328,11 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 328 | if !i.linked_commits.is_empty() { | 328 | if !i.linked_commits.is_empty() { |
| 329 | println!("\n--- Linked Commits ---"); | 329 | println!("\n--- Linked Commits ---"); |
| 330 | for lc in &i.linked_commits { | 330 | for lc in &i.linked_commits { |
| 331 | let short_sha = if lc.commit.len() >= 7 { &lc.commit[..7] } else { &lc.commit }; | 331 | let short_sha = if lc.commit.len() >= 7 { |
| 332 | &lc.commit[..7] | ||
| 333 | } else { | ||
| 334 | &lc.commit | ||
| 335 | }; | ||
| 332 | let (subject, commit_author) = match git2::Oid::from_str(&lc.commit) | 336 | let (subject, commit_author) = match git2::Oid::from_str(&lc.commit) |
| 333 | .ok() | 337 | .ok() |
| 334 | .and_then(|oid| repo.find_commit(oid).ok()) | 338 | .and_then(|oid| repo.find_commit(oid).ok()) |
| @@ -338,11 +342,8 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 338 | .summary() | 342 | .summary() |
| 339 | .map(|s| truncate_summary(s, 60)) | 343 | .map(|s| truncate_summary(s, 60)) |
| 340 | .unwrap_or_default(); | 344 | .unwrap_or_default(); |
| 341 | let author = commit | 345 | let author = |
| 342 | .author() | 346 | commit.author().name().unwrap_or("unknown").to_string(); |
| 343 | .name() | ||
| 344 | .unwrap_or("unknown") | ||
| 345 | .to_string(); | ||
| 346 | (Some(subject), Some(author)) | 347 | (Some(subject), Some(author)) |
| 347 | } | 348 | } |
| 348 | None => (None, None), | 349 | None => (None, None), |
| @@ -361,10 +362,7 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 361 | _ => { | 362 | _ => { |
| 362 | println!( | 363 | println!( |
| 363 | "· linked {} (commit {} not in local repo) (linked by {}, {})", | 364 | "· linked {} (commit {} not in local repo) (linked by {}, {})", |
| 364 | short_sha, | 365 | short_sha, short_sha, lc.event_author.name, lc.event_timestamp, |
| 365 | short_sha, | ||
| 366 | lc.event_author.name, | ||
| 367 | lc.event_timestamp, | ||
| 368 | ); | 366 | ); |
| 369 | } | 367 | } |
| 370 | } | 368 | } |
| @@ -834,10 +832,8 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 834 | // same rule inline comments already follow: an absent | 832 | // same rule inline comments already follow: an absent |
| 835 | // anchor is shown as absent, never as `(r?)` or as a | 833 | // anchor is shown as absent, never as `(r?)` or as a |
| 836 | // revision it was not cast against. | 834 | // revision it was not cast against. |
| 837 | let rev_label = r | 835 | let rev_label = |
| 838 | .revision | 836 | r.revision.map(|n| format!(" (r{})", n)).unwrap_or_default(); |
| 839 | .map(|n| format!(" (r{})", n)) | ||
| 840 | .unwrap_or_default(); | ||
| 841 | println!( | 837 | println!( |
| 842 | "\n{} ({}) - {}{}{} [{:.8}]:\n{}", | 838 | "\n{} ({}) - {}{}{} [{:.8}]:\n{}", |
| 843 | r.author.name, | 839 | r.author.name, |
| @@ -886,7 +882,11 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 886 | // "blocking" would relabel every comment ever written | 882 | // "blocking" would relabel every comment ever written |
| 887 | // as a demand, which is the reading the flag exists to | 883 | // as a demand, which is the reading the flag exists to |
| 888 | // stop being the only one available. | 884 | // stop being the only one available. |
| 889 | let blocking = if c.non_blocking { " [non-blocking]" } else { "" }; | 885 | let blocking = if c.non_blocking { |
| 886 | " [non-blocking]" | ||
| 887 | } else { | ||
| 888 | "" | ||
| 889 | }; | ||
| 890 | println!( | 890 | println!( |
| 891 | "\n{} on {}:{} ({}{}){}{} [{:.8}]:\n {}", | 891 | "\n{} on {}:{} ({}{}){}{} [{:.8}]:\n {}", |
| 892 | c.author.name, | 892 | c.author.name, |
| @@ -918,7 +918,11 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 918 | let stale = match (r.revision, latest_revision) { | 918 | let stale = match (r.revision, latest_revision) { |
| 919 | (Some(res), Some(head)) if res < head => { | 919 | (Some(res), Some(head)) if res < head => { |
| 920 | format!(" — {} revision{} landed since", head - res, { | 920 | format!(" — {} revision{} landed since", head - res, { |
| 921 | if head - res == 1 { "" } else { "s" } | 921 | if head - res == 1 { |
| 922 | "" | ||
| 923 | } else { | ||
| 924 | "s" | ||
| 925 | } | ||
| 922 | }) | 926 | }) |
| 923 | } | 927 | } |
| 924 | _ => String::new(), | 928 | _ => String::new(), |
| @@ -996,7 +1000,8 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 996 | stat, | 1000 | stat, |
| 997 | paths, | 1001 | paths, |
| 998 | }; | 1002 | }; |
| 999 | let diff = patch::diff(repo, &id, revision, between_pair, answers.as_deref(), &opts)?; | 1003 | let diff = |
| 1004 | patch::diff(repo, &id, revision, between_pair, answers.as_deref(), &opts)?; | ||
| 1000 | if diff.is_empty() { | 1005 | if diff.is_empty() { |
| 1001 | // An empty diff means two different things, and saying | 1006 | // An empty diff means two different things, and saying |
| 1002 | // "commits may be identical" for both is how a `--path` | 1007 | // "commits may be identical" for both is how a `--path` |
| @@ -1209,11 +1214,7 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 1209 | || "Revision description updated.".to_string(), | 1214 | || "Revision description updated.".to_string(), |
| 1210 | ) | 1215 | ) |
| 1211 | } | 1216 | } |
| 1212 | PatchCmd::Log { | 1217 | PatchCmd::Log { id, timeline, json } => { |
| 1213 | id, | ||
| 1214 | timeline, | ||
| 1215 | json, | ||
| 1216 | } => { | ||
| 1217 | if timeline { | 1218 | if timeline { |
| 1218 | let (_, entries) = timeline::build(repo, &id)?; | 1219 | let (_, entries) = timeline::build(repo, &id)?; |
| 1219 | if json { | 1220 | if json { |
| @@ -1270,8 +1271,7 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { | |||
| 1270 | } => { | 1271 | } => { |
| 1271 | let merged = patch::merge(repo, &id, commit.as_deref(), !no_close)?; | 1272 | let merged = patch::merge(repo, &id, commit.as_deref(), !no_close)?; |
| 1272 | let closed = merged.close == merge_scan::CloseOutcome::Closed; | 1273 | let closed = merged.close == merge_scan::CloseOutcome::Closed; |
| 1273 | let already = | 1274 | let already = matches!(merged.outcome, merge_scan::MergeOutcome::AlreadyMerged); |
| 1274 | matches!(merged.outcome, merge_scan::MergeOutcome::AlreadyMerged); | ||
| 1275 | report( | 1275 | report( |
| 1276 | json, | 1276 | json, |
| 1277 | || { | 1277 | || { |
| @@ -1632,7 +1632,11 @@ fn search(repo: &Repository, query: &str) -> Result<(), error::Error> { | |||
| 1632 | if issue.body.to_lowercase().contains(&q) { | 1632 | if issue.body.to_lowercase().contains(&q) { |
| 1633 | matches.push("body"); | 1633 | matches.push("body"); |
| 1634 | } | 1634 | } |
| 1635 | if issue.comments.iter().any(|c| c.body.to_lowercase().contains(&q)) { | 1635 | if issue |
| 1636 | .comments | ||
| 1637 | .iter() | ||
| 1638 | .any(|c| c.body.to_lowercase().contains(&q)) | ||
| 1639 | { | ||
| 1636 | matches.push("comment"); | 1640 | matches.push("comment"); |
| 1637 | } | 1641 | } |
| 1638 | if !matches.is_empty() { | 1642 | if !matches.is_empty() { |
| @@ -1656,13 +1660,25 @@ fn search(repo: &Repository, query: &str) -> Result<(), error::Error> { | |||
| 1656 | if patch.body.to_lowercase().contains(&q) { | 1660 | if patch.body.to_lowercase().contains(&q) { |
| 1657 | matches.push("body"); | 1661 | matches.push("body"); |
| 1658 | } | 1662 | } |
| 1659 | if patch.comments.iter().any(|c| c.body.to_lowercase().contains(&q)) { | 1663 | if patch |
| 1664 | .comments | ||
| 1665 | .iter() | ||
| 1666 | .any(|c| c.body.to_lowercase().contains(&q)) | ||
| 1667 | { | ||
| 1660 | matches.push("comment"); | 1668 | matches.push("comment"); |
| 1661 | } | 1669 | } |
| 1662 | if patch.reviews.iter().any(|r| r.body.to_lowercase().contains(&q)) { | 1670 | if patch |
| 1671 | .reviews | ||
| 1672 | .iter() | ||
| 1673 | .any(|r| r.body.to_lowercase().contains(&q)) | ||
| 1674 | { | ||
| 1663 | matches.push("review"); | 1675 | matches.push("review"); |
| 1664 | } | 1676 | } |
| 1665 | if patch.inline_comments.iter().any(|ic| ic.body.to_lowercase().contains(&q)) { | 1677 | if patch |
| 1678 | .inline_comments | ||
| 1679 | .iter() | ||
| 1680 | .any(|ic| ic.body.to_lowercase().contains(&q)) | ||
| 1681 | { | ||
| 1666 | matches.push("inline comment"); | 1682 | matches.push("inline comment"); |
| 1667 | } | 1683 | } |
| 1668 | if !matches.is_empty() { | 1684 | if !matches.is_empty() { |
src/merge_scan.rs
| Old | New | ||
|---|---|---|---|
| @@ -412,7 +412,10 @@ fn collect_trailers( | |||
| 412 | let message = commit.message().unwrap_or(""); | 412 | let message = commit.message().unwrap_or(""); |
| 413 | for prefix in trailer::parse_trailers(message, trailer::PATCH_TOKEN) { | 413 | for prefix in trailer::parse_trailers(message, trailer::PATCH_TOKEN) { |
| 414 | if seen_prefix.insert(prefix.clone()) { | 414 | if seen_prefix.insert(prefix.clone()) { |
| 415 | found.push(FoundTrailer { prefix, commit: oid }); | 415 | found.push(FoundTrailer { |
| 416 | prefix, | ||
| 417 | commit: oid, | ||
| 418 | }); | ||
| 416 | } | 419 | } |
| 417 | } | 420 | } |
| 418 | } | 421 | } |
src/patch.rs
| Old | New | ||
|---|---|---|---|
| @@ -152,9 +152,7 @@ fn count_unread(repo: &git2::Repository, id: &str) -> Option<usize> { | |||
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | let mut revwalk = repo.revwalk().ok()?; | 154 | let mut revwalk = repo.revwalk().ok()?; |
| 155 | revwalk | 155 | revwalk.set_sorting(git2::Sort::TOPOLOGICAL).ok()?; |
| 156 | .set_sorting(git2::Sort::TOPOLOGICAL) | ||
| 157 | .ok()?; | ||
| 158 | revwalk.push(tip).ok()?; | 156 | revwalk.push(tip).ok()?; |
| 159 | revwalk.hide(seen_oid).ok()?; | 157 | revwalk.hide(seen_oid).ok()?; |
| 160 | Some(revwalk.count()) | 158 | Some(revwalk.count()) |
| @@ -2040,14 +2038,8 @@ pub fn merge( | |||
| 2040 | 2038 | ||
| 2041 | let author = get_author(repo)?; | 2039 | let author = get_author(repo)?; |
| 2042 | let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; | 2040 | let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; |
| 2043 | let outcome = crate::merge_scan::record_merge( | 2041 | let outcome = |
| 2044 | repo, | 2042 | crate::merge_scan::record_merge(repo, &events_ref, &patch, commit_oid, &author, &sk)?; |
| 2045 | &events_ref, | ||
| 2046 | &patch, | ||
| 2047 | commit_oid, | ||
| 2048 | &author, | ||
| 2049 | &sk, | ||
| 2050 | )?; | ||
| 2051 | // Reconcile the `fixes` issue whether or not the merge was freshly | 2043 | // Reconcile the `fixes` issue whether or not the merge was freshly |
| 2052 | // emitted, so a close that failed the first time is retried rather than | 2044 | // emitted, so a close that failed the first time is retried rather than |
| 2053 | // stranded. Idempotent: an already-closed issue is left alone. | 2045 | // stranded. Idempotent: an already-closed issue is left alone. |
src/release.rs
| Old | New | ||
|---|---|---|---|
| @@ -382,8 +382,15 @@ mod tests { | |||
| 382 | let dir_that_looks_like_a_file = tmp.path().join("payload.tar.gz"); | 382 | let dir_that_looks_like_a_file = tmp.path().join("payload.tar.gz"); |
| 383 | std::fs::create_dir(&dir_that_looks_like_a_file).unwrap(); | 383 | std::fs::create_dir(&dir_that_looks_like_a_file).unwrap(); |
| 384 | 384 | ||
| 385 | let err = | 385 | let err = publish( |
| 386 | publish(&repo, "origin", "v1", &[dir_that_looks_like_a_file], false, false).unwrap_err(); | 386 | &repo, |
| 387 | "origin", | ||
| 388 | "v1", | ||
| 389 | &[dir_that_looks_like_a_file], | ||
| 390 | false, | ||
| 391 | false, | ||
| 392 | ) | ||
| 393 | .unwrap_err(); | ||
| 387 | assert!( | 394 | assert!( |
| 388 | err.to_string().contains("not a regular file"), | 395 | err.to_string().contains("not a regular file"), |
| 389 | "got: {}", | 396 | "got: {}", |
src/server/http/mod.rs
| Old | New | ||
|---|---|---|---|
| @@ -35,7 +35,10 @@ pub fn router(state: AppState) -> Router { | |||
| 35 | .route("/{repo_name}/tree", axum::routing::get(repo::tree_root)) | 35 | .route("/{repo_name}/tree", axum::routing::get(repo::tree_root)) |
| 36 | .route("/{repo_name}/tree/{*rest}", axum::routing::get(repo::tree)) | 36 | .route("/{repo_name}/tree/{*rest}", axum::routing::get(repo::tree)) |
| 37 | .route("/{repo_name}/blob/{*rest}", axum::routing::get(repo::blob)) | 37 | .route("/{repo_name}/blob/{*rest}", axum::routing::get(repo::blob)) |
| 38 | .route("/{repo_name}/history/{*rest}", axum::routing::get(repo::history)) | 38 | .route( |
| 39 | "/{repo_name}/history/{*rest}", | ||
| 40 | axum::routing::get(repo::history), | ||
| 41 | ) | ||
| 39 | .route("/{repo_name}/diff/{oid}", axum::routing::get(repo::diff)) | 42 | .route("/{repo_name}/diff/{oid}", axum::routing::get(repo::diff)) |
| 40 | .route("/{repo_name}/patches", axum::routing::get(repo::patches)) | 43 | .route("/{repo_name}/patches", axum::routing::get(repo::patches)) |
| 41 | .route( | 44 | .route( |
src/server/http/repo/issues.rs
| Old | New | ||
|---|---|---|---|
| @@ -197,7 +197,11 @@ pub async fn issue_detail( | |||
| 197 | labels: is.labels.join(", "), | 197 | labels: is.labels.join(", "), |
| 198 | assignees: is.assignees.join(", "), | 198 | assignees: is.assignees.join(", "), |
| 199 | close_reason: is.close_reason, | 199 | close_reason: is.close_reason, |
| 200 | comments: is.comments.into_iter().map(CommentView::from_state).collect(), | 200 | comments: is |
| 201 | .comments | ||
| 202 | .into_iter() | ||
| 203 | .map(CommentView::from_state) | ||
| 204 | .collect(), | ||
| 201 | }; | 205 | }; |
| 202 | 206 | ||
| 203 | IssueDetailTemplate { | 207 | IssueDetailTemplate { |
src/server/http/repo/mod.rs
| Old | New | ||
|---|---|---|---|
| @@ -1,9 +1,9 @@ | |||
| 1 | mod commits; | 1 | mod commits; |
| 2 | mod diff; | 2 | mod diff; |
| 3 | mod patch_diff; | ||
| 4 | mod history; | 3 | mod history; |
| 5 | mod issues; | 4 | mod issues; |
| 6 | mod overview; | 5 | mod overview; |
| 6 | mod patch_diff; | ||
| 7 | mod patches; | 7 | mod patches; |
| 8 | mod readme; | 8 | mod readme; |
| 9 | mod releases; | 9 | mod releases; |
| @@ -11,10 +11,10 @@ mod tree; | |||
| 11 | 11 | ||
| 12 | pub use commits::{commits, commits_ref}; | 12 | pub use commits::{commits, commits_ref}; |
| 13 | pub use diff::diff; | 13 | pub use diff::diff; |
| 14 | pub use patch_diff::patch_diff; | ||
| 15 | pub use history::history; | 14 | pub use history::history; |
| 16 | pub use issues::{issue_detail, issues}; | 15 | pub use issues::{issue_detail, issues}; |
| 17 | pub use overview::overview; | 16 | pub use overview::overview; |
| 17 | pub use patch_diff::patch_diff; | ||
| 18 | pub use patches::{patch_detail, patches}; | 18 | pub use patches::{patch_detail, patches}; |
| 19 | pub use releases::{release_download, releases}; | 19 | pub use releases::{release_download, releases}; |
| 20 | pub use tree::{blob, tree, tree_root}; | 20 | pub use tree::{blob, tree, tree_root}; |
src/server/http/repo/overview.rs
| Old | New | ||
|---|---|---|---|
| @@ -4,8 +4,9 @@ use axum::extract::{Path, State}; | |||
| 4 | use axum::response::{IntoResponse, Response}; | 4 | use axum::response::{IntoResponse, Response}; |
| 5 | 5 | ||
| 6 | use super::{ | 6 | use super::{ |
| 7 | AppState, OverviewCommit, collab_counts, head_branch_name, open_repo, recent_commits, | 7 | collab_counts, head_branch_name, open_repo, |
| 8 | readme::{self, RenderedReadme}, | 8 | readme::{self, RenderedReadme}, |
| 9 | recent_commits, AppState, OverviewCommit, | ||
| 9 | }; | 10 | }; |
| 10 | 11 | ||
| 11 | #[derive(Debug)] | 12 | #[derive(Debug)] |
src/server/http/repo/patches.rs
| Old | New | ||
|---|---|---|---|
| @@ -362,7 +362,11 @@ pub async fn patch_detail( | |||
| 362 | resolved_at: ic.resolved.as_ref().and_then(|r| r.revision), | 362 | resolved_at: ic.resolved.as_ref().and_then(|r| r.revision), |
| 363 | }) | 363 | }) |
| 364 | .collect(), | 364 | .collect(), |
| 365 | comments: ps.comments.into_iter().map(CommentView::from_state).collect(), | 365 | comments: ps |
| 366 | .comments | ||
| 367 | .into_iter() | ||
| 368 | .map(CommentView::from_state) | ||
| 369 | .collect(), | ||
| 366 | }; | 370 | }; |
| 367 | 371 | ||
| 368 | PatchDetailTemplate { | 372 | PatchDetailTemplate { |
src/server/http/repo/readme.rs
| Old | New | ||
|---|---|---|---|
| @@ -18,11 +18,7 @@ const MAX_HTML_BYTES: usize = 2 * 1024 * 1024; | |||
| 18 | /// Returns `None` when no README is present, the blob is binary or invalid | 18 | /// Returns `None` when no README is present, the blob is binary or invalid |
| 19 | /// UTF-8, or any unexpected git2 error occurs. The README must never break | 19 | /// UTF-8, or any unexpected git2 error occurs. The README must never break |
| 20 | /// the overview page — failures degrade silently to "no README". | 20 | /// the overview page — failures degrade silently to "no README". |
| 21 | pub fn load_readme( | 21 | pub fn load_readme(repo: &Repository, repo_name: &str, branch: &str) -> Option<RenderedReadme> { |
| 22 | repo: &Repository, | ||
| 23 | repo_name: &str, | ||
| 24 | branch: &str, | ||
| 25 | ) -> Option<RenderedReadme> { | ||
| 26 | // Resolve the branch ref directly so this works on bare repos where HEAD | 22 | // Resolve the branch ref directly so this works on bare repos where HEAD |
| 27 | // may point to an unborn branch (e.g. `master` when only `main` is pushed). | 23 | // may point to an unborn branch (e.g. `master` when only `main` is pushed). |
| 28 | let obj = repo | 24 | let obj = repo |
| @@ -135,7 +131,11 @@ fn find_readme_entry(tree: &git2::Tree) -> Option<ReadmeEntry> { | |||
| 135 | Some(v) => v, | 131 | Some(v) => v, |
| 136 | None => continue, | 132 | None => continue, |
| 137 | }; | 133 | }; |
| 138 | let candidate = ReadmeEntry { name: name.to_string(), oid: entry.id(), kind }; | 134 | let candidate = ReadmeEntry { |
| 135 | name: name.to_string(), | ||
| 136 | oid: entry.id(), | ||
| 137 | kind, | ||
| 138 | }; | ||
| 139 | match &best { | 139 | match &best { |
| 140 | None => best = Some((score, candidate)), | 140 | None => best = Some((score, candidate)), |
| 141 | Some((cur_score, _)) if score < *cur_score => best = Some((score, candidate)), | 141 | Some((cur_score, _)) if score < *cur_score => best = Some((score, candidate)), |
| @@ -146,7 +146,7 @@ fn find_readme_entry(tree: &git2::Tree) -> Option<ReadmeEntry> { | |||
| 146 | } | 146 | } |
| 147 | 147 | ||
| 148 | fn render_markdown(src: &str) -> String { | 148 | fn render_markdown(src: &str) -> String { |
| 149 | use pulldown_cmark::{Options, Parser, html}; | 149 | use pulldown_cmark::{html, Options, Parser}; |
| 150 | let mut opts = Options::empty(); | 150 | let mut opts = Options::empty(); |
| 151 | opts.insert(Options::ENABLE_TABLES); | 151 | opts.insert(Options::ENABLE_TABLES); |
| 152 | opts.insert(Options::ENABLE_STRIKETHROUGH); | 152 | opts.insert(Options::ENABLE_STRIKETHROUGH); |
| @@ -196,7 +196,9 @@ mod tests { | |||
| 196 | use tempfile::TempDir; | 196 | use tempfile::TempDir; |
| 197 | 197 | ||
| 198 | fn load(repo: &Repository) -> Option<RenderedReadme> { | 198 | fn load(repo: &Repository) -> Option<RenderedReadme> { |
| 199 | let branch = repo.head().ok() | 199 | let branch = repo |
| 200 | .head() | ||
| 201 | .ok() | ||
| 200 | .and_then(|h| h.shorthand().map(String::from)) | 202 | .and_then(|h| h.shorthand().map(String::from)) |
| 201 | .unwrap_or_else(|| "main".to_string()); | 203 | .unwrap_or_else(|| "main".to_string()); |
| 202 | load_readme(repo, "test-repo", &branch) | 204 | load_readme(repo, "test-repo", &branch) |
| @@ -220,7 +222,8 @@ mod tests { | |||
| 220 | builder.write().unwrap() | 222 | builder.write().unwrap() |
| 221 | }; | 223 | }; |
| 222 | let tree = repo.find_tree(tree_oid).unwrap(); | 224 | let tree = repo.find_tree(tree_oid).unwrap(); |
| 223 | repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]).unwrap(); | 225 | repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]) |
| 226 | .unwrap(); | ||
| 224 | } | 227 | } |
| 225 | 228 | ||
| 226 | (repo, tmp) | 229 | (repo, tmp) |
| @@ -247,10 +250,7 @@ mod tests { | |||
| 247 | 250 | ||
| 248 | #[test] | 251 | #[test] |
| 249 | fn md_wins_over_txt() { | 252 | fn md_wins_over_txt() { |
| 250 | let (repo, _tmp) = repo_with_files(&[ | 253 | let (repo, _tmp) = repo_with_files(&[("README.md", b"# md\n"), ("README.txt", b"plain")]); |
| 251 | ("README.md", b"# md\n"), | ||
| 252 | ("README.txt", b"plain"), | ||
| 253 | ]); | ||
| 254 | let r = load(&repo).unwrap(); | 254 | let r = load(&repo).unwrap(); |
| 255 | assert!(r.html.contains("<h1>md</h1>")); | 255 | assert!(r.html.contains("<h1>md</h1>")); |
| 256 | assert!(!r.html.contains("plain")); | 256 | assert!(!r.html.contains("plain")); |
| @@ -258,10 +258,8 @@ mod tests { | |||
| 258 | 258 | ||
| 259 | #[test] | 259 | #[test] |
| 260 | fn readme_wins_over_txt() { | 260 | fn readme_wins_over_txt() { |
| 261 | let (repo, _tmp) = repo_with_files(&[ | 261 | let (repo, _tmp) = |
| 262 | ("README", b"plain readme"), | 262 | repo_with_files(&[("README", b"plain readme"), ("README.txt", b"plain txt")]); |
| 263 | ("README.txt", b"plain txt"), | ||
| 264 | ]); | ||
| 265 | let r = load(&repo).unwrap(); | 263 | let r = load(&repo).unwrap(); |
| 266 | assert!(r.html.contains("plain readme")); | 264 | assert!(r.html.contains("plain readme")); |
| 267 | assert!(!r.html.contains("plain txt")); | 265 | assert!(!r.html.contains("plain txt")); |
| @@ -269,10 +267,7 @@ mod tests { | |||
| 269 | 267 | ||
| 270 | #[test] | 268 | #[test] |
| 271 | fn mixed_case_md_still_wins_over_lowercase_txt() { | 269 | fn mixed_case_md_still_wins_over_lowercase_txt() { |
| 272 | let (repo, _tmp) = repo_with_files(&[ | 270 | let (repo, _tmp) = repo_with_files(&[("README.md", b"# md\n"), ("readme.txt", b"plain")]); |
| 273 | ("README.md", b"# md\n"), | ||
| 274 | ("readme.txt", b"plain"), | ||
| 275 | ]); | ||
| 276 | assert!(load(&repo).unwrap().html.contains("<h1>md</h1>")); | 271 | assert!(load(&repo).unwrap().html.contains("<h1>md</h1>")); |
| 277 | } | 272 | } |
| 278 | 273 | ||
| @@ -302,7 +297,8 @@ mod tests { | |||
| 302 | root.insert("README", root_blob, 0o100644).unwrap(); | 297 | root.insert("README", root_blob, 0o100644).unwrap(); |
| 303 | let root_oid = root.write().unwrap(); | 298 | let root_oid = root.write().unwrap(); |
| 304 | let tree = repo.find_tree(root_oid).unwrap(); | 299 | let tree = repo.find_tree(root_oid).unwrap(); |
| 305 | repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]).unwrap(); | 300 | repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]) |
| 301 | .unwrap(); | ||
| 306 | 302 | ||
| 307 | let r = load(&repo).expect("root README must be found"); | 303 | let r = load(&repo).expect("root README must be found"); |
| 308 | assert!(r.html.contains("actual root readme")); | 304 | assert!(r.html.contains("actual root readme")); |
| @@ -311,9 +307,7 @@ mod tests { | |||
| 311 | 307 | ||
| 312 | #[test] | 308 | #[test] |
| 313 | fn plain_readme_escapes_script_tag() { | 309 | fn plain_readme_escapes_script_tag() { |
| 314 | let (repo, _tmp) = repo_with_files(&[ | 310 | let (repo, _tmp) = repo_with_files(&[("README", b"<script>alert(1)</script>\nhello")]); |
| 315 | ("README", b"<script>alert(1)</script>\nhello"), | ||
| 316 | ]); | ||
| 317 | let r = load(&repo).unwrap(); | 311 | let r = load(&repo).unwrap(); |
| 318 | assert!(r.html.starts_with("<pre>")); | 312 | assert!(r.html.starts_with("<pre>")); |
| 319 | assert!(r.html.contains("<script>")); | 313 | assert!(r.html.contains("<script>")); |
| @@ -334,7 +328,8 @@ mod tests { | |||
| 334 | root.insert("README.md", target_blob, 0o120000).unwrap(); | 328 | root.insert("README.md", target_blob, 0o120000).unwrap(); |
| 335 | let root_oid = root.write().unwrap(); | 329 | let root_oid = root.write().unwrap(); |
| 336 | let tree = repo.find_tree(root_oid).unwrap(); | 330 | let tree = repo.find_tree(root_oid).unwrap(); |
| 337 | repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]).unwrap(); | 331 | repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]) |
| 332 | .unwrap(); | ||
| 338 | 333 | ||
| 339 | assert!(load(&repo).is_none()); | 334 | assert!(load(&repo).is_none()); |
| 340 | } | 335 | } |
| @@ -343,9 +338,7 @@ mod tests { | |||
| 343 | fn markdown_strips_script_tag() { | 338 | fn markdown_strips_script_tag() { |
| 344 | // Passes because pulldown-cmark does not emit raw HTML without | 339 | // Passes because pulldown-cmark does not emit raw HTML without |
| 345 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. | 340 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. |
| 346 | let (repo, _tmp) = repo_with_files(&[ | 341 | let (repo, _tmp) = repo_with_files(&[("README.md", b"# t\n\n<script>alert(1)</script>\n")]); |
| 347 | ("README.md", b"# t\n\n<script>alert(1)</script>\n"), | ||
| 348 | ]); | ||
| 349 | let html = load(&repo).unwrap().html; | 342 | let html = load(&repo).unwrap().html; |
| 350 | assert!(!html.contains("<script>"), "got: {}", html); | 343 | assert!(!html.contains("<script>"), "got: {}", html); |
| 351 | } | 344 | } |
| @@ -354,9 +347,7 @@ mod tests { | |||
| 354 | fn markdown_strips_javascript_href() { | 347 | fn markdown_strips_javascript_href() { |
| 355 | // Passes because pulldown-cmark does not emit raw HTML without | 348 | // Passes because pulldown-cmark does not emit raw HTML without |
| 356 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. | 349 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. |
| 357 | let (repo, _tmp) = repo_with_files(&[ | 350 | let (repo, _tmp) = repo_with_files(&[("README.md", b"[click](javascript:alert(1))\n")]); |
| 358 | ("README.md", b"[click](javascript:alert(1))\n"), | ||
| 359 | ]); | ||
| 360 | let html = load(&repo).unwrap().html; | 351 | let html = load(&repo).unwrap().html; |
| 361 | assert!(!html.contains("javascript:"), "got: {}", html); | 352 | assert!(!html.contains("javascript:"), "got: {}", html); |
| 362 | } | 353 | } |
| @@ -365,9 +356,10 @@ mod tests { | |||
| 365 | fn markdown_strips_onerror_attribute() { | 356 | fn markdown_strips_onerror_attribute() { |
| 366 | // Passes because pulldown-cmark does not emit raw HTML without | 357 | // Passes because pulldown-cmark does not emit raw HTML without |
| 367 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. | 358 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. |
| 368 | let (repo, _tmp) = repo_with_files(&[ | 359 | let (repo, _tmp) = repo_with_files(&[( |
| 369 | ("README.md", b"<img src=\"https://x/y.png\" onerror=\"alert(1)\">\n"), | 360 | "README.md", |
| 370 | ]); | 361 | b"<img src=\"https://x/y.png\" onerror=\"alert(1)\">\n", |
| 362 | )]); | ||
| 371 | let html = load(&repo).unwrap().html; | 363 | let html = load(&repo).unwrap().html; |
| 372 | assert!(!html.contains("onerror"), "got: {}", html); | 364 | assert!(!html.contains("onerror"), "got: {}", html); |
| 373 | } | 365 | } |
| @@ -376,18 +368,18 @@ mod tests { | |||
| 376 | fn markdown_strips_iframe() { | 368 | fn markdown_strips_iframe() { |
| 377 | // Passes because pulldown-cmark does not emit raw HTML without | 369 | // Passes because pulldown-cmark does not emit raw HTML without |
| 378 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. | 370 | // ENABLE_UNSAFE_HTML — defense-in-depth canary, not an ammonia test. |
| 379 | let (repo, _tmp) = repo_with_files(&[ | 371 | let (repo, _tmp) = repo_with_files(&[( |
| 380 | ("README.md", b"<iframe src=\"https://evil.example/\"></iframe>\n"), | 372 | "README.md", |
| 381 | ]); | 373 | b"<iframe src=\"https://evil.example/\"></iframe>\n", |
| 374 | )]); | ||
| 382 | let html = load(&repo).unwrap().html; | 375 | let html = load(&repo).unwrap().html; |
| 383 | assert!(!html.contains("<iframe"), "got: {}", html); | 376 | assert!(!html.contains("<iframe"), "got: {}", html); |
| 384 | } | 377 | } |
| 385 | 378 | ||
| 386 | #[test] | 379 | #[test] |
| 387 | fn markdown_strips_data_image_uri() { | 380 | fn markdown_strips_data_image_uri() { |
| 388 | let (repo, _tmp) = repo_with_files(&[ | 381 | let (repo, _tmp) = |
| 389 | ("README.md", b"<img src=\"data:image/png;base64,AAAA\">\n"), | 382 | repo_with_files(&[("README.md", b"<img src=\"data:image/png;base64,AAAA\">\n")]); |
| 390 | ]); | ||
| 391 | let html = load(&repo).unwrap().html; | 383 | let html = load(&repo).unwrap().html; |
| 392 | // Either the whole <img> is dropped or the src attr is gone. | 384 | // Either the whole <img> is dropped or the src attr is gone. |
| 393 | assert!(!html.contains("data:"), "got: {}", html); | 385 | assert!(!html.contains("data:"), "got: {}", html); |
| @@ -423,7 +415,12 @@ mod tests { | |||
| 423 | for _ in 0..100_000usize { | 415 | for _ in 0..100_000usize { |
| 424 | src.push_str("`a`\n\n"); | 416 | src.push_str("`a`\n\n"); |
| 425 | } | 417 | } |
| 426 | assert!(src.len() < MAX_BLOB_BYTES, "source {} >= blob cap {}", src.len(), MAX_BLOB_BYTES); | 418 | assert!( |
| 419 | src.len() < MAX_BLOB_BYTES, | ||
| 420 | "source {} >= blob cap {}", | ||
| 421 | src.len(), | ||
| 422 | MAX_BLOB_BYTES | ||
| 423 | ); | ||
| 427 | let (repo, _tmp) = repo_with_files(&[("README.md", src.as_bytes())]); | 424 | let (repo, _tmp) = repo_with_files(&[("README.md", src.as_bytes())]); |
| 428 | let r = load(&repo).unwrap(); | 425 | let r = load(&repo).unwrap(); |
| 429 | assert!(r.html.contains("too large"), "expected bomb to trip cap"); | 426 | assert!(r.html.contains("too large"), "expected bomb to trip cap"); |
| @@ -435,7 +432,10 @@ mod tests { | |||
| 435 | let bytes = vec![b'&'; 500 * 1024]; | 432 | let bytes = vec![b'&'; 500 * 1024]; |
| 436 | let (repo, _tmp) = repo_with_files(&[("README", &bytes)]); | 433 | let (repo, _tmp) = repo_with_files(&[("README", &bytes)]); |
| 437 | let r = load(&repo).unwrap(); | 434 | let r = load(&repo).unwrap(); |
| 438 | assert!(r.html.contains("too large"), "expected plain-text bomb to trip cap"); | 435 | assert!( |
| 436 | r.html.contains("too large"), | ||
| 437 | "expected plain-text bomb to trip cap" | ||
| 438 | ); | ||
| 439 | } | 439 | } |
| 440 | 440 | ||
| 441 | #[test] | 441 | #[test] |
src/server/ssh/auth.rs
| Old | New | ||
|---|---|---|---|
| @@ -20,7 +20,11 @@ pub fn parse_authorized_keys(content: &str) -> Vec<AuthorizedKey> { | |||
| 20 | let key_type = parts.next()?.to_string(); | 20 | let key_type = parts.next()?.to_string(); |
| 21 | let key_data = parts.next()?.to_string(); | 21 | let key_data = parts.next()?.to_string(); |
| 22 | let comment = parts.next().map(|s| s.to_string()); | 22 | let comment = parts.next().map(|s| s.to_string()); |
| 23 | Some(AuthorizedKey { key_type, key_data, comment }) | 23 | Some(AuthorizedKey { |
| 24 | key_type, | ||
| 25 | key_data, | ||
| 26 | comment, | ||
| 27 | }) | ||
| 24 | }) | 28 | }) |
| 25 | .collect() | 29 | .collect() |
| 26 | } | 30 | } |
| @@ -36,8 +40,8 @@ fn is_rsa_key_type(key_type: &str) -> bool { | |||
| 36 | 40 | ||
| 37 | pub fn is_authorized(keys: &[AuthorizedKey], key_type: &str, key_data: &str) -> bool { | 41 | pub fn is_authorized(keys: &[AuthorizedKey], key_type: &str, key_data: &str) -> bool { |
| 38 | keys.iter().any(|k| { | 42 | keys.iter().any(|k| { |
| 39 | let type_matches = k.key_type == key_type | 43 | let type_matches = |
| 40 | || (is_rsa_key_type(&k.key_type) && is_rsa_key_type(key_type)); | 44 | k.key_type == key_type || (is_rsa_key_type(&k.key_type) && is_rsa_key_type(key_type)); |
| 41 | type_matches && k.key_data == key_data | 45 | type_matches && k.key_data == key_data |
| 42 | }) | 46 | }) |
| 43 | } | 47 | } |
src/signing.rs
| Old | New | ||
|---|---|---|---|
| @@ -356,7 +356,10 @@ mod tests { | |||
| 356 | let keys: Vec<&String> = map.keys().collect(); | 356 | let keys: Vec<&String> = map.keys().collect(); |
| 357 | let mut sorted = keys.clone(); | 357 | let mut sorted = keys.clone(); |
| 358 | sorted.sort(); | 358 | sorted.sort(); |
| 359 | assert_eq!(keys, sorted, "top-level JSON keys must be alphabetically sorted"); | 359 | assert_eq!( |
| 360 | keys, sorted, | ||
| 361 | "top-level JSON keys must be alphabetically sorted" | ||
| 362 | ); | ||
| 360 | } else { | 363 | } else { |
| 361 | panic!("expected JSON object"); | 364 | panic!("expected JSON object"); |
| 362 | } | 365 | } |
src/state.rs
| Old | New | ||
|---|---|---|---|
| @@ -95,7 +95,13 @@ impl BodyOverrides { | |||
| 95 | /// Record a correction, keeping the one that wins on `(clock, oid)`. | 95 | /// Record a correction, keeping the one that wins on `(clock, oid)`. |
| 96 | /// `>=` matches the status fold: later clock wins, and on a tie the | 96 | /// `>=` matches the status fold: later clock wins, and on a tie the |
| 97 | /// lexicographically larger OID does. | 97 | /// lexicographically larger OID does. |
| 98 | fn record(&mut self, target: String, key: (u64, String), author: &Author, body: Option<String>) { | 98 | fn record( |
| 99 | &mut self, | ||
| 100 | target: String, | ||
| 101 | key: (u64, String), | ||
| 102 | author: &Author, | ||
| 103 | body: Option<String>, | ||
| 104 | ) { | ||
| 99 | let candidate = BodyOverride { | 105 | let candidate = BodyOverride { |
| 100 | key, | 106 | key, |
| 101 | author_email: author.email.clone(), | 107 | author_email: author.email.clone(), |
| @@ -1606,10 +1612,7 @@ impl PatchState { | |||
| 1606 | new_body.clone_into(&mut r.body); | 1612 | new_body.clone_into(&mut r.body); |
| 1607 | r.edited = true; | 1613 | r.edited = true; |
| 1608 | } | 1614 | } |
| 1609 | } else if let Some(rev) = s | 1615 | } else if let Some(rev) = s.revisions.iter_mut().find(|rev| rev.event_id == *target) |
| 1610 | .revisions | ||
| 1611 | .iter_mut() | ||
| 1612 | .find(|rev| rev.event_id == *target) | ||
| 1613 | { | 1616 | { |
| 1614 | // Same rule: the commit and tree a revision points at are | 1617 | // Same rule: the commit and tree a revision points at are |
| 1615 | // immutable, the description is not. | 1618 | // immutable, the description is not. |
| @@ -1756,7 +1759,10 @@ fn patch_refs_under( | |||
| 1756 | // A revision ref, not a patch. | 1759 | // A revision ref, not a patch. |
| 1757 | Some(_) => continue, | 1760 | Some(_) => continue, |
| 1758 | // The older layout: no suffix at all, the ref is the DAG. | 1761 | // The older layout: no suffix at all, the ref is the DAG. |
| 1759 | None => ref_name.strip_prefix(prefix).unwrap_or_default().to_string(), | 1762 | None => ref_name |
| 1763 | .strip_prefix(prefix) | ||
| 1764 | .unwrap_or_default() | ||
| 1765 | .to_string(), | ||
| 1760 | }; | 1766 | }; |
| 1761 | if id.is_empty() { | 1767 | if id.is_empty() { |
| 1762 | continue; | 1768 | continue; |
| @@ -1920,7 +1926,11 @@ fn all_ids(repo: &Repository, kind: &str) -> Result<Vec<String>, crate::error::E | |||
| 1920 | .into_iter() | 1926 | .into_iter() |
| 1921 | .map(|(_, id)| id) | 1927 | .map(|(_, id)| id) |
| 1922 | .collect(); | 1928 | .collect(); |
| 1923 | ids.extend(collab_archive_refs(repo, kind)?.into_iter().map(|(_, id)| id)); | 1929 | ids.extend( |
| 1930 | collab_archive_refs(repo, kind)? | ||
| 1931 | .into_iter() | ||
| 1932 | .map(|(_, id)| id), | ||
| 1933 | ); | ||
| 1924 | ids.sort_unstable(); | 1934 | ids.sort_unstable(); |
| 1925 | ids.dedup(); | 1935 | ids.dedup(); |
| 1926 | Ok(ids) | 1936 | Ok(ids) |
src/sync.rs
| Old | New | ||
|---|---|---|---|
| @@ -354,7 +354,8 @@ fn print_refname_conflict_advice(remote: &str, ids: &[String]) { | |||
| 354 | "\nThat deletes only {} listed above, and only on '{}'. Nothing local is\n\ | 354 | "\nThat deletes only {} listed above, and only on '{}'. Nothing local is\n\ |
| 355 | touched: the next sync republishes the same events under `<id>/events`,\n\ | 355 | touched: the next sync republishes the same events under `<id>/events`,\n\ |
| 356 | which is where every current version reads them from.", | 356 | which is where every current version reads them from.", |
| 357 | refs_above, remote | 357 | refs_above, |
| 358 | remote | ||
| 358 | ); | 359 | ); |
| 359 | } | 360 | } |
| 360 | 361 | ||
| @@ -368,7 +369,8 @@ fn print_sync_summary(result: &SyncResult) { | |||
| 368 | } else { | 369 | } else { |
| 369 | errln!( | 370 | errln!( |
| 370 | "\nSync partially failed: {} of {} refs pushed.", | 371 | "\nSync partially failed: {} of {} refs pushed.", |
| 371 | succeeded, total | 372 | succeeded, |
| 373 | total | ||
| 372 | ); | 374 | ); |
| 373 | } | 375 | } |
| 374 | 376 | ||
| @@ -411,7 +413,8 @@ fn print_sync_summary(result: &SyncResult) { | |||
| 411 | if retryable > 0 { | 413 | if retryable > 0 { |
| 412 | errln!( | 414 | errln!( |
| 413 | "\nRun `git-collab sync --remote {}` again to retry {} failed ref(s).", | 415 | "\nRun `git-collab sync --remote {}` again to retry {} failed ref(s).", |
| 414 | result.remote, retryable | 416 | result.remote, |
| 417 | retryable | ||
| 415 | ); | 418 | ); |
| 416 | } | 419 | } |
| 417 | } | 420 | } |
src/timeline.rs
| Old | New | ||
|---|---|---|---|
| @@ -413,7 +413,9 @@ fn authorized( | |||
| 413 | target: &str, | 413 | target: &str, |
| 414 | author: &Author, | 414 | author: &Author, |
| 415 | ) -> bool { | 415 | ) -> bool { |
| 416 | owners.get(target).is_some_and(|owner| *owner == author.email) | 416 | owners |
| 417 | .get(target) | ||
| 418 | .is_some_and(|owner| *owner == author.email) | ||
| 417 | } | 419 | } |
| 418 | 420 | ||
| 419 | /// Render the timeline as one line per event. | 421 | /// Render the timeline as one line per event. |
| @@ -485,9 +487,7 @@ pub fn to_writer(entries: &[Entry], writer: &mut dyn std::io::Write) -> Result<( | |||
| 485 | | Kind::Resolved { target } | 487 | | Kind::Resolved { target } |
| 486 | | Kind::CommentUnresolved { target } => parts.push(format!("{:.8}", target)), | 488 | | Kind::CommentUnresolved { target } => parts.push(format!("{:.8}", target)), |
| 487 | Kind::Closed { reason } => parts.extend(reason.clone()), | 489 | Kind::Closed { reason } => parts.extend(reason.clone()), |
| 488 | Kind::Merged { commit } => { | 490 | Kind::Merged { commit } => parts.extend(commit.as_deref().map(|c| format!("{:.8}", c))), |
| 489 | parts.extend(commit.as_deref().map(|c| format!("{:.8}", c))) | ||
| 490 | } | ||
| 491 | Kind::Reopened => {} | 491 | Kind::Reopened => {} |
| 492 | } | 492 | } |
| 493 | 493 | ||
src/tui/events.rs
| Old | New | ||
|---|---|---|---|
| @@ -399,7 +399,10 @@ fn submit_comment( | |||
| 399 | return; | 399 | return; |
| 400 | }; | 400 | }; |
| 401 | let short = app.patch_abbrev.of(&patch.id).to_string(); | 401 | let short = app.patch_abbrev.of(&patch.id).to_string(); |
| 402 | let revision = patch.revisions.get(app.patch_revision_idx).map(|r| r.number); | 402 | let revision = patch |
| 403 | .revisions | ||
| 404 | .get(app.patch_revision_idx) | ||
| 405 | .map(|r| r.number); | ||
| 403 | 406 | ||
| 404 | // Inline when the cursor is on a line of the diff, on the thread | 407 | // Inline when the cursor is on a line of the diff, on the thread |
| 405 | // otherwise. Both go through `patch::comment`, which is the CLI's own | 408 | // otherwise. Both go through `patch::comment`, which is the CLI's own |
| @@ -457,7 +460,10 @@ fn submit_review( | |||
| 457 | return; | 460 | return; |
| 458 | }; | 461 | }; |
| 459 | let short = app.patch_abbrev.of(&patch.id).to_string(); | 462 | let short = app.patch_abbrev.of(&patch.id).to_string(); |
| 460 | let revision = patch.revisions.get(app.patch_revision_idx).map(|r| r.number); | 463 | let revision = patch |
| 464 | .revisions | ||
| 465 | .get(app.patch_revision_idx) | ||
| 466 | .map(|r| r.number); | ||
| 461 | let seed = comment_seed( | 467 | let seed = comment_seed( |
| 462 | &format!( | 468 | &format!( |
| 463 | "Review of patch {}: {}\n# Verdict: {}", | 469 | "Review of patch {}: {}\n# Verdict: {}", |
| @@ -488,8 +494,7 @@ fn toggle_resolve(app: &mut App, repo: &Repository) { | |||
| 488 | return; | 494 | return; |
| 489 | }; | 495 | }; |
| 490 | let RowTarget::Comment { oid, resolved } = app.cursor_target() else { | 496 | let RowTarget::Comment { oid, resolved } = app.cursor_target() else { |
| 491 | app.status_msg = | 497 | app.status_msg = Some("Move to an inline comment to mark it answered (j/k).".to_string()); |
| 492 | Some("Move to an inline comment to mark it answered (j/k).".to_string()); | ||
| 493 | return; | 498 | return; |
| 494 | }; | 499 | }; |
| 495 | let outcome = if resolved { | 500 | let outcome = if resolved { |
| @@ -604,8 +609,12 @@ fn open_patch_detail(app: &mut App, repo: &Repository, patch: crate::state::Patc | |||
| 604 | 609 | ||
| 605 | fn regenerate_patch_diff(app: &mut App, repo: &Repository) { | 610 | fn regenerate_patch_diff(app: &mut App, repo: &Repository) { |
| 606 | if let Some(ref patch) = app.current_patch { | 611 | if let Some(ref patch) = app.current_patch { |
| 607 | match generate_patch_diff_for(repo, patch, app.patch_revision_idx, app.patch_interdiff_mode) | 612 | match generate_patch_diff_for( |
| 608 | { | 613 | repo, |
| 614 | patch, | ||
| 615 | app.patch_revision_idx, | ||
| 616 | app.patch_interdiff_mode, | ||
| 617 | ) { | ||
| 609 | Ok(rows) => app.patch_diff = rows, | 618 | Ok(rows) => app.patch_diff = rows, |
| 610 | Err(e) => app.set_diff_text(&format!("(error generating diff: {})", e)), | 619 | Err(e) => app.set_diff_text(&format!("(error generating diff: {})", e)), |
| 611 | } | 620 | } |
| @@ -631,14 +640,20 @@ mod tests { | |||
| 631 | /// comment made of hash marks. | 640 | /// comment made of hash marks. |
| 632 | #[test] | 641 | #[test] |
| 633 | fn an_untouched_buffer_is_blank() { | 642 | fn an_untouched_buffer_is_blank() { |
| 634 | let seed = comment_seed("Commenting on src/lib.rs:3", vec![" let x = 1;".to_string()]); | 643 | let seed = comment_seed( |
| 644 | "Commenting on src/lib.rs:3", | ||
| 645 | vec![" let x = 1;".to_string()], | ||
| 646 | ); | ||
| 635 | assert!(strip_comments(&seed).trim().is_empty()); | 647 | assert!(strip_comments(&seed).trim().is_empty()); |
| 636 | } | 648 | } |
| 637 | 649 | ||
| 638 | /// Only a leading `#` is a comment marker; a `#` inside a line is prose. | 650 | /// Only a leading `#` is a comment marker; a `#` inside a line is prose. |
| 639 | #[test] | 651 | #[test] |
| 640 | fn a_hash_inside_a_line_survives() { | 652 | fn a_hash_inside_a_line_survives() { |
| 641 | assert_eq!(strip_comments("issue #12 is the same bug"), "issue #12 is the same bug"); | 653 | assert_eq!( |
| 654 | strip_comments("issue #12 is the same bug"), | ||
| 655 | "issue #12 is the same bug" | ||
| 656 | ); | ||
| 642 | } | 657 | } |
| 643 | 658 | ||
| 644 | /// The buffer says what is being written about and where the cursor was, | 659 | /// The buffer says what is being written about and where the cursor was, |
src/tui/keys.rs
| Old | New | ||
|---|---|---|---|
| @@ -405,7 +405,12 @@ const ISSUE_VERBS: &[Entry] = &[ | |||
| 405 | e(&[k('/')], Action::BeginSearch, "search", true), | 405 | e(&[k('/')], Action::BeginSearch, "search", true), |
| 406 | e(&[k('r')], Action::Reload, "refresh", true), | 406 | e(&[k('r')], Action::Reload, "refresh", true), |
| 407 | e(&[k('n')], Action::BeginCreateIssue, "new issue", true), | 407 | e(&[k('n')], Action::BeginCreateIssue, "new issue", true), |
| 408 | e(&[k('o')], Action::Checkout, "check out the linked patch", false), | 408 | e( |
| 409 | &[k('o')], | ||
| 410 | Action::Checkout, | ||
| 411 | "check out the linked patch", | ||
| 412 | false, | ||
| 413 | ), | ||
| 409 | e( | 414 | e( |
| 410 | &[c(KeyCode::PageDown)], | 415 | &[c(KeyCode::PageDown)], |
| 411 | Action::ScrollPageDown, | 416 | Action::ScrollPageDown, |
| @@ -501,14 +506,24 @@ const PATCH_DETAIL: &[Entry] = &[ | |||
| 501 | e(&[k('o')], Action::Checkout, "checkout", true), | 506 | e(&[k('o')], Action::Checkout, "checkout", true), |
| 502 | e(&[k('w')], Action::ToggleWrap, "wrap", true), | 507 | e(&[k('w')], Action::ToggleWrap, "wrap", true), |
| 503 | e(&[ctrl('e')], Action::ScrollRowDown, "scroll a row", false), | 508 | e(&[ctrl('e')], Action::ScrollRowDown, "scroll a row", false), |
| 504 | e(&[ctrl('y')], Action::ScrollRowUp, "scroll a row back", false), | 509 | e( |
| 510 | &[ctrl('y')], | ||
| 511 | Action::ScrollRowUp, | ||
| 512 | "scroll a row back", | ||
| 513 | false, | ||
| 514 | ), | ||
| 505 | e( | 515 | e( |
| 506 | &[c(KeyCode::PageDown)], | 516 | &[c(KeyCode::PageDown)], |
| 507 | Action::CursorPageDown, | 517 | Action::CursorPageDown, |
| 508 | "page down", | 518 | "page down", |
| 509 | false, | 519 | false, |
| 510 | ), | 520 | ), |
| 511 | e(&[c(KeyCode::PageUp)], Action::CursorPageUp, "page up", false), | 521 | e( |
| 522 | &[c(KeyCode::PageUp)], | ||
| 523 | Action::CursorPageUp, | ||
| 524 | "page up", | ||
| 525 | false, | ||
| 526 | ), | ||
| 512 | ]; | 527 | ]; |
| 513 | 528 | ||
| 514 | const EVENT_HISTORY: &[Entry] = &[ | 529 | const EVENT_HISTORY: &[Entry] = &[ |
| @@ -538,7 +553,12 @@ const EVENT_DETAIL: &[Entry] = &[ | |||
| 538 | "page down", | 553 | "page down", |
| 539 | false, | 554 | false, |
| 540 | ), | 555 | ), |
| 541 | e(&[c(KeyCode::PageUp)], Action::ScrollPageUp, "page up", false), | 556 | e( |
| 557 | &[c(KeyCode::PageUp)], | ||
| 558 | Action::ScrollPageUp, | ||
| 559 | "page up", | ||
| 560 | false, | ||
| 561 | ), | ||
| 542 | ]; | 562 | ]; |
| 543 | 563 | ||
| 544 | const REVIEW_VERDICT: &[Entry] = &[ | 564 | const REVIEW_VERDICT: &[Entry] = &[ |
src/tui/mod.rs
| Old | New | ||
|---|---|---|---|
| @@ -1739,9 +1739,7 @@ mod tests { | |||
| 1739 | fn logical_index_of_diff_line(app: &App, line: u32) -> usize { | 1739 | fn logical_index_of_diff_line(app: &App, line: u32) -> usize { |
| 1740 | app.patch_lines | 1740 | app.patch_lines |
| 1741 | .iter() | 1741 | .iter() |
| 1742 | .position(|r| { | 1742 | .position(|r| matches!(&r.target, RowTarget::Diff { line: l, .. } if *l == line)) |
| 1743 | matches!(&r.target, RowTarget::Diff { line: l, .. } if *l == line) | ||
| 1744 | }) | ||
| 1745 | .expect("a diff line with that anchor") | 1743 | .expect("a diff line with that anchor") |
| 1746 | } | 1744 | } |
| 1747 | 1745 | ||
| @@ -1861,8 +1859,7 @@ mod tests { | |||
| 1861 | 1859 | ||
| 1862 | let highlighted = (1..buf.area.height - 1) | 1860 | let highlighted = (1..buf.area.height - 1) |
| 1863 | .filter(|y| { | 1861 | .filter(|y| { |
| 1864 | (0..buf.area.width) | 1862 | (0..buf.area.width).any(|x| buf.cell((x, *y)).unwrap().bg == Color::DarkGray) |
| 1865 | .any(|x| buf.cell((x, *y)).unwrap().bg == Color::DarkGray) | ||
| 1866 | }) | 1863 | }) |
| 1867 | .count(); | 1864 | .count(); |
| 1868 | assert_eq!( | 1865 | assert_eq!( |
| @@ -2110,7 +2107,9 @@ mod tests { | |||
| 2110 | ] | 2107 | ] |
| 2111 | ); | 2108 | ); |
| 2112 | assert!( | 2109 | assert!( |
| 2113 | app.patch_lines.iter().all(|r| !r.line.to_string().contains('\n')), | 2110 | app.patch_lines |
| 2111 | .iter() | ||
| 2112 | .all(|r| !r.line.to_string().contains('\n')), | ||
| 2114 | "a rendered line still holds a newline" | 2113 | "a rendered line still holds a newline" |
| 2115 | ); | 2114 | ); |
| 2116 | } | 2115 | } |
| @@ -2635,7 +2634,11 @@ mod tests { | |||
| 2635 | crossterm::event::KeyModifiers::empty(), | 2634 | crossterm::event::KeyModifiers::empty(), |
| 2636 | ); | 2635 | ); |
| 2637 | let text = buffer_to_string(&render_app(&mut app)); | 2636 | let text = buffer_to_string(&render_app(&mut app)); |
| 2638 | assert!(text.contains("Ctrl-e"), "no Ctrl-e in the overlay:\n{}", text); | 2637 | assert!( |
| 2638 | text.contains("Ctrl-e"), | ||
| 2639 | "no Ctrl-e in the overlay:\n{}", | ||
| 2640 | text | ||
| 2641 | ); | ||
| 2639 | assert!( | 2642 | assert!( |
| 2640 | text.contains("Patch detail"), | 2643 | text.contains("Patch detail"), |
| 2641 | "the overlay does not say which pane it is about:\n{}", | 2644 | "the overlay does not say which pane it is about:\n{}", |
src/tui/state.rs
| Old | New | ||
|---|---|---|---|
| @@ -599,14 +599,16 @@ impl App { | |||
| 599 | let len = self.event_history.len(); | 599 | let len = self.event_history.len(); |
| 600 | if len > 0 { | 600 | if len > 0 { |
| 601 | let current = self.event_list_state.selected().unwrap_or(0); | 601 | let current = self.event_list_state.selected().unwrap_or(0); |
| 602 | self.event_list_state.select(Some((current + 1).min(len - 1))); | 602 | self.event_list_state |
| 603 | .select(Some((current + 1).min(len - 1))); | ||
| 603 | } | 604 | } |
| 604 | KeyAction::Continue | 605 | KeyAction::Continue |
| 605 | } | 606 | } |
| 606 | Action::EventPrev => { | 607 | Action::EventPrev => { |
| 607 | if !self.event_history.is_empty() { | 608 | if !self.event_history.is_empty() { |
| 608 | let current = self.event_list_state.selected().unwrap_or(0); | 609 | let current = self.event_list_state.selected().unwrap_or(0); |
| 609 | self.event_list_state.select(Some(current.saturating_sub(1))); | 610 | self.event_list_state |
| 611 | .select(Some(current.saturating_sub(1))); | ||
| 610 | } | 612 | } |
| 611 | KeyAction::Continue | 613 | KeyAction::Continue |
| 612 | } | 614 | } |
| @@ -638,8 +640,7 @@ impl App { | |||
| 638 | if self.linked_patch_for_selected().is_some() { | 640 | if self.linked_patch_for_selected().is_some() { |
| 639 | KeyAction::OpenPatchDetail | 641 | KeyAction::OpenPatchDetail |
| 640 | } else { | 642 | } else { |
| 641 | self.status_msg = | 643 | self.status_msg = Some("No patch is linked to this issue yet.".to_string()); |
| 642 | Some("No patch is linked to this issue yet.".to_string()); | ||
| 643 | KeyAction::Continue | 644 | KeyAction::Continue |
| 644 | } | 645 | } |
| 645 | } | 646 | } |
src/tui/widgets.rs
| Old | New | ||
|---|---|---|---|
| @@ -340,7 +340,11 @@ impl Rows { | |||
| 340 | /// scroll stays a row offset. A width of zero means the pane has not been drawn | 340 | /// scroll stays a row offset. A width of zero means the pane has not been drawn |
| 341 | /// yet and nothing is known about how much room there is, so the lines pass | 341 | /// yet and nothing is known about how much room there is, so the lines pass |
| 342 | /// through one for one. | 342 | /// through one for one. |
| 343 | pub(crate) fn wrap_detail_rows(lines: &[DetailRow], width: u16, wrap_diff: bool) -> Vec<DisplayRow> { | 343 | pub(crate) fn wrap_detail_rows( |
| 344 | lines: &[DetailRow], | ||
| 345 | width: u16, | ||
| 346 | wrap_diff: bool, | ||
| 347 | ) -> Vec<DisplayRow> { | ||
| 344 | let width = width as usize; | 348 | let width = width as usize; |
| 345 | let mut out = Vec::with_capacity(lines.len()); | 349 | let mut out = Vec::with_capacity(lines.len()); |
| 346 | for (logical, row) in lines.iter().enumerate() { | 350 | for (logical, row) in lines.iter().enumerate() { |
| @@ -494,8 +498,7 @@ fn render_help(frame: &mut Frame, app: &App, area: Rect) { | |||
| 494 | .max() | 498 | .max() |
| 495 | .unwrap_or(0); | 499 | .unwrap_or(0); |
| 496 | // ` <keys> <label>` inside a border on each side. | 500 | // ` <keys> <label>` inside a border on each side. |
| 497 | let width = (keys_width + label_width + 4) | 501 | let width = (keys_width + label_width + 4).max(context.title().chars().count() + 9) as u16; |
| 498 | .max(context.title().chars().count() + 9) as u16; | ||
| 499 | let width = width.min(area.width); | 502 | let width = width.min(area.width); |
| 500 | let height = (rows.len() as u16 + 2).min(area.height); | 503 | let height = (rows.len() as u16 + 2).min(area.height); |
| 501 | 504 | ||
tests/alias_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -17,8 +17,8 @@ use git_collab::cli::Cli; | |||
| 17 | fn parse(args: &[&str]) -> String { | 17 | fn parse(args: &[&str]) -> String { |
| 18 | let mut argv = vec!["git-collab"]; | 18 | let mut argv = vec!["git-collab"]; |
| 19 | argv.extend_from_slice(args); | 19 | argv.extend_from_slice(args); |
| 20 | let cli = Cli::try_parse_from(&argv) | 20 | let cli = |
| 21 | .unwrap_or_else(|e| panic!("failed to parse {:?}: {}", args, e)); | 21 | Cli::try_parse_from(&argv).unwrap_or_else(|e| panic!("failed to parse {:?}: {}", args, e)); |
| 22 | format!("{:?}", cli.command) | 22 | format!("{:?}", cli.command) |
| 23 | } | 23 | } |
| 24 | 24 | ||
| @@ -85,7 +85,10 @@ fn key_generate_reaches_the_same_generator_as_init_key() { | |||
| 85 | #[test] | 85 | #[test] |
| 86 | fn new_is_a_synonym_for_open_and_create() { | 86 | fn new_is_a_synonym_for_open_and_create() { |
| 87 | assert_same(&["issue", "new", "-t", "t"], &["issue", "open", "-t", "t"]); | 87 | assert_same(&["issue", "new", "-t", "t"], &["issue", "open", "-t", "t"]); |
| 88 | assert_same(&["patch", "new", "-t", "t"], &["patch", "create", "-t", "t"]); | 88 | assert_same( |
| 89 | &["patch", "new", "-t", "t"], | ||
| 90 | &["patch", "create", "-t", "t"], | ||
| 91 | ); | ||
| 89 | } | 92 | } |
| 90 | 93 | ||
| 91 | #[test] | 94 | #[test] |
tests/body_edit_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -118,7 +118,14 @@ fn a_patch_thread_comment_can_be_corrected() { | |||
| 118 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 118 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 119 | let comment_id = first_comment_id(&json, "comments"); | 119 | let comment_id = first_comment_id(&json, "comments"); |
| 120 | 120 | ||
| 121 | repo.run_ok(&["patch", "edit-comment", &id, &comment_id[..8], "-b", "right"]); | 121 | repo.run_ok(&[ |
| 122 | "patch", | ||
| 123 | "edit-comment", | ||
| 124 | &id, | ||
| 125 | &comment_id[..8], | ||
| 126 | "-b", | ||
| 127 | "right", | ||
| 128 | ]); | ||
| 122 | 129 | ||
| 123 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 130 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 124 | assert_eq!(body_at(&json, "/comments/0/body"), "right"); | 131 | assert_eq!(body_at(&json, "/comments/0/body"), "right"); |
| @@ -145,7 +152,14 @@ fn an_inline_comment_can_be_corrected_without_moving() { | |||
| 145 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 152 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 146 | let comment_id = first_comment_id(&json, "inline_comments"); | 153 | let comment_id = first_comment_id(&json, "inline_comments"); |
| 147 | 154 | ||
| 148 | repo.run_ok(&["patch", "edit-comment", &id, &comment_id[..8], "-b", "right"]); | 155 | repo.run_ok(&[ |
| 156 | "patch", | ||
| 157 | "edit-comment", | ||
| 158 | &id, | ||
| 159 | &comment_id[..8], | ||
| 160 | "-b", | ||
| 161 | "right", | ||
| 162 | ]); | ||
| 149 | 163 | ||
| 150 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 164 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 151 | assert_eq!(body_at(&json, "/inline_comments/0/body"), "right"); | 165 | assert_eq!(body_at(&json, "/inline_comments/0/body"), "right"); |
| @@ -231,10 +245,7 @@ fn editing_with_no_body_opens_the_editor_seeded_with_the_current_text() { | |||
| 231 | "#!/bin/sh\nprintf '%s and more' \"$(cat \"$1\")\" > \"$1\"\n", | 245 | "#!/bin/sh\nprintf '%s and more' \"$(cat \"$1\")\" > \"$1\"\n", |
| 232 | ); | 246 | ); |
| 233 | 247 | ||
| 234 | repo.run_in_pty( | 248 | repo.run_in_pty(&["issue", "edit-comment", &id, &comment_id[..8]], &editor); |
| 235 | &["issue", "edit-comment", &id, &comment_id[..8]], | ||
| 236 | &editor, | ||
| 237 | ); | ||
| 238 | 249 | ||
| 239 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); | 250 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); |
| 240 | assert_eq!(body_at(&json, "/comments/0/body"), "seed me and more"); | 251 | assert_eq!(body_at(&json, "/comments/0/body"), "seed me and more"); |
| @@ -266,11 +277,20 @@ fn a_deleted_comment_leaves_a_tombstone_in_place() { | |||
| 266 | let id = repo.patch_create("tombstone"); | 277 | let id = repo.patch_create("tombstone"); |
| 267 | repo.run_ok(&["patch", "comment", &id, "-b", "first"]); | 278 | repo.run_ok(&["patch", "comment", &id, "-b", "first"]); |
| 268 | repo.run_ok(&["patch", "comment", &id, "-b", "regrettable probe"]); | 279 | repo.run_ok(&["patch", "comment", &id, "-b", "regrettable probe"]); |
| 269 | repo.run_ok(&["patch", "comment", &id, "-b", "third, replying to the above"]); | 280 | repo.run_ok(&[ |
| 281 | "patch", | ||
| 282 | "comment", | ||
| 283 | &id, | ||
| 284 | "-b", | ||
| 285 | "third, replying to the above", | ||
| 286 | ]); | ||
| 270 | 287 | ||
| 271 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 288 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 272 | let value: serde_json::Value = serde_json::from_str(&json).unwrap(); | 289 | let value: serde_json::Value = serde_json::from_str(&json).unwrap(); |
| 273 | let target = value["comments"][1]["commit_id"].as_str().unwrap().to_string(); | 290 | let target = value["comments"][1]["commit_id"] |
| 291 | .as_str() | ||
| 292 | .unwrap() | ||
| 293 | .to_string(); | ||
| 274 | 294 | ||
| 275 | repo.run_ok(&["patch", "delete-comment", &id, &target[..8]]); | 295 | repo.run_ok(&["patch", "delete-comment", &id, &target[..8]]); |
| 276 | 296 | ||
| @@ -465,7 +485,10 @@ fn an_edited_comment_keeps_its_position() { | |||
| 465 | 485 | ||
| 466 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); | 486 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); |
| 467 | let value: serde_json::Value = serde_json::from_str(&json).unwrap(); | 487 | let value: serde_json::Value = serde_json::from_str(&json).unwrap(); |
| 468 | let target = value["comments"][0]["commit_id"].as_str().unwrap().to_string(); | 488 | let target = value["comments"][0]["commit_id"] |
| 489 | .as_str() | ||
| 490 | .unwrap() | ||
| 491 | .to_string(); | ||
| 469 | 492 | ||
| 470 | repo.run_ok(&["issue", "edit-comment", &id, &target[..8], "-b", "FIRST"]); | 493 | repo.run_ok(&["issue", "edit-comment", &id, &target[..8], "-b", "FIRST"]); |
| 471 | 494 | ||
| @@ -508,7 +531,8 @@ fn concurrent_edits_of_one_comment_converge() { | |||
| 508 | 531 | ||
| 509 | // Her other clone, offline, edits the same comment from the fork point. | 532 | // Her other clone, offline, edits the same comment from the fork point. |
| 510 | let branch_ref = "refs/collab/issues/other-clone"; | 533 | let branch_ref = "refs/collab/issues/other-clone"; |
| 511 | repo.reference(branch_ref, fork_point, false, "fork").unwrap(); | 534 | repo.reference(branch_ref, fork_point, false, "fork") |
| 535 | .unwrap(); | ||
| 512 | let b_edit = append( | 536 | let b_edit = append( |
| 513 | &repo, | 537 | &repo, |
| 514 | branch_ref, | 538 | branch_ref, |
| @@ -566,7 +590,8 @@ fn a_delete_and_an_edit_racing_converge() { | |||
| 566 | let a_tip = repo.refname_to_id(&ref_name).unwrap(); | 590 | let a_tip = repo.refname_to_id(&ref_name).unwrap(); |
| 567 | 591 | ||
| 568 | let branch_ref = "refs/collab/issues/delete-clone"; | 592 | let branch_ref = "refs/collab/issues/delete-clone"; |
| 569 | repo.reference(branch_ref, fork_point, false, "fork").unwrap(); | 593 | repo.reference(branch_ref, fork_point, false, "fork") |
| 594 | .unwrap(); | ||
| 570 | let delete_oid = append( | 595 | let delete_oid = append( |
| 571 | &repo, | 596 | &repo, |
| 572 | branch_ref, | 597 | branch_ref, |
| @@ -583,7 +608,8 @@ fn a_delete_and_an_edit_racing_converge() { | |||
| 583 | let one = issue_state(&repo, merged_ref, &id); | 608 | let one = issue_state(&repo, merged_ref, &id); |
| 584 | 609 | ||
| 585 | let other_ref = "refs/collab/issues/dm-other"; | 610 | let other_ref = "refs/collab/issues/dm-other"; |
| 586 | repo.reference(other_ref, delete_oid, false, "copy").unwrap(); | 611 | repo.reference(other_ref, delete_oid, false, "copy") |
| 612 | .unwrap(); | ||
| 587 | dag::reconcile(&repo, other_ref, &ref_name, &alice(), &sk).unwrap(); | 613 | dag::reconcile(&repo, other_ref, &ref_name, &alice(), &sk).unwrap(); |
| 588 | let two = issue_state(&repo, other_ref, &id); | 614 | let two = issue_state(&repo, other_ref, &id); |
| 589 | 615 | ||
| @@ -610,7 +636,10 @@ fn a_revision_body_can_be_corrected() { | |||
| 610 | 636 | ||
| 611 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 637 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 612 | let value: serde_json::Value = serde_json::from_str(&json).unwrap(); | 638 | let value: serde_json::Value = serde_json::from_str(&json).unwrap(); |
| 613 | let commit_before = value["revisions"][1]["commit"].as_str().unwrap().to_string(); | 639 | let commit_before = value["revisions"][1]["commit"] |
| 640 | .as_str() | ||
| 641 | .unwrap() | ||
| 642 | .to_string(); | ||
| 614 | assert_eq!(value["revisions"][1]["body"], "test"); | 643 | assert_eq!(value["revisions"][1]["body"], "test"); |
| 615 | 644 | ||
| 616 | repo.run_ok(&[ | 645 | repo.run_ok(&[ |
| @@ -703,7 +732,11 @@ fn reading_a_patch_never_moves_its_event_ref() { | |||
| 703 | repo.run_ok(&["patch", "delete-comment", &id, &comment_id[..8]]); | 732 | repo.run_ok(&["patch", "delete-comment", &id, &comment_id[..8]]); |
| 704 | 733 | ||
| 705 | let events_ref = repo | 734 | let events_ref = repo |
| 706 | .git(&["for-each-ref", "--format=%(refname)", "refs/collab/patches/"]) | 735 | .git(&[ |
| 736 | "for-each-ref", | ||
| 737 | "--format=%(refname)", | ||
| 738 | "refs/collab/patches/", | ||
| 739 | ]) | ||
| 707 | .lines() | 740 | .lines() |
| 708 | .find(|l| l.ends_with("/events")) | 741 | .find(|l| l.ends_with("/events")) |
| 709 | .expect("patch events ref") | 742 | .expect("patch events ref") |
| @@ -802,7 +835,14 @@ fn an_edited_comment_is_marked_as_edited() { | |||
| 802 | 835 | ||
| 803 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); | 836 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); |
| 804 | let comment_id = first_comment_id(&json, "comments"); | 837 | let comment_id = first_comment_id(&json, "comments"); |
| 805 | repo.run_ok(&["issue", "edit-comment", &id, &comment_id[..8], "-b", "after"]); | 838 | repo.run_ok(&[ |
| 839 | "issue", | ||
| 840 | "edit-comment", | ||
| 841 | &id, | ||
| 842 | &comment_id[..8], | ||
| 843 | "-b", | ||
| 844 | "after", | ||
| 845 | ]); | ||
| 806 | 846 | ||
| 807 | let out = repo.run_ok(&["issue", "show", &id]); | 847 | let out = repo.run_ok(&["issue", "show", &id]); |
| 808 | assert!( | 848 | assert!( |
tests/body_input_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -66,7 +66,10 @@ fn body_file_has_a_long_form_too() { | |||
| 66 | ]); | 66 | ]); |
| 67 | 67 | ||
| 68 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); | 68 | let json = repo.run_ok(&["issue", "show", &id, "--json"]); |
| 69 | assert_eq!(json_field(&json, "/comments/0/body"), "from the long form\n"); | 69 | assert_eq!( |
| 70 | json_field(&json, "/comments/0/body"), | ||
| 71 | "from the long form\n" | ||
| 72 | ); | ||
| 70 | } | 73 | } |
| 71 | 74 | ||
| 72 | #[test] | 75 | #[test] |
| @@ -157,7 +160,11 @@ fn stdin_body_survives_on_every_command_that_takes_one() { | |||
| 157 | &["issue", "open", "-t", "opened from stdin", "-F", "-"], | 160 | &["issue", "open", "-t", "opened from stdin", "-F", "-"], |
| 158 | NASTY.as_bytes(), | 161 | NASTY.as_bytes(), |
| 159 | ); | 162 | ); |
| 160 | let issue_id = out.trim().strip_prefix("Opened issue ").unwrap().to_string(); | 163 | let issue_id = out |
| 164 | .trim() | ||
| 165 | .strip_prefix("Opened issue ") | ||
| 166 | .unwrap() | ||
| 167 | .to_string(); | ||
| 161 | let json = repo.run_ok(&["issue", "show", &issue_id, "--json"]); | 168 | let json = repo.run_ok(&["issue", "show", &issue_id, "--json"]); |
| 162 | assert_eq!(json_field(&json, "/body"), NASTY, "issue open"); | 169 | assert_eq!(json_field(&json, "/body"), NASTY, "issue open"); |
| 163 | 170 | ||
| @@ -186,7 +193,11 @@ fn stdin_body_survives_on_every_command_that_takes_one() { | |||
| 186 | NASTY.as_bytes(), | 193 | NASTY.as_bytes(), |
| 187 | ); | 194 | ); |
| 188 | repo.git(&["checkout", "main"]); | 195 | repo.git(&["checkout", "main"]); |
| 189 | let patch_id = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 196 | let patch_id = out |
| 197 | .trim() | ||
| 198 | .strip_prefix("Created patch ") | ||
| 199 | .unwrap() | ||
| 200 | .to_string(); | ||
| 190 | let json = repo.run_ok(&["patch", "show", &patch_id, "--json"]); | 201 | let json = repo.run_ok(&["patch", "show", &patch_id, "--json"]); |
| 191 | assert_eq!(json_field(&json, "/body"), NASTY, "patch create"); | 202 | assert_eq!(json_field(&json, "/body"), NASTY, "patch create"); |
| 192 | 203 | ||
| @@ -196,7 +207,11 @@ fn stdin_body_survives_on_every_command_that_takes_one() { | |||
| 196 | repo.run_stdin_ok(&["patch", "revise", &patch_id, "-F", "-"], NASTY.as_bytes()); | 207 | repo.run_stdin_ok(&["patch", "revise", &patch_id, "-F", "-"], NASTY.as_bytes()); |
| 197 | repo.git(&["checkout", "main"]); | 208 | repo.git(&["checkout", "main"]); |
| 198 | let json = repo.run_ok(&["patch", "show", &patch_id, "--json"]); | 209 | let json = repo.run_ok(&["patch", "show", &patch_id, "--json"]); |
| 199 | assert_eq!(json_field(&json, "/revisions/1/body"), NASTY, "patch revise"); | 210 | assert_eq!( |
| 211 | json_field(&json, "/revisions/1/body"), | ||
| 212 | NASTY, | ||
| 213 | "patch revise" | ||
| 214 | ); | ||
| 200 | } | 215 | } |
| 201 | 216 | ||
| 202 | // =========================================================================== | 217 | // =========================================================================== |
tests/cli_surface_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -67,7 +67,8 @@ fn scanned_files() -> Vec<PathBuf> { | |||
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | fn collect_rs(dir: &Path, out: &mut Vec<PathBuf>) { | 69 | fn collect_rs(dir: &Path, out: &mut Vec<PathBuf>) { |
| 70 | let entries = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read {}: {}", dir.display(), e)); | 70 | let entries = |
| 71 | std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read {}: {}", dir.display(), e)); | ||
| 71 | for entry in entries { | 72 | for entry in entries { |
| 72 | let path = entry.unwrap().path(); | 73 | let path = entry.unwrap().path(); |
| 73 | if path.is_dir() { | 74 | if path.is_dir() { |
| @@ -104,7 +105,8 @@ const BINARY_HEADS: [&str; 3] = ["git-collab ", "{} ", "collab "]; | |||
| 104 | const DELIMITERS: [char; 2] = ['`', '\'']; | 105 | const DELIMITERS: [char; 2] = ['`', '\'']; |
| 105 | 106 | ||
| 106 | fn citations_in(file: &Path) -> (Vec<Citation>, Vec<Citation>) { | 107 | fn citations_in(file: &Path) -> (Vec<Citation>, Vec<Citation>) { |
| 107 | let text = std::fs::read_to_string(file).unwrap_or_else(|e| panic!("read {}: {}", file.display(), e)); | 108 | let text = |
| 109 | std::fs::read_to_string(file).unwrap_or_else(|e| panic!("read {}: {}", file.display(), e)); | ||
| 108 | let mut good = Vec::new(); | 110 | let mut good = Vec::new(); |
| 109 | let mut wrong_binary = Vec::new(); | 111 | let mut wrong_binary = Vec::new(); |
| 110 | 112 | ||
| @@ -153,7 +155,10 @@ fn openings(line: &str) -> Vec<(usize, &'static str, Option<char>)> { | |||
| 153 | // real spellings are looked for here. | 155 | // real spellings are looked for here. |
| 154 | let trimmed = line.trim_start(); | 156 | let trimmed = line.trim_start(); |
| 155 | if let Some(cmd) = trimmed.strip_prefix("$ ") { | 157 | if let Some(cmd) = trimmed.strip_prefix("$ ") { |
| 156 | if let Some(head) = ["git-collab ", "collab "].iter().find(|h| cmd.starts_with(**h)) { | 158 | if let Some(head) = ["git-collab ", "collab "] |
| 159 | .iter() | ||
| 160 | .find(|h| cmd.starts_with(**h)) | ||
| 161 | { | ||
| 157 | let offset = line.len() - cmd.len(); | 162 | let offset = line.len() - cmd.len(); |
| 158 | found.push((offset, *head, None)); | 163 | found.push((offset, *head, None)); |
| 159 | } | 164 | } |
tests/cli_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -970,7 +970,11 @@ fn test_git_merge_shows_a_hint_and_patch_merge_records_it() { | |||
| 970 | 970 | ||
| 971 | // Create patch pointing at the feature branch | 971 | // Create patch pointing at the feature branch |
| 972 | let out = repo.run_ok(&["patch", "create", "-t", "Add feature", "-B", "feature"]); | 972 | let out = repo.run_ok(&["patch", "create", "-t", "Add feature", "-B", "feature"]); |
| 973 | let id = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 973 | let id = out |
| 974 | .trim() | ||
| 975 | .strip_prefix("Created patch ") | ||
| 976 | .unwrap() | ||
| 977 | .to_string(); | ||
| 974 | 978 | ||
| 975 | // Merge via git directly | 979 | // Merge via git directly |
| 976 | repo.git(&["merge", "feature"]); | 980 | repo.git(&["merge", "feature"]); |
| @@ -982,7 +986,11 @@ fn test_git_merge_shows_a_hint_and_patch_merge_records_it() { | |||
| 982 | 986 | ||
| 983 | repo.run_ok(&["patch", "merge", &id]); | 987 | repo.run_ok(&["patch", "merge", &id]); |
| 984 | let out = repo.run_ok(&["patch", "show", &id]); | 988 | let out = repo.run_ok(&["patch", "show", &id]); |
| 985 | assert!(out.contains("[merged]") && !out.contains("[merged?]"), "{}", out); | 989 | assert!( |
| 990 | out.contains("[merged]") && !out.contains("[merged?]"), | ||
| 991 | "{}", | ||
| 992 | out | ||
| 993 | ); | ||
| 986 | } | 994 | } |
| 987 | 995 | ||
| 988 | #[test] | 996 | #[test] |
tests/comment_resolution_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -126,7 +126,16 @@ fn rebased_patch_with_resolved_comment() -> (TestRepo, String, String) { | |||
| 126 | 126 | ||
| 127 | // A reviewer leaves an inline comment on r1. | 127 | // A reviewer leaves an inline comment on r1. |
| 128 | repo.run_ok(&[ | 128 | repo.run_ok(&[ |
| 129 | "patch", "comment", &id, "--file", "feature.txt", "--line", "1", "--revision", "1", "-b", | 129 | "patch", |
| 130 | "comment", | ||
| 131 | &id, | ||
| 132 | "--file", | ||
| 133 | "feature.txt", | ||
| 134 | "--line", | ||
| 135 | "1", | ||
| 136 | "--revision", | ||
| 137 | "1", | ||
| 138 | "-b", | ||
| 130 | "wrong value here", | 139 | "wrong value here", |
| 131 | ]); | 140 | ]); |
| 132 | 141 | ||
| @@ -157,7 +166,15 @@ fn resolving_a_comment_records_who_and_at_which_revision() { | |||
| 157 | let repo = TestRepo::new("Alice", "alice@example.com"); | 166 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 158 | let id = patch_over_a_file(&repo, "needs a guard"); | 167 | let id = patch_over_a_file(&repo, "needs a guard"); |
| 159 | repo.run_ok(&[ | 168 | repo.run_ok(&[ |
| 160 | "patch", "comment", &id, "--file", "feature.txt", "--line", "1", "-b", "add a guard", | 169 | "patch", |
| 170 | "comment", | ||
| 171 | &id, | ||
| 172 | "--file", | ||
| 173 | "feature.txt", | ||
| 174 | "--line", | ||
| 175 | "1", | ||
| 176 | "-b", | ||
| 177 | "add a guard", | ||
| 161 | ]); | 178 | ]); |
| 162 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 179 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 163 | let comment = first_inline_id(&json); | 180 | let comment = first_inline_id(&json); |
| @@ -177,7 +194,9 @@ fn resolving_a_comment_records_who_and_at_which_revision() { | |||
| 177 | "resolution defaults to the patch's latest revision" | 194 | "resolution defaults to the patch's latest revision" |
| 178 | ); | 195 | ); |
| 179 | assert!( | 196 | assert!( |
| 180 | resolved["commit_id"].as_str().is_some_and(|s| s.len() == 40), | 197 | resolved["commit_id"] |
| 198 | .as_str() | ||
| 199 | .is_some_and(|s| s.len() == 40), | ||
| 181 | "the resolving event's full id must be in --json: {}", | 200 | "the resolving event's full id must be in --json: {}", |
| 182 | resolved | 201 | resolved |
| 183 | ); | 202 | ); |
| @@ -227,7 +246,15 @@ fn an_unresolved_comment_reports_resolved_null_rather_than_omitting_it() { | |||
| 227 | let repo = TestRepo::new("Alice", "alice@example.com"); | 246 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 228 | let id = patch_over_a_file(&repo, "nothing resolved"); | 247 | let id = patch_over_a_file(&repo, "nothing resolved"); |
| 229 | repo.run_ok(&[ | 248 | repo.run_ok(&[ |
| 230 | "patch", "comment", &id, "--file", "feature.txt", "--line", "1", "-b", "look here", | 249 | "patch", |
| 250 | "comment", | ||
| 251 | &id, | ||
| 252 | "--file", | ||
| 253 | "feature.txt", | ||
| 254 | "--line", | ||
| 255 | "1", | ||
| 256 | "-b", | ||
| 257 | "look here", | ||
| 231 | ]); | 258 | ]); |
| 232 | 259 | ||
| 233 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 260 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| @@ -247,7 +274,15 @@ fn patch_show_marks_resolved_threads_and_says_who_resolved_them() { | |||
| 247 | let repo = TestRepo::new("Alice", "alice@example.com"); | 274 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 248 | let id = patch_over_a_file(&repo, "marked up"); | 275 | let id = patch_over_a_file(&repo, "marked up"); |
| 249 | repo.run_ok(&[ | 276 | repo.run_ok(&[ |
| 250 | "patch", "comment", &id, "--file", "feature.txt", "--line", "1", "-b", "add a guard", | 277 | "patch", |
| 278 | "comment", | ||
| 279 | &id, | ||
| 280 | "--file", | ||
| 281 | "feature.txt", | ||
| 282 | "--line", | ||
| 283 | "1", | ||
| 284 | "-b", | ||
| 285 | "add a guard", | ||
| 251 | ]); | 286 | ]); |
| 252 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 287 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 253 | let comment = first_inline_id(&json); | 288 | let comment = first_inline_id(&json); |
| @@ -259,11 +294,7 @@ fn patch_show_marks_resolved_threads_and_says_who_resolved_them() { | |||
| 259 | "patch show must say a thread was resolved: {}", | 294 | "patch show must say a thread was resolved: {}", |
| 260 | out | 295 | out |
| 261 | ); | 296 | ); |
| 262 | assert!( | 297 | assert!(out.contains("Alice"), "and who resolved it: {}", out); |
| 263 | out.contains("Alice"), | ||
| 264 | "and who resolved it: {}", | ||
| 265 | out | ||
| 266 | ); | ||
| 267 | } | 298 | } |
| 268 | 299 | ||
| 269 | #[test] | 300 | #[test] |
| @@ -271,7 +302,15 @@ fn reopening_clears_the_resolution_and_both_events_stay_in_the_record() { | |||
| 271 | let repo = TestRepo::new("Alice", "alice@example.com"); | 302 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 272 | let id = patch_over_a_file(&repo, "disputed"); | 303 | let id = patch_over_a_file(&repo, "disputed"); |
| 273 | repo.run_ok(&[ | 304 | repo.run_ok(&[ |
| 274 | "patch", "comment", &id, "--file", "feature.txt", "--line", "1", "-b", "not done", | 305 | "patch", |
| 306 | "comment", | ||
| 307 | &id, | ||
| 308 | "--file", | ||
| 309 | "feature.txt", | ||
| 310 | "--line", | ||
| 311 | "1", | ||
| 312 | "-b", | ||
| 313 | "not done", | ||
| 275 | ]); | 314 | ]); |
| 276 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); | 315 | let json = repo.run_ok(&["patch", "show", &id, "--json"]); |
| 277 | let comment = first_inline_id(&json); | 316 | let comment = first_inline_id(&json); |
| @@ -339,7 +378,11 @@ fn a_new_revision_leaves_a_resolution_at_the_revision_it_was_claimed_against() { | |||
| 339 | repo.git(&["checkout", "-b", "feat"]); | 378 | repo.git(&["checkout", "-b", "feat"]); |
| 340 | repo.commit_file("feature.txt", "v1\n", "feature v1"); | 379 | repo.commit_file("feature.txt", "v1\n", "feature v1"); |
| 341 | let out = repo.run_ok(&["patch", "create", "-t", "moving target", "-B", "feat"]); | 380 | let out = repo.run_ok(&["patch", "create", "-t", "moving target", "-B", "feat"]); |
| 342 | let id = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 381 | let id = out |
| 382 | .trim() | ||
| 383 | .strip_prefix("Created patch ") | ||
| 384 | .unwrap() | ||
| 385 | .to_string(); | ||
| 343 | 386 | ||
| 344 | repo.run_ok(&[ | 387 | repo.run_ok(&[ |
| 345 | "patch", | 388 | "patch", |
| @@ -415,7 +458,8 @@ fn a_concurrent_resolve_and_reopen_converge() { | |||
| 415 | 458 | ||
| 416 | // The reviewer, offline, disagrees from the same fork point. | 459 | // The reviewer, offline, disagrees from the same fork point. |
| 417 | let branch_ref = "refs/collab/patches/other-clone/events"; | 460 | let branch_ref = "refs/collab/patches/other-clone/events"; |
| 418 | repo.reference(branch_ref, fork_point, false, "fork").unwrap(); | 461 | repo.reference(branch_ref, fork_point, false, "fork") |
| 462 | .unwrap(); | ||
| 419 | let reopen_oid = append( | 463 | let reopen_oid = append( |
| 420 | &repo, | 464 | &repo, |
| 421 | branch_ref, | 465 | branch_ref, |
| @@ -433,7 +477,8 @@ fn a_concurrent_resolve_and_reopen_converge() { | |||
| 433 | let one = patch_state(&repo, merged_ref, &id); | 477 | let one = patch_state(&repo, merged_ref, &id); |
| 434 | 478 | ||
| 435 | let other_ref = "refs/collab/patches/merged-other-way/events"; | 479 | let other_ref = "refs/collab/patches/merged-other-way/events"; |
| 436 | repo.reference(other_ref, reopen_oid, false, "copy").unwrap(); | 480 | repo.reference(other_ref, reopen_oid, false, "copy") |
| 481 | .unwrap(); | ||
| 437 | dag::reconcile(&repo, other_ref, &ref_name, &alice(), &sk).unwrap(); | 482 | dag::reconcile(&repo, other_ref, &ref_name, &alice(), &sk).unwrap(); |
| 438 | let two = patch_state(&repo, other_ref, &id); | 483 | let two = patch_state(&repo, other_ref, &id); |
| 439 | 484 | ||
| @@ -570,10 +615,26 @@ fn resolving_by_an_ambiguous_prefix_errors_like_every_other_prefix() { | |||
| 570 | let repo = TestRepo::new("Alice", "alice@example.com"); | 615 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 571 | let id = patch_over_a_file(&repo, "ambiguity"); | 616 | let id = patch_over_a_file(&repo, "ambiguity"); |
| 572 | repo.run_ok(&[ | 617 | repo.run_ok(&[ |
| 573 | "patch", "comment", &id, "--file", "feature.txt", "--line", "1", "-b", "one", | 618 | "patch", |
| 619 | "comment", | ||
| 620 | &id, | ||
| 621 | "--file", | ||
| 622 | "feature.txt", | ||
| 623 | "--line", | ||
| 624 | "1", | ||
| 625 | "-b", | ||
| 626 | "one", | ||
| 574 | ]); | 627 | ]); |
| 575 | repo.run_ok(&[ | 628 | repo.run_ok(&[ |
| 576 | "patch", "comment", &id, "--file", "feature.txt", "--line", "1", "-b", "two", | 629 | "patch", |
| 630 | "comment", | ||
| 631 | &id, | ||
| 632 | "--file", | ||
| 633 | "feature.txt", | ||
| 634 | "--line", | ||
| 635 | "1", | ||
| 636 | "-b", | ||
| 637 | "two", | ||
| 577 | ]); | 638 | ]); |
| 578 | 639 | ||
| 579 | // The empty-ish prefix every comment shares. | 640 | // The empty-ish prefix every comment shares. |
| @@ -596,9 +657,17 @@ fn rendering_answers_does_not_write_to_the_dag() { | |||
| 596 | let (repo, id, comment) = rebased_patch_with_resolved_comment(); | 657 | let (repo, id, comment) = rebased_patch_with_resolved_comment(); |
| 597 | repo.run_ok(&["patch", "resolve", &id, &comment[..8]]); | 658 | repo.run_ok(&["patch", "resolve", &id, &comment[..8]]); |
| 598 | 659 | ||
| 599 | let before = repo.git(&["for-each-ref", "--format=%(refname) %(objectname)", "refs/collab"]); | 660 | let before = repo.git(&[ |
| 661 | "for-each-ref", | ||
| 662 | "--format=%(refname) %(objectname)", | ||
| 663 | "refs/collab", | ||
| 664 | ]); | ||
| 600 | repo.run_ok(&["patch", "diff", &id, "--answers", &comment[..8]]); | 665 | repo.run_ok(&["patch", "diff", &id, "--answers", &comment[..8]]); |
| 601 | let after = repo.git(&["for-each-ref", "--format=%(refname) %(objectname)", "refs/collab"]); | 666 | let after = repo.git(&[ |
| 667 | "for-each-ref", | ||
| 668 | "--format=%(refname) %(objectname)", | ||
| 669 | "refs/collab", | ||
| 670 | ]); | ||
| 602 | 671 | ||
| 603 | assert_eq!( | 672 | assert_eq!( |
| 604 | before, after, | 673 | before, after, |
tests/commit_link_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -8,9 +8,7 @@ use git_collab::state::IssueState; | |||
| 8 | use tempfile::TempDir; | 8 | use tempfile::TempDir; |
| 9 | 9 | ||
| 10 | mod common; | 10 | mod common; |
| 11 | use common::{ | 11 | use common::{add_commit_link, alice, init_repo, open_issue, test_signing_key, ScopedTestConfig}; |
| 12 | add_commit_link, alice, init_repo, open_issue, test_signing_key, ScopedTestConfig, | ||
| 13 | }; | ||
| 14 | 12 | ||
| 15 | /// Append a commit-link event with an explicitly chosen `clock` value, | 13 | /// Append a commit-link event with an explicitly chosen `clock` value, |
| 16 | /// bypassing `dag::append_event`'s automatic clock-bump. The new commit's | 14 | /// bypassing `dag::append_event`'s automatic clock-bump. The new commit's |
| @@ -53,8 +51,15 @@ fn append_commit_link_with_clock( | |||
| 53 | let sig = author_signature(author).unwrap(); | 51 | let sig = author_signature(author).unwrap(); |
| 54 | let parent_oid = repo.refname_to_id(ref_name).unwrap(); | 52 | let parent_oid = repo.refname_to_id(ref_name).unwrap(); |
| 55 | let parent = repo.find_commit(parent_oid).unwrap(); | 53 | let parent = repo.find_commit(parent_oid).unwrap(); |
| 56 | repo.commit(Some(ref_name), &sig, &sig, "issue.commit_link", &tree, &[&parent]) | 54 | repo.commit( |
| 57 | .unwrap() | 55 | Some(ref_name), |
| 56 | &sig, | ||
| 57 | &sig, | ||
| 58 | "issue.commit_link", | ||
| 59 | &tree, | ||
| 60 | &[&parent], | ||
| 61 | ) | ||
| 62 | .unwrap() | ||
| 58 | } | 63 | } |
| 59 | 64 | ||
| 60 | fn test_author() -> Author { | 65 | fn test_author() -> Author { |
| @@ -160,8 +165,7 @@ fn issue_state_dedups_commit_links_keeps_lower_clock_even_when_appended_later() | |||
| 160 | assert_eq!(issue.linked_commits[0].commit, target_sha); | 165 | assert_eq!(issue.linked_commits[0].commit, target_sha); |
| 161 | // The lower-clock event wins, even though it was appended later. | 166 | // The lower-clock event wins, even though it was appended later. |
| 162 | assert_eq!( | 167 | assert_eq!( |
| 163 | issue.linked_commits[0].event_author.name, | 168 | issue.linked_commits[0].event_author.name, "Bob", |
| 164 | "Bob", | ||
| 165 | "expected Bob's lower-clock event to win the tiebreak" | 169 | "expected Bob's lower-clock event to win the tiebreak" |
| 166 | ); | 170 | ); |
| 167 | } | 171 | } |
tests/commit_msg_hook_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -56,7 +56,8 @@ fn repo_with_hook_and_patch() -> (TestRepo, String) { | |||
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | fn full_id(repo: &TestRepo, short: &str) -> String { | 58 | fn full_id(repo: &TestRepo, short: &str) -> String { |
| 59 | let json: Value = serde_json::from_str(&repo.run_ok(&["patch", "show", short, "--json"])).unwrap(); | 59 | let json: Value = |
| 60 | serde_json::from_str(&repo.run_ok(&["patch", "show", short, "--json"])).unwrap(); | ||
| 60 | json["id"].as_str().unwrap().to_string() | 61 | json["id"].as_str().unwrap().to_string() |
| 61 | } | 62 | } |
| 62 | 63 | ||
| @@ -135,8 +136,13 @@ fn init_installs_the_commit_msg_hook() { | |||
| 135 | 136 | ||
| 136 | let path = hook_path(&repo); | 137 | let path = hook_path(&repo); |
| 137 | assert!(path.exists(), "init did not install a hook:\n{}", out); | 138 | assert!(path.exists(), "init did not install a hook:\n{}", out); |
| 138 | let mode = std::os::unix::fs::PermissionsExt::mode(&std::fs::metadata(&path).unwrap().permissions()); | 139 | let mode = |
| 139 | assert!(mode & 0o111 != 0, "hook is not executable (mode {:o})", mode); | 140 | std::os::unix::fs::PermissionsExt::mode(&std::fs::metadata(&path).unwrap().permissions()); |
| 141 | assert!( | ||
| 142 | mode & 0o111 != 0, | ||
| 143 | "hook is not executable (mode {:o})", | ||
| 144 | mode | ||
| 145 | ); | ||
| 140 | assert!( | 146 | assert!( |
| 141 | out.contains("commit-msg hook"), | 147 | out.contains("commit-msg hook"), |
| 142 | "init did not report the hook:\n{}", | 148 | "init did not report the hook:\n{}", |
| @@ -152,7 +158,10 @@ fn init_installs_the_hook_even_with_no_remotes() { | |||
| 152 | let repo = TestRepo::new("Alice", "alice@example.com"); | 158 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 153 | assert!(repo.git(&["remote"]).trim().is_empty()); | 159 | assert!(repo.git(&["remote"]).trim().is_empty()); |
| 154 | repo.run_ok(&["init"]); | 160 | repo.run_ok(&["init"]); |
| 155 | assert!(hook_path(&repo).exists(), "no hook in a repo with no remotes"); | 161 | assert!( |
| 162 | hook_path(&repo).exists(), | ||
| 163 | "no hook in a repo with no remotes" | ||
| 164 | ); | ||
| 156 | } | 165 | } |
| 157 | 166 | ||
| 158 | #[test] | 167 | #[test] |
| @@ -212,7 +221,8 @@ fn init_recognizes_a_foreign_hook_that_already_calls_git_collab() { | |||
| 212 | let repo = TestRepo::new("Alice", "alice@example.com"); | 221 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 213 | let path = hook_path(&repo); | 222 | let path = hook_path(&repo); |
| 214 | std::fs::create_dir_all(path.parent().unwrap()).unwrap(); | 223 | std::fs::create_dir_all(path.parent().unwrap()).unwrap(); |
| 215 | let theirs = "#!/bin/sh\ngit-collab hooks run-commit-msg \"$1\" >/dev/null 2>&1 || true\nexit 0\n"; | 224 | let theirs = |
| 225 | "#!/bin/sh\ngit-collab hooks run-commit-msg \"$1\" >/dev/null 2>&1 || true\nexit 0\n"; | ||
| 216 | std::fs::write(&path, theirs).unwrap(); | 226 | std::fs::write(&path, theirs).unwrap(); |
| 217 | 227 | ||
| 218 | let out = repo.run_ok(&["init"]); | 228 | let out = repo.run_ok(&["init"]); |
| @@ -287,7 +297,11 @@ fn hooks_status_reports_what_the_hook_would_stamp() { | |||
| 287 | // when something is wrong. This is the command that explains it. | 297 | // when something is wrong. This is the command that explains it. |
| 288 | let (repo, id) = repo_with_hook_and_patch(); | 298 | let (repo, id) = repo_with_hook_and_patch(); |
| 289 | let out = repo.run_ok(&["hooks", "status"]); | 299 | let out = repo.run_ok(&["hooks", "status"]); |
| 290 | assert!(out.contains("installed"), "status did not report installation:\n{}", out); | 300 | assert!( |
| 301 | out.contains("installed"), | ||
| 302 | "status did not report installation:\n{}", | ||
| 303 | out | ||
| 304 | ); | ||
| 291 | assert!( | 305 | assert!( |
| 292 | out.contains(&id[..8]), | 306 | out.contains(&id[..8]), |
| 293 | "status did not name the patch it would stamp:\n{}", | 307 | "status did not name the patch it would stamp:\n{}", |
| @@ -350,11 +364,7 @@ fn round_trip_message_already_ending_in_an_issue_trailer() { | |||
| 350 | // which silently breaks the commit-issue link the author wrote by hand. | 364 | // which silently breaks the commit-issue link the author wrote by hand. |
| 351 | let (repo, id) = repo_with_hook_and_patch(); | 365 | let (repo, id) = repo_with_hook_and_patch(); |
| 352 | let issue = repo.issue_open("something to fix"); | 366 | let issue = repo.issue_open("something to fix"); |
| 353 | let stored = commit_and_read_back( | 367 | let stored = commit_and_read_back(&repo, "a.txt", &format!("subject\n\nIssue: {}", issue)); |
| 354 | &repo, | ||
| 355 | "a.txt", | ||
| 356 | &format!("subject\n\nIssue: {}", issue), | ||
| 357 | ); | ||
| 358 | assert_round_trips(&stored, &id); | 368 | assert_round_trips(&stored, &id); |
| 359 | assert_eq!( | 369 | assert_eq!( |
| 360 | parse_trailers(&stored, ISSUE_TOKEN), | 370 | parse_trailers(&stored, ISSUE_TOKEN), |
| @@ -640,7 +650,10 @@ fn the_hook_exits_zero_when_the_message_file_is_missing() { | |||
| 640 | .current_dir(repo.dir.path()) | 650 | .current_dir(repo.dir.path()) |
| 641 | .output() | 651 | .output() |
| 642 | .unwrap(); | 652 | .unwrap(); |
| 643 | assert!(out.status.success(), "a missing message file failed the hook"); | 653 | assert!( |
| 654 | out.status.success(), | ||
| 655 | "a missing message file failed the hook" | ||
| 656 | ); | ||
| 644 | } | 657 | } |
| 645 | 658 | ||
| 646 | // --------------------------------------------------------------------------- | 659 | // --------------------------------------------------------------------------- |
| @@ -666,7 +679,11 @@ fn a_hook_stamped_commit_is_recorded_as_merged_by_sync() { | |||
| 666 | repo.git(&["checkout", "-b", "feature"]); | 679 | repo.git(&["checkout", "-b", "feature"]); |
| 667 | repo.commit_file("f.txt", "one", "first commit"); | 680 | repo.commit_file("f.txt", "one", "first commit"); |
| 668 | let out = repo.run_ok(&["patch", "create", "-t", "feature work", "-B", "feature"]); | 681 | let out = repo.run_ok(&["patch", "create", "-t", "feature work", "-B", "feature"]); |
| 669 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 682 | let short = out |
| 683 | .trim() | ||
| 684 | .strip_prefix("Created patch ") | ||
| 685 | .unwrap() | ||
| 686 | .to_string(); | ||
| 670 | 687 | ||
| 671 | // A commit made after the patch exists — the one the hook can stamp. | 688 | // A commit made after the patch exists — the one the hook can stamp. |
| 672 | repo.commit_file("f.txt", "two", "more work"); | 689 | repo.commit_file("f.txt", "two", "more work"); |
| @@ -683,7 +700,8 @@ fn a_hook_stamped_commit_is_recorded_as_merged_by_sync() { | |||
| 683 | repo.git(&["merge", "--no-ff", "-m", "merge feature", "feature"]); | 700 | repo.git(&["merge", "--no-ff", "-m", "merge feature", "feature"]); |
| 684 | repo.run_ok(&["sync"]); | 701 | repo.run_ok(&["sync"]); |
| 685 | 702 | ||
| 686 | let json: Value = serde_json::from_str(&repo.run_ok(&["patch", "show", &short, "--json"])).unwrap(); | 703 | let json: Value = |
| 704 | serde_json::from_str(&repo.run_ok(&["patch", "show", &short, "--json"])).unwrap(); | ||
| 687 | assert_eq!( | 705 | assert_eq!( |
| 688 | json["status"].as_str().unwrap().to_lowercase(), | 706 | json["status"].as_str().unwrap().to_lowercase(), |
| 689 | "merged", | 707 | "merged", |
tests/interdiff_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -434,10 +434,18 @@ fn an_auto_merging_replay_shows_only_the_authors_hunk_and_writes_nothing() { | |||
| 434 | let repo = TestRepo::new("Alice", "alice@example.com"); | 434 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 435 | repo.commit_file("both.txt", "a\nb\nc\nd\ne\nf\ng\n", "seed"); | 435 | repo.commit_file("both.txt", "a\nb\nc\nd\ne\nf\ng\n", "seed"); |
| 436 | repo.git(&["checkout", "-b", "feat-automerge"]); | 436 | repo.git(&["checkout", "-b", "feat-automerge"]); |
| 437 | repo.commit_file("both.txt", "a\nb\nc\nd\ne\nf\nGGG\n", "patch edits the tail"); | 437 | repo.commit_file( |
| 438 | "both.txt", | ||
| 439 | "a\nb\nc\nd\ne\nf\nGGG\n", | ||
| 440 | "patch edits the tail", | ||
| 441 | ); | ||
| 438 | let id = create_patch(&repo, "feat-automerge", "Auto-merging replay"); | 442 | let id = create_patch(&repo, "feat-automerge", "Auto-merging replay"); |
| 439 | repo.git(&["checkout", "main"]); | 443 | repo.git(&["checkout", "main"]); |
| 440 | repo.commit_file("both.txt", "AAA\nb\nc\nd\ne\nf\ng\n", "upstream edits the head"); | 444 | repo.commit_file( |
| 445 | "both.txt", | ||
| 446 | "AAA\nb\nc\nd\ne\nf\ng\n", | ||
| 447 | "upstream edits the head", | ||
| 448 | ); | ||
| 441 | repo.git(&["checkout", "feat-automerge"]); | 449 | repo.git(&["checkout", "feat-automerge"]); |
| 442 | repo.git(&["rebase", "main"]); | 450 | repo.git(&["rebase", "main"]); |
| 443 | repo.commit_file("both.txt", "AAA\nb\nc\nd\ne\nf\nHHH\n", "address review"); | 451 | repo.commit_file("both.txt", "AAA\nb\nc\nd\ne\nf\nHHH\n", "address review"); |
| @@ -449,12 +457,7 @@ fn an_auto_merging_replay_shows_only_the_authors_hunk_and_writes_nothing() { | |||
| 449 | let out = repo.run_ok(&["patch", "diff", &id, "--between", "1", "2"]); | 457 | let out = repo.run_ok(&["patch", "diff", &id, "--between", "1", "2"]); |
| 450 | let after = count_objects(&objects); | 458 | let after = count_objects(&objects); |
| 451 | 459 | ||
| 452 | assert_eq!( | 460 | assert_eq!(changed_files(&out), vec!["both.txt".to_string()], "{}", out); |
| 453 | changed_files(&out), | ||
| 454 | vec!["both.txt".to_string()], | ||
| 455 | "{}", | ||
| 456 | out | ||
| 457 | ); | ||
| 458 | assert!( | 461 | assert!( |
| 459 | out.contains("-GGG") && out.contains("+HHH"), | 462 | out.contains("-GGG") && out.contains("+HHH"), |
| 460 | "the author's hunk must be the one shown: {}", | 463 | "the author's hunk must be the one shown: {}", |
tests/issue_reopen_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -201,7 +201,11 @@ fn a_concurrent_close_and_reopen_converge_on_the_later_reopen() { | |||
| 201 | 201 | ||
| 202 | alice.run_ok(&["issue", "close", &short]); | 202 | alice.run_ok(&["issue", "close", &short]); |
| 203 | // Bob's extra tick, so his reopen is the strictly later event. | 203 | // Bob's extra tick, so his reopen is the strictly later event. |
| 204 | collab_in(&alice, &bob, &["issue", "comment", &short, "-b", "still here"]); | 204 | collab_in( |
| 205 | &alice, | ||
| 206 | &bob, | ||
| 207 | &["issue", "comment", &short, "-b", "still here"], | ||
| 208 | ); | ||
| 205 | collab_in(&alice, &bob, &["issue", "reopen", &short]); | 209 | collab_in(&alice, &bob, &["issue", "reopen", &short]); |
| 206 | 210 | ||
| 207 | alice.run_ok(&["sync"]); | 211 | alice.run_ok(&["sync"]); |
| @@ -255,7 +259,11 @@ fn both_clones_keep_every_concurrent_event() { | |||
| 255 | 259 | ||
| 256 | alice.run_ok(&["issue", "comment", &short, "-b", "from alice"]); | 260 | alice.run_ok(&["issue", "comment", &short, "-b", "from alice"]); |
| 257 | alice.run_ok(&["issue", "close", &short]); | 261 | alice.run_ok(&["issue", "close", &short]); |
| 258 | collab_in(&alice, &bob, &["issue", "comment", &short, "-b", "from bob"]); | 262 | collab_in( |
| 263 | &alice, | ||
| 264 | &bob, | ||
| 265 | &["issue", "comment", &short, "-b", "from bob"], | ||
| 266 | ); | ||
| 259 | collab_in(&alice, &bob, &["issue", "reopen", &short]); | 267 | collab_in(&alice, &bob, &["issue", "reopen", &short]); |
| 260 | 268 | ||
| 261 | alice.run_ok(&["sync"]); | 269 | alice.run_ok(&["sync"]); |
tests/merge_recording_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -57,7 +57,11 @@ fn patch_with_trailer(repo: &TestRepo, branch: &str, file: &str) -> String { | |||
| 57 | repo.git(&["checkout", "-b", branch]); | 57 | repo.git(&["checkout", "-b", branch]); |
| 58 | repo.commit_file(file, "content", &format!("work on {}", branch)); | 58 | repo.commit_file(file, "content", &format!("work on {}", branch)); |
| 59 | let out = repo.run_ok(&["patch", "create", "-t", branch, "-B", branch]); | 59 | let out = repo.run_ok(&["patch", "create", "-t", branch, "-B", branch]); |
| 60 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 60 | let short = out |
| 61 | .trim() | ||
| 62 | .strip_prefix("Created patch ") | ||
| 63 | .unwrap() | ||
| 64 | .to_string(); | ||
| 61 | stamp_head(repo, &short); | 65 | stamp_head(repo, &short); |
| 62 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); | 66 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); |
| 63 | repo.git(&["checkout", "main"]); | 67 | repo.git(&["checkout", "main"]); |
| @@ -87,7 +91,11 @@ fn count_events(repo: &TestRepo, ref_name: &str) -> usize { | |||
| 87 | } | 91 | } |
| 88 | 92 | ||
| 89 | fn patch_events_ref(repo: &TestRepo, short: &str) -> String { | 93 | fn patch_events_ref(repo: &TestRepo, short: &str) -> String { |
| 90 | let out = repo.git(&["for-each-ref", "--format=%(refname)", "refs/collab/patches/"]); | 94 | let out = repo.git(&[ |
| 95 | "for-each-ref", | ||
| 96 | "--format=%(refname)", | ||
| 97 | "refs/collab/patches/", | ||
| 98 | ]); | ||
| 91 | out.lines() | 99 | out.lines() |
| 92 | .find(|r| r.contains(short) && r.ends_with("/events")) | 100 | .find(|r| r.contains(short) && r.ends_with("/events")) |
| 93 | .unwrap_or_else(|| panic!("no events ref for {} in {}", short, out)) | 101 | .unwrap_or_else(|| panic!("no events ref for {} in {}", short, out)) |
| @@ -111,7 +119,11 @@ fn patch_fixing_issue(repo: &TestRepo, branch: &str, file: &str, issue: &str) -> | |||
| 111 | let out = repo.run_ok(&[ | 119 | let out = repo.run_ok(&[ |
| 112 | "patch", "create", "-t", branch, "-B", branch, "--fixes", issue, | 120 | "patch", "create", "-t", branch, "-B", branch, "--fixes", issue, |
| 113 | ]); | 121 | ]); |
| 114 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 122 | let short = out |
| 123 | .trim() | ||
| 124 | .strip_prefix("Created patch ") | ||
| 125 | .unwrap() | ||
| 126 | .to_string(); | ||
| 115 | stamp_head(repo, &short); | 127 | stamp_head(repo, &short); |
| 116 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); | 128 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); |
| 117 | repo.git(&["checkout", "main"]); | 129 | repo.git(&["checkout", "main"]); |
| @@ -215,7 +227,11 @@ fn patch_merge_records_the_merge() { | |||
| 215 | repo.git(&["checkout", "-b", "feat"]); | 227 | repo.git(&["checkout", "-b", "feat"]); |
| 216 | repo.commit_file("a.txt", "x", "the patch"); | 228 | repo.commit_file("a.txt", "x", "the patch"); |
| 217 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 229 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 218 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 230 | let short = out |
| 231 | .trim() | ||
| 232 | .strip_prefix("Created patch ") | ||
| 233 | .unwrap() | ||
| 234 | .to_string(); | ||
| 219 | repo.git(&["checkout", "main"]); | 235 | repo.git(&["checkout", "main"]); |
| 220 | repo.git(&["merge", "--ff-only", "feat"]); | 236 | repo.git(&["merge", "--ff-only", "feat"]); |
| 221 | 237 | ||
| @@ -231,7 +247,11 @@ fn patch_merge_records_the_base_tip_as_the_landing_commit() { | |||
| 231 | repo.git(&["checkout", "-b", "feat"]); | 247 | repo.git(&["checkout", "-b", "feat"]); |
| 232 | repo.commit_file("a.txt", "x", "the patch"); | 248 | repo.commit_file("a.txt", "x", "the patch"); |
| 233 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 249 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 234 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 250 | let short = out |
| 251 | .trim() | ||
| 252 | .strip_prefix("Created patch ") | ||
| 253 | .unwrap() | ||
| 254 | .to_string(); | ||
| 235 | repo.git(&["checkout", "main"]); | 255 | repo.git(&["checkout", "main"]); |
| 236 | squash_merge_keeping_message(&repo, "feat"); | 256 | squash_merge_keeping_message(&repo, "feat"); |
| 237 | let landed = repo.git(&["rev-parse", "main"]).trim().to_string(); | 257 | let landed = repo.git(&["rev-parse", "main"]).trim().to_string(); |
| @@ -255,7 +275,11 @@ fn merged_state_survives_branch_deletion_and_a_cold_cache() { | |||
| 255 | repo.git(&["checkout", "-b", "feat"]); | 275 | repo.git(&["checkout", "-b", "feat"]); |
| 256 | repo.commit_file("a.txt", "x", "the patch"); | 276 | repo.commit_file("a.txt", "x", "the patch"); |
| 257 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 277 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 258 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 278 | let short = out |
| 279 | .trim() | ||
| 280 | .strip_prefix("Created patch ") | ||
| 281 | .unwrap() | ||
| 282 | .to_string(); | ||
| 259 | repo.git(&["checkout", "main"]); | 283 | repo.git(&["checkout", "main"]); |
| 260 | repo.git(&["merge", "--ff-only", "feat"]); | 284 | repo.git(&["merge", "--ff-only", "feat"]); |
| 261 | repo.run_ok(&["patch", "merge", &short]); | 285 | repo.run_ok(&["patch", "merge", &short]); |
| @@ -275,7 +299,11 @@ fn patch_merge_twice_records_nothing_the_second_time() { | |||
| 275 | repo.git(&["checkout", "-b", "feat"]); | 299 | repo.git(&["checkout", "-b", "feat"]); |
| 276 | repo.commit_file("a.txt", "x", "the patch"); | 300 | repo.commit_file("a.txt", "x", "the patch"); |
| 277 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 301 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 278 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 302 | let short = out |
| 303 | .trim() | ||
| 304 | .strip_prefix("Created patch ") | ||
| 305 | .unwrap() | ||
| 306 | .to_string(); | ||
| 279 | repo.git(&["checkout", "main"]); | 307 | repo.git(&["checkout", "main"]); |
| 280 | repo.git(&["merge", "--ff-only", "feat"]); | 308 | repo.git(&["merge", "--ff-only", "feat"]); |
| 281 | 309 | ||
| @@ -305,7 +333,11 @@ fn patch_merge_closes_the_fixed_issue() { | |||
| 305 | let out = repo.run_ok(&[ | 333 | let out = repo.run_ok(&[ |
| 306 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, | 334 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, |
| 307 | ]); | 335 | ]); |
| 308 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 336 | let short = out |
| 337 | .trim() | ||
| 338 | .strip_prefix("Created patch ") | ||
| 339 | .unwrap() | ||
| 340 | .to_string(); | ||
| 309 | repo.git(&["checkout", "main"]); | 341 | repo.git(&["checkout", "main"]); |
| 310 | repo.git(&["merge", "--ff-only", "feat"]); | 342 | repo.git(&["merge", "--ff-only", "feat"]); |
| 311 | 343 | ||
| @@ -490,7 +522,11 @@ fn fixes_closes_the_issue_exactly_once_across_repeated_syncs() { | |||
| 490 | let out = repo.run_ok(&[ | 522 | let out = repo.run_ok(&[ |
| 491 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, | 523 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, |
| 492 | ]); | 524 | ]); |
| 493 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 525 | let short = out |
| 526 | .trim() | ||
| 527 | .strip_prefix("Created patch ") | ||
| 528 | .unwrap() | ||
| 529 | .to_string(); | ||
| 494 | stamp_head(&repo, &short); | 530 | stamp_head(&repo, &short); |
| 495 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); | 531 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); |
| 496 | repo.git(&["checkout", "main"]); | 532 | repo.git(&["checkout", "main"]); |
| @@ -530,7 +566,11 @@ fn a_fixes_issue_that_is_already_closed_gets_no_further_close() { | |||
| 530 | let out = repo.run_ok(&[ | 566 | let out = repo.run_ok(&[ |
| 531 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, | 567 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, |
| 532 | ]); | 568 | ]); |
| 533 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 569 | let short = out |
| 570 | .trim() | ||
| 571 | .strip_prefix("Created patch ") | ||
| 572 | .unwrap() | ||
| 573 | .to_string(); | ||
| 534 | stamp_head(&repo, &short); | 574 | stamp_head(&repo, &short); |
| 535 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); | 575 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); |
| 536 | repo.git(&["checkout", "main"]); | 576 | repo.git(&["checkout", "main"]); |
| @@ -575,7 +615,11 @@ fn a_close_that_did_not_happen_with_the_merge_is_retried_by_the_next_scan() { | |||
| 575 | let out = repo.run_ok(&[ | 615 | let out = repo.run_ok(&[ |
| 576 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, | 616 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, |
| 577 | ]); | 617 | ]); |
| 578 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 618 | let short = out |
| 619 | .trim() | ||
| 620 | .strip_prefix("Created patch ") | ||
| 621 | .unwrap() | ||
| 622 | .to_string(); | ||
| 579 | stamp_head(&repo, &short); | 623 | stamp_head(&repo, &short); |
| 580 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); | 624 | repo.run_ok(&["patch", "revise", &short, "-b", "stamped"]); |
| 581 | repo.git(&["checkout", "main"]); | 625 | repo.git(&["checkout", "main"]); |
| @@ -607,7 +651,11 @@ fn a_reachable_patch_with_no_merge_event_shows_as_merged_with_a_question_mark() | |||
| 607 | repo.git(&["checkout", "-b", "feat"]); | 651 | repo.git(&["checkout", "-b", "feat"]); |
| 608 | repo.commit_file("a.txt", "x", "the patch"); | 652 | repo.commit_file("a.txt", "x", "the patch"); |
| 609 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 653 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 610 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 654 | let short = out |
| 655 | .trim() | ||
| 656 | .strip_prefix("Created patch ") | ||
| 657 | .unwrap() | ||
| 658 | .to_string(); | ||
| 611 | repo.git(&["checkout", "main"]); | 659 | repo.git(&["checkout", "main"]); |
| 612 | repo.git(&["merge", "--ff-only", "feat"]); | 660 | repo.git(&["merge", "--ff-only", "feat"]); |
| 613 | 661 | ||
| @@ -631,7 +679,11 @@ fn displaying_a_reachable_patch_writes_no_event() { | |||
| 631 | repo.git(&["checkout", "-b", "feat"]); | 679 | repo.git(&["checkout", "-b", "feat"]); |
| 632 | repo.commit_file("a.txt", "x", "the patch"); | 680 | repo.commit_file("a.txt", "x", "the patch"); |
| 633 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 681 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 634 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 682 | let short = out |
| 683 | .trim() | ||
| 684 | .strip_prefix("Created patch ") | ||
| 685 | .unwrap() | ||
| 686 | .to_string(); | ||
| 635 | repo.git(&["checkout", "main"]); | 687 | repo.git(&["checkout", "main"]); |
| 636 | repo.git(&["merge", "--ff-only", "feat"]); | 688 | repo.git(&["merge", "--ff-only", "feat"]); |
| 637 | 689 | ||
| @@ -658,7 +710,11 @@ fn sync_names_the_patches_that_look_merged_but_are_not_recorded() { | |||
| 658 | repo.git(&["checkout", "-b", "feat"]); | 710 | repo.git(&["checkout", "-b", "feat"]); |
| 659 | repo.commit_file("a.txt", "x", "the patch"); | 711 | repo.commit_file("a.txt", "x", "the patch"); |
| 660 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 712 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 661 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 713 | let short = out |
| 714 | .trim() | ||
| 715 | .strip_prefix("Created patch ") | ||
| 716 | .unwrap() | ||
| 717 | .to_string(); | ||
| 662 | repo.git(&["checkout", "main"]); | 718 | repo.git(&["checkout", "main"]); |
| 663 | repo.git(&["merge", "--ff-only", "feat"]); | 719 | repo.git(&["merge", "--ff-only", "feat"]); |
| 664 | 720 | ||
| @@ -676,7 +732,11 @@ fn a_recorded_merge_is_not_reported_as_a_hint() { | |||
| 676 | repo.git(&["checkout", "-b", "feat"]); | 732 | repo.git(&["checkout", "-b", "feat"]); |
| 677 | repo.commit_file("a.txt", "x", "the patch"); | 733 | repo.commit_file("a.txt", "x", "the patch"); |
| 678 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 734 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 679 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 735 | let short = out |
| 736 | .trim() | ||
| 737 | .strip_prefix("Created patch ") | ||
| 738 | .unwrap() | ||
| 739 | .to_string(); | ||
| 680 | repo.git(&["checkout", "main"]); | 740 | repo.git(&["checkout", "main"]); |
| 681 | repo.git(&["merge", "--ff-only", "feat"]); | 741 | repo.git(&["merge", "--ff-only", "feat"]); |
| 682 | repo.run_ok(&["patch", "merge", &short]); | 742 | repo.run_ok(&["patch", "merge", &short]); |
| @@ -817,7 +877,11 @@ fn patch_merge_does_not_reclose_an_issue_reopened_after_its_merge() { | |||
| 817 | let out = repo.run_ok(&[ | 877 | let out = repo.run_ok(&[ |
| 818 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, | 878 | "patch", "create", "-t", "The fix", "-B", "feat", "--fixes", &issue, |
| 819 | ]); | 879 | ]); |
| 820 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 880 | let short = out |
| 881 | .trim() | ||
| 882 | .strip_prefix("Created patch ") | ||
| 883 | .unwrap() | ||
| 884 | .to_string(); | ||
| 821 | repo.git(&["checkout", "main"]); | 885 | repo.git(&["checkout", "main"]); |
| 822 | repo.git(&["merge", "--ff-only", "feat"]); | 886 | repo.git(&["merge", "--ff-only", "feat"]); |
| 823 | 887 | ||
| @@ -868,7 +932,11 @@ fn two_clones_recording_a_merge_concurrently_converge() { | |||
| 868 | alice.git(&["checkout", "-b", "feat"]); | 932 | alice.git(&["checkout", "-b", "feat"]); |
| 869 | alice.commit_file("a.txt", "x", "the patch"); | 933 | alice.commit_file("a.txt", "x", "the patch"); |
| 870 | let out = alice.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 934 | let out = alice.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 871 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 935 | let short = out |
| 936 | .trim() | ||
| 937 | .strip_prefix("Created patch ") | ||
| 938 | .unwrap() | ||
| 939 | .to_string(); | ||
| 872 | alice.git(&["checkout", "main"]); | 940 | alice.git(&["checkout", "main"]); |
| 873 | alice.git(&["merge", "--ff-only", "feat"]); | 941 | alice.git(&["merge", "--ff-only", "feat"]); |
| 874 | alice.git(&["push", "origin", "main"]); | 942 | alice.git(&["push", "origin", "main"]); |
| @@ -898,9 +966,12 @@ fn two_clones_recording_a_merge_concurrently_converge() { | |||
| 898 | collab_in(&alice, &bob, &["sync"]); | 966 | collab_in(&alice, &bob, &["sync"]); |
| 899 | alice.run_ok(&["sync"]); | 967 | alice.run_ok(&["sync"]); |
| 900 | 968 | ||
| 901 | let bob_json: Value = | 969 | let bob_json: Value = serde_json::from_str(&collab_in( |
| 902 | serde_json::from_str(&collab_in(&alice, &bob, &["patch", "show", &short, "--json"])) | 970 | &alice, |
| 903 | .unwrap(); | 971 | &bob, |
| 972 | &["patch", "show", &short, "--json"], | ||
| 973 | )) | ||
| 974 | .unwrap(); | ||
| 904 | assert_eq!(show_json(&alice, &short)["status"], "merged"); | 975 | assert_eq!(show_json(&alice, &short)["status"], "merged"); |
| 905 | assert_eq!(bob_json["status"], "merged"); | 976 | assert_eq!(bob_json["status"], "merged"); |
| 906 | 977 | ||
| @@ -922,7 +993,11 @@ fn json_reports_the_merge_hint_beside_the_status() { | |||
| 922 | repo.git(&["checkout", "-b", "feat"]); | 993 | repo.git(&["checkout", "-b", "feat"]); |
| 923 | repo.commit_file("a.txt", "x", "the patch"); | 994 | repo.commit_file("a.txt", "x", "the patch"); |
| 924 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 995 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 925 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 996 | let short = out |
| 997 | .trim() | ||
| 998 | .strip_prefix("Created patch ") | ||
| 999 | .unwrap() | ||
| 1000 | .to_string(); | ||
| 926 | repo.git(&["checkout", "main"]); | 1001 | repo.git(&["checkout", "main"]); |
| 927 | 1002 | ||
| 928 | // Not merged: no hint at all, so its absence never reads as an assertion. | 1003 | // Not merged: no hint at all, so its absence never reads as an assertion. |
| @@ -964,7 +1039,11 @@ fn the_recorded_merge_commit_reaches_show_json_and_patch_log() { | |||
| 964 | repo.git(&["checkout", "-b", "feat"]); | 1039 | repo.git(&["checkout", "-b", "feat"]); |
| 965 | repo.commit_file("a.txt", "x", "the patch"); | 1040 | repo.commit_file("a.txt", "x", "the patch"); |
| 966 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 1041 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 967 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 1042 | let short = out |
| 1043 | .trim() | ||
| 1044 | .strip_prefix("Created patch ") | ||
| 1045 | .unwrap() | ||
| 1046 | .to_string(); | ||
| 968 | repo.git(&["checkout", "main"]); | 1047 | repo.git(&["checkout", "main"]); |
| 969 | squash_merge_keeping_message(&repo, "feat"); | 1048 | squash_merge_keeping_message(&repo, "feat"); |
| 970 | let landed = repo.git(&["rev-parse", "main"]).trim().to_string(); | 1049 | let landed = repo.git(&["rev-parse", "main"]).trim().to_string(); |
| @@ -1014,7 +1093,11 @@ fn displaying_a_merged_patch_writes_no_event() { | |||
| 1014 | repo.git(&["checkout", "-b", "feat"]); | 1093 | repo.git(&["checkout", "-b", "feat"]); |
| 1015 | repo.commit_file("a.txt", "x", "the patch"); | 1094 | repo.commit_file("a.txt", "x", "the patch"); |
| 1016 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 1095 | let out = repo.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 1017 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 1096 | let short = out |
| 1097 | .trim() | ||
| 1098 | .strip_prefix("Created patch ") | ||
| 1099 | .unwrap() | ||
| 1100 | .to_string(); | ||
| 1018 | repo.git(&["checkout", "main"]); | 1101 | repo.git(&["checkout", "main"]); |
| 1019 | repo.git(&["merge", "--ff-only", "feat"]); | 1102 | repo.git(&["merge", "--ff-only", "feat"]); |
| 1020 | repo.run_ok(&["patch", "merge", &short]); | 1103 | repo.run_ok(&["patch", "merge", &short]); |
| @@ -1048,7 +1131,11 @@ fn two_clones_recording_different_merge_commits_agree_on_one() { | |||
| 1048 | alice.git(&["checkout", "-b", "feat"]); | 1131 | alice.git(&["checkout", "-b", "feat"]); |
| 1049 | alice.commit_file("a.txt", "x", "the patch"); | 1132 | alice.commit_file("a.txt", "x", "the patch"); |
| 1050 | let out = alice.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); | 1133 | let out = alice.run_ok(&["patch", "create", "-t", "Feature", "-B", "feat"]); |
| 1051 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 1134 | let short = out |
| 1135 | .trim() | ||
| 1136 | .strip_prefix("Created patch ") | ||
| 1137 | .unwrap() | ||
| 1138 | .to_string(); | ||
| 1052 | alice.git(&["checkout", "main"]); | 1139 | alice.git(&["checkout", "main"]); |
| 1053 | alice.git(&["merge", "--ff-only", "feat"]); | 1140 | alice.git(&["merge", "--ff-only", "feat"]); |
| 1054 | let first = alice.git(&["rev-parse", "main"]).trim().to_string(); | 1141 | let first = alice.git(&["rev-parse", "main"]).trim().to_string(); |
tests/mutating_json_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -55,7 +55,8 @@ fn assert_full_id(value: &Value, what: &str) { | |||
| 55 | s.len() | 55 | s.len() |
| 56 | ); | 56 | ); |
| 57 | assert!( | 57 | assert!( |
| 58 | s.chars().all(|c| c.is_ascii_hexdigit() && !c.is_uppercase()), | 58 | s.chars() |
| 59 | .all(|c| c.is_ascii_hexdigit() && !c.is_uppercase()), | ||
| 59 | "{} must be a hex object name, got {:?}", | 60 | "{} must be a hex object name, got {:?}", |
| 60 | what, | 61 | what, |
| 61 | s | 62 | s |
| @@ -127,7 +128,15 @@ fn patch_create_json_carries_the_fixed_issue_in_full() { | |||
| 127 | let json = json_ok( | 128 | let json = json_ok( |
| 128 | &repo, | 129 | &repo, |
| 129 | &[ | 130 | &[ |
| 130 | "patch", "create", "-t", "Fix", "-B", "feat", "--fixes", &issue[..8], "--json", | 131 | "patch", |
| 132 | "create", | ||
| 133 | "-t", | ||
| 134 | "Fix", | ||
| 135 | "-B", | ||
| 136 | "feat", | ||
| 137 | "--fixes", | ||
| 138 | &issue[..8], | ||
| 139 | "--json", | ||
| 131 | ], | 140 | ], |
| 132 | ); | 141 | ); |
| 133 | assert_full_id(&json["patch"], "patch"); | 142 | assert_full_id(&json["patch"], "patch"); |
| @@ -199,13 +208,7 @@ fn every_issue_mutation_reports_the_issue_in_full() { | |||
| 199 | 208 | ||
| 200 | let deleted = json_ok( | 209 | let deleted = json_ok( |
| 201 | &repo, | 210 | &repo, |
| 202 | &[ | 211 | &["issue", "delete-comment", short, &comment_id[..8], "--json"], |
| 203 | "issue", | ||
| 204 | "delete-comment", | ||
| 205 | short, | ||
| 206 | &comment_id[..8], | ||
| 207 | "--json", | ||
| 208 | ], | ||
| 209 | ); | 212 | ); |
| 210 | assert_eq!(deleted["action"], "issue.delete_comment"); | 213 | assert_eq!(deleted["action"], "issue.delete_comment"); |
| 211 | assert_eq!(deleted["comment"], comment_id); | 214 | assert_eq!(deleted["comment"], comment_id); |
| @@ -222,7 +225,8 @@ fn every_issue_mutation_reports_the_issue_in_full() { | |||
| 222 | let deleted = json_ok(&repo, &["issue", "delete", short, "--json"]); | 225 | let deleted = json_ok(&repo, &["issue", "delete", short, "--json"]); |
| 223 | assert_eq!(deleted["action"], "issue.delete"); | 226 | assert_eq!(deleted["action"], "issue.delete"); |
| 224 | assert_eq!( | 227 | assert_eq!( |
| 225 | deleted["issue"], id.as_str(), | 228 | deleted["issue"], |
| 229 | id.as_str(), | ||
| 226 | "the id of something deleted is exactly what it was" | 230 | "the id of something deleted is exactly what it was" |
| 227 | ); | 231 | ); |
| 228 | } | 232 | } |
| @@ -267,7 +271,9 @@ fn every_patch_mutation_reports_the_patch_in_full() { | |||
| 267 | 271 | ||
| 268 | let review = json_ok( | 272 | let review = json_ok( |
| 269 | &repo, | 273 | &repo, |
| 270 | &["patch", "review", short, "-v", "approve", "-b", "lgtm", "--json"], | 274 | &[ |
| 275 | "patch", "review", short, "-v", "approve", "-b", "lgtm", "--json", | ||
| 276 | ], | ||
| 271 | ); | 277 | ); |
| 272 | assert_eq!(review["action"], "patch.review"); | 278 | assert_eq!(review["action"], "patch.review"); |
| 273 | assert_eq!(review["patch"], id.as_str()); | 279 | assert_eq!(review["patch"], id.as_str()); |
| @@ -317,7 +323,14 @@ fn every_patch_mutation_reports_the_patch_in_full() { | |||
| 317 | let revised = json_ok( | 323 | let revised = json_ok( |
| 318 | &repo, | 324 | &repo, |
| 319 | &[ | 325 | &[ |
| 320 | "patch", "revise", short, "-B", "feat", "-b", "round two", "--json", | 326 | "patch", |
| 327 | "revise", | ||
| 328 | short, | ||
| 329 | "-B", | ||
| 330 | "feat", | ||
| 331 | "-b", | ||
| 332 | "round two", | ||
| 333 | "--json", | ||
| 321 | ], | 334 | ], |
| 322 | ); | 335 | ); |
| 323 | assert_eq!(revised["action"], "patch.revision"); | 336 | assert_eq!(revised["action"], "patch.revision"); |
| @@ -328,7 +341,15 @@ fn every_patch_mutation_reports_the_patch_in_full() { | |||
| 328 | 341 | ||
| 329 | let edited_rev = json_ok( | 342 | let edited_rev = json_ok( |
| 330 | &repo, | 343 | &repo, |
| 331 | &["patch", "edit-revision", short, "2", "-b", "fixed", "--json"], | 344 | &[ |
| 345 | "patch", | ||
| 346 | "edit-revision", | ||
| 347 | short, | ||
| 348 | "2", | ||
| 349 | "-b", | ||
| 350 | "fixed", | ||
| 351 | "--json", | ||
| 352 | ], | ||
| 332 | ); | 353 | ); |
| 333 | assert_eq!(edited_rev["action"], "patch.edit_revision"); | 354 | assert_eq!(edited_rev["action"], "patch.edit_revision"); |
| 334 | assert_eq!(edited_rev["patch"], id.as_str()); | 355 | assert_eq!(edited_rev["patch"], id.as_str()); |
| @@ -366,7 +387,15 @@ fn patch_merge_json_names_the_patch_the_commit_and_the_issue_it_closed() { | |||
| 366 | let created = json_ok( | 387 | let created = json_ok( |
| 367 | &repo, | 388 | &repo, |
| 368 | &[ | 389 | &[ |
| 369 | "patch", "create", "-t", "Fix", "-B", "feat", "--fixes", &issue[..8], "--json", | 390 | "patch", |
| 391 | "create", | ||
| 392 | "-t", | ||
| 393 | "Fix", | ||
| 394 | "-B", | ||
| 395 | "feat", | ||
| 396 | "--fixes", | ||
| 397 | &issue[..8], | ||
| 398 | "--json", | ||
| 370 | ], | 399 | ], |
| 371 | ); | 400 | ); |
| 372 | let id = created["patch"].as_str().unwrap().to_string(); | 401 | let id = created["patch"].as_str().unwrap().to_string(); |
| @@ -422,7 +451,10 @@ fn patch_reopen_json_reports_the_merge_commit_it_cleared() { | |||
| 422 | fn key_and_identity_mutations_report_what_they_changed() { | 451 | fn key_and_identity_mutations_report_what_they_changed() { |
| 423 | let repo = TestRepo::new("Alice", "alice@example.com"); | 452 | let repo = TestRepo::new("Alice", "alice@example.com"); |
| 424 | 453 | ||
| 425 | let added = json_ok(&repo, &["key", "add", "--self", "--label", "mine", "--json"]); | 454 | let added = json_ok( |
| 455 | &repo, | ||
| 456 | &["key", "add", "--self", "--label", "mine", "--json"], | ||
| 457 | ); | ||
| 426 | assert_eq!(added["action"], "key.add"); | 458 | assert_eq!(added["action"], "key.add"); |
| 427 | assert_eq!(added["added"], true); | 459 | assert_eq!(added["added"], true); |
| 428 | assert_eq!(added["label"], "mine"); | 460 | assert_eq!(added["label"], "mine"); |
tests/patch_reopen_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -66,7 +66,11 @@ fn patch_on_branch(repo: &TestRepo, branch: &str, file: &str) -> String { | |||
| 66 | repo.git(&["checkout", "-b", branch]); | 66 | repo.git(&["checkout", "-b", branch]); |
| 67 | repo.commit_file(file, "content", &format!("work on {}", branch)); | 67 | repo.commit_file(file, "content", &format!("work on {}", branch)); |
| 68 | let out = repo.run_ok(&["patch", "create", "-t", branch, "-B", branch]); | 68 | let out = repo.run_ok(&["patch", "create", "-t", branch, "-B", branch]); |
| 69 | let short = out.trim().strip_prefix("Created patch ").unwrap().to_string(); | 69 | let short = out |
| 70 | .trim() | ||
| 71 | .strip_prefix("Created patch ") | ||
| 72 | .unwrap() | ||
| 73 | .to_string(); | ||
| 70 | repo.git(&["checkout", "main"]); | 74 | repo.git(&["checkout", "main"]); |
| 71 | short | 75 | short |
| 72 | } | 76 | } |
| @@ -298,9 +302,12 @@ fn a_concurrent_close_and_reopen_converge() { | |||
| 298 | alice.run_ok(&["sync"]); | 302 | alice.run_ok(&["sync"]); |
| 299 | 303 | ||
| 300 | let alice_status = show_json(&alice, &short)["status"].clone(); | 304 | let alice_status = show_json(&alice, &short)["status"].clone(); |
| 301 | let bob_json: Value = | 305 | let bob_json: Value = serde_json::from_str(&collab_in( |
| 302 | serde_json::from_str(&collab_in(&alice, &bob, &["patch", "show", &short, "--json"])) | 306 | &alice, |
| 303 | .unwrap(); | 307 | &bob, |
| 308 | &["patch", "show", &short, "--json"], | ||
| 309 | )) | ||
| 310 | .unwrap(); | ||
| 304 | assert_eq!( | 311 | assert_eq!( |
| 305 | alice_status, bob_json["status"], | 312 | alice_status, bob_json["status"], |
| 306 | "a close and a reopen that never saw each other must still land on one answer" | 313 | "a close and a reopen that never saw each other must still land on one answer" |
tests/release_cli_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -388,7 +388,8 @@ fn assert_full_sha256(value: &serde_json::Value, expected: &str) { | |||
| 388 | s.len() | 388 | s.len() |
| 389 | ); | 389 | ); |
| 390 | assert!( | 390 | assert!( |
| 391 | s.chars().all(|c| c.is_ascii_hexdigit() && !c.is_uppercase()), | 391 | s.chars() |
| 392 | .all(|c| c.is_ascii_hexdigit() && !c.is_uppercase()), | ||
| 392 | "sha256 must be lowercase hex, got {:?}", | 393 | "sha256 must be lowercase hex, got {:?}", |
| 393 | s | 394 | s |
| 394 | ); | 395 | ); |
| @@ -562,7 +563,10 @@ fn a_failing_delete_prints_the_error_object_on_stdout() { | |||
| 562 | 563 | ||
| 563 | // Rejected client-side, before any network round trip: the same contract | 564 | // Rejected client-side, before any network round trip: the same contract |
| 564 | // has to hold on the path that never reaches the server. | 565 | // has to hold on the path that never reaches the server. |
| 565 | let bad = release_cmd(&harness, &["delete", "../evil", "--remote", "srv", "--json"]); | 566 | let bad = release_cmd( |
| 567 | &harness, | ||
| 568 | &["delete", "../evil", "--remote", "srv", "--json"], | ||
| 569 | ); | ||
| 566 | let message = json_err(&bad, "deleting an invalid version with --json"); | 570 | let message = json_err(&bad, "deleting an invalid version with --json"); |
| 567 | assert!( | 571 | assert!( |
| 568 | message.contains("invalid version"), | 572 | message.contains("invalid version"), |
tests/revision_refs_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -361,7 +361,8 @@ fn merging_the_base_branch_forward_makes_the_patch_look_merged() { | |||
| 361 | 361 | ||
| 362 | assert!(looks_merged(&repo, &short)); | 362 | assert!(looks_merged(&repo, &short)); |
| 363 | assert_eq!( | 363 | assert_eq!( |
| 364 | show_json(&repo, &short)["status"], "open", | 364 | show_json(&repo, &short)["status"], |
| 365 | "open", | ||
| 365 | "the hint is not the status" | 366 | "the hint is not the status" |
| 366 | ); | 367 | ); |
| 367 | } | 368 | } |
tests/server_behavior_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -187,7 +187,11 @@ fn readme_md_renders_on_repo_overview_page() { | |||
| 187 | harness.push_head(); | 187 | harness.push_head(); |
| 188 | 188 | ||
| 189 | let overview = harness.get_ok(&format!("/{}", harness.repo_name())); | 189 | let overview = harness.get_ok(&format!("/{}", harness.repo_name())); |
| 190 | assert!(overview.body.contains("<h1>Hello World</h1>"), "missing h1: {}", overview.body); | 190 | assert!( |
| 191 | overview.body.contains("<h1>Hello World</h1>"), | ||
| 192 | "missing h1: {}", | ||
| 193 | overview.body | ||
| 194 | ); | ||
| 191 | assert!(overview.body.contains("href=\"https://example.com\"")); | 195 | assert!(overview.body.contains("href=\"https://example.com\"")); |
| 192 | // Sanity: the new card wrapper exists. | 196 | // Sanity: the new card wrapper exists. |
| 193 | assert!(overview.body.contains("class=\"readme-body\"")); | 197 | assert!(overview.body.contains("class=\"readme-body\"")); |
| @@ -197,11 +201,9 @@ fn readme_md_renders_on_repo_overview_page() { | |||
| 197 | fn missing_readme_does_not_break_overview_page() { | 201 | fn missing_readme_does_not_break_overview_page() { |
| 198 | let harness = ServerHarness::new("behavior-no-readme"); | 202 | let harness = ServerHarness::new("behavior-no-readme"); |
| 199 | 203 | ||
| 200 | harness.work_repo().commit_file( | 204 | harness |
| 201 | "src/lib.rs", | 205 | .work_repo() |
| 202 | "pub fn x() {}\n", | 206 | .commit_file("src/lib.rs", "pub fn x() {}\n", "add lib"); |
| 203 | "add lib", | ||
| 204 | ); | ||
| 205 | harness.push_head(); | 207 | harness.push_head(); |
| 206 | 208 | ||
| 207 | let overview = harness.get_ok(&format!("/{}", harness.repo_name())); | 209 | let overview = harness.get_ok(&format!("/{}", harness.repo_name())); |
tests/sync_diagnostics_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -171,8 +171,11 @@ fn ordinary_push_failure_still_gets_retry_advice() { | |||
| 171 | // ref layout, and clearing it does make a retry succeed. | 171 | // ref layout, and clearing it does make a retry succeed. |
| 172 | let lock_dir = bare.path().join("refs/collab/patches").join(&id); | 172 | let lock_dir = bare.path().join("refs/collab/patches").join(&id); |
| 173 | std::fs::create_dir_all(&lock_dir).unwrap(); | 173 | std::fs::create_dir_all(&lock_dir).unwrap(); |
| 174 | std::fs::write(lock_dir.join("events.lock"), "0000000000000000000000000000000000000000\n") | 174 | std::fs::write( |
| 175 | .unwrap(); | 175 | lock_dir.join("events.lock"), |
| 176 | "0000000000000000000000000000000000000000\n", | ||
| 177 | ) | ||
| 178 | .unwrap(); | ||
| 176 | 179 | ||
| 177 | let stderr = repo.run_err(&["sync", "--remote", "origin"]); | 180 | let stderr = repo.run_err(&["sync", "--remote", "origin"]); |
| 178 | 181 | ||
tests/sync_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -502,12 +502,7 @@ fn test_auto_sync_remote_config_pins_to_single_remote() { | |||
| 502 | fn add_unreachable_remote(repo_dir: &std::path::Path, remote_name: &str) { | 502 | fn add_unreachable_remote(repo_dir: &std::path::Path, remote_name: &str) { |
| 503 | let missing = repo_dir.join("no-such-remote.git"); | 503 | let missing = repo_dir.join("no-such-remote.git"); |
| 504 | Command::new("git") | 504 | Command::new("git") |
| 505 | .args([ | 505 | .args(["remote", "add", remote_name, missing.to_str().unwrap()]) |
| 506 | "remote", | ||
| 507 | "add", | ||
| 508 | remote_name, | ||
| 509 | missing.to_str().unwrap(), | ||
| 510 | ]) | ||
| 511 | .current_dir(repo_dir) | 506 | .current_dir(repo_dir) |
| 512 | .status() | 507 | .status() |
| 513 | .unwrap(); | 508 | .unwrap(); |
| @@ -584,11 +579,7 @@ fn test_auto_sync_output_stays_off_stdout() { | |||
| 584 | let stderr = String::from_utf8(output.stderr).unwrap(); | 579 | let stderr = String::from_utf8(output.stderr).unwrap(); |
| 585 | 580 | ||
| 586 | // stdout is the command's own result, whole and alone. | 581 | // stdout is the command's own result, whole and alone. |
| 587 | assert!( | 582 | assert!(stdout.starts_with("Opened issue "), "stdout: {:?}", stdout); |
| 588 | stdout.starts_with("Opened issue "), | ||
| 589 | "stdout: {:?}", | ||
| 590 | stdout | ||
| 591 | ); | ||
| 592 | assert_eq!( | 583 | assert_eq!( |
| 593 | stdout.lines().count(), | 584 | stdout.lines().count(), |
| 594 | 1, | 585 | 1, |
| @@ -596,7 +587,12 @@ fn test_auto_sync_output_stays_off_stdout() { | |||
| 596 | stdout, | 587 | stdout, |
| 597 | stderr | 588 | stderr |
| 598 | ); | 589 | ); |
| 599 | for noise in ["Fetching from", "Pushing to", "Sync complete", "Pushed refs/"] { | 590 | for noise in [ |
| 591 | "Fetching from", | ||
| 592 | "Pushing to", | ||
| 593 | "Sync complete", | ||
| 594 | "Pushed refs/", | ||
| 595 | ] { | ||
| 600 | assert!( | 596 | assert!( |
| 601 | !stdout.contains(noise), | 597 | !stdout.contains(noise), |
| 602 | "{:?} is auto-sync's output, not the command's:\nstdout: {}", | 598 | "{:?} is auto-sync's output, not the command's:\nstdout: {}", |
| @@ -1635,10 +1631,7 @@ fn commit_link_scan_emits_event_for_matching_trailer() { | |||
| 1635 | 1631 | ||
| 1636 | // Run the scanner directly (we test the sync integration in later tests). | 1632 | // Run the scanner directly (we test the sync integration in later tests). |
| 1637 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); | 1633 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); |
| 1638 | let sk = signing::load_signing_key( | 1634 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); |
| 1639 | &signing::signing_key_dir().unwrap(), | ||
| 1640 | ) | ||
| 1641 | .unwrap(); | ||
| 1642 | let emitted = commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(); | 1635 | let emitted = commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(); |
| 1643 | assert_eq!(emitted, 1); | 1636 | assert_eq!(emitted, 1); |
| 1644 | 1637 | ||
| @@ -1684,9 +1677,18 @@ fn commit_link_scan_is_idempotent_across_runs() { | |||
| 1684 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); | 1677 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); |
| 1685 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); | 1678 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); |
| 1686 | 1679 | ||
| 1687 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 1); | 1680 | assert_eq!( |
| 1688 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 0); | 1681 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), |
| 1689 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 0); | 1682 | 1 |
| 1683 | ); | ||
| 1684 | assert_eq!( | ||
| 1685 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), | ||
| 1686 | 0 | ||
| 1687 | ); | ||
| 1688 | assert_eq!( | ||
| 1689 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), | ||
| 1690 | 0 | ||
| 1691 | ); | ||
| 1690 | 1692 | ||
| 1691 | let issue = IssueState::from_ref_uncached(&alice_repo, &issue_ref, &issue_id).unwrap(); | 1693 | let issue = IssueState::from_ref_uncached(&alice_repo, &issue_ref, &issue_id).unwrap(); |
| 1692 | assert_eq!(issue.linked_commits.len(), 1); | 1694 | assert_eq!(issue.linked_commits.len(), 1); |
| @@ -1713,7 +1715,10 @@ fn commit_link_scan_walks_all_local_branches_and_dedups_shared_ancestors() { | |||
| 1713 | 1715 | ||
| 1714 | // Should emit exactly one event despite the commit being reachable from | 1716 | // Should emit exactly one event despite the commit being reachable from |
| 1715 | // two branch tips. | 1717 | // two branch tips. |
| 1716 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 1); | 1718 | assert_eq!( |
| 1719 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), | ||
| 1720 | 1 | ||
| 1721 | ); | ||
| 1717 | let issue = IssueState::from_ref_uncached(&alice_repo, &issue_ref, &issue_id).unwrap(); | 1722 | let issue = IssueState::from_ref_uncached(&alice_repo, &issue_ref, &issue_id).unwrap(); |
| 1718 | assert_eq!(issue.linked_commits.len(), 1); | 1723 | assert_eq!(issue.linked_commits.len(), 1); |
| 1719 | } | 1724 | } |
| @@ -1725,16 +1730,15 @@ fn commit_link_scan_handles_multiple_issue_trailers_on_one_commit() { | |||
| 1725 | let (issue_ref_a, id_a) = open_issue(&alice_repo, &alice(), "bug a"); | 1730 | let (issue_ref_a, id_a) = open_issue(&alice_repo, &alice(), "bug a"); |
| 1726 | let (issue_ref_b, id_b) = open_issue(&alice_repo, &alice(), "bug b"); | 1731 | let (issue_ref_b, id_b) = open_issue(&alice_repo, &alice(), "bug b"); |
| 1727 | 1732 | ||
| 1728 | let message = format!( | 1733 | let message = format!("Fix both\n\nIssue: {}\nIssue: {}", &id_a[..8], &id_b[..8]); |
| 1729 | "Fix both\n\nIssue: {}\nIssue: {}", | ||
| 1730 | &id_a[..8], | ||
| 1731 | &id_b[..8] | ||
| 1732 | ); | ||
| 1733 | make_commit_with_message(&alice_repo, &message); | 1734 | make_commit_with_message(&alice_repo, &message); |
| 1734 | 1735 | ||
| 1735 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); | 1736 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); |
| 1736 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); | 1737 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); |
| 1737 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 2); | 1738 | assert_eq!( |
| 1739 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), | ||
| 1740 | 2 | ||
| 1741 | ); | ||
| 1738 | 1742 | ||
| 1739 | let issue_a = IssueState::from_ref_uncached(&alice_repo, &issue_ref_a, &id_a).unwrap(); | 1743 | let issue_a = IssueState::from_ref_uncached(&alice_repo, &issue_ref_a, &id_a).unwrap(); |
| 1740 | let issue_b = IssueState::from_ref_uncached(&alice_repo, &issue_ref_b, &id_b).unwrap(); | 1744 | let issue_b = IssueState::from_ref_uncached(&alice_repo, &issue_ref_b, &id_b).unwrap(); |
| @@ -1748,14 +1752,14 @@ fn commit_link_scan_skips_unknown_prefix_without_error() { | |||
| 1748 | let alice_repo = cluster.alice_repo(); | 1752 | let alice_repo = cluster.alice_repo(); |
| 1749 | 1753 | ||
| 1750 | // No issue exists. Commit uses a completely unrelated prefix. | 1754 | // No issue exists. Commit uses a completely unrelated prefix. |
| 1751 | make_commit_with_message( | 1755 | make_commit_with_message(&alice_repo, "Fix\n\nIssue: zzzzzzzz"); |
| 1752 | &alice_repo, | ||
| 1753 | "Fix\n\nIssue: zzzzzzzz", | ||
| 1754 | ); | ||
| 1755 | 1756 | ||
| 1756 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); | 1757 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); |
| 1757 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); | 1758 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); |
| 1758 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 0); | 1759 | assert_eq!( |
| 1760 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), | ||
| 1761 | 0 | ||
| 1762 | ); | ||
| 1759 | } | 1763 | } |
| 1760 | 1764 | ||
| 1761 | #[test] | 1765 | #[test] |
| @@ -1773,8 +1777,7 @@ fn commit_link_scan_skips_genuinely_ambiguous_prefix() { | |||
| 1773 | } | 1777 | } |
| 1774 | 1778 | ||
| 1775 | // Find a first-char that has at least 2 matching IDs. | 1779 | // Find a first-char that has at least 2 matching IDs. |
| 1776 | let mut counts: std::collections::HashMap<char, Vec<&str>> = | 1780 | let mut counts: std::collections::HashMap<char, Vec<&str>> = std::collections::HashMap::new(); |
| 1777 | std::collections::HashMap::new(); | ||
| 1778 | for id in &ids { | 1781 | for id in &ids { |
| 1779 | let c = id.chars().next().unwrap(); | 1782 | let c = id.chars().next().unwrap(); |
| 1780 | counts.entry(c).or_default().push(id.as_str()); | 1783 | counts.entry(c).or_default().push(id.as_str()); |
| @@ -1829,7 +1832,10 @@ fn commit_link_scan_skips_archived_issues_with_warning() { | |||
| 1829 | 1832 | ||
| 1830 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); | 1833 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); |
| 1831 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); | 1834 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); |
| 1832 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 0); | 1835 | assert_eq!( |
| 1836 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), | ||
| 1837 | 0 | ||
| 1838 | ); | ||
| 1833 | 1839 | ||
| 1834 | // Confirm the archived ref did not accrue a new event: the archived | 1840 | // Confirm the archived ref did not accrue a new event: the archived |
| 1835 | // DAG tip should still be the archive-time tip. | 1841 | // DAG tip should still be the archive-time tip. |
| @@ -1865,7 +1871,10 @@ fn commit_link_scan_no_op_on_detached_head_with_no_branches() { | |||
| 1865 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); | 1871 | let author = git_collab::identity::get_author(&alice_repo).unwrap(); |
| 1866 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); | 1872 | let sk = signing::load_signing_key(&signing::signing_key_dir().unwrap()).unwrap(); |
| 1867 | // No branches to walk — silent no-op. | 1873 | // No branches to walk — silent no-op. |
| 1868 | assert_eq!(commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), 0); | 1874 | assert_eq!( |
| 1875 | commit_link::scan_and_link(&alice_repo, &author, &sk).unwrap(), | ||
| 1876 | 0 | ||
| 1877 | ); | ||
| 1869 | } | 1878 | } |
| 1870 | 1879 | ||
| 1871 | #[test] | 1880 | #[test] |
| @@ -1913,7 +1922,10 @@ fn commit_link_scan_dedups_against_remote_originated_events() { | |||
| 1913 | let bob_ref = format!("refs/collab/issues/{}", issue_id); | 1922 | let bob_ref = format!("refs/collab/issues/{}", issue_id); |
| 1914 | let bob_issue = IssueState::from_ref_uncached(&bob_repo, &bob_ref, &issue_id).unwrap(); | 1923 | let bob_issue = IssueState::from_ref_uncached(&bob_repo, &bob_ref, &issue_id).unwrap(); |
| 1915 | assert_eq!(bob_issue.linked_commits.len(), 1); | 1924 | assert_eq!(bob_issue.linked_commits.len(), 1); |
| 1916 | assert_eq!(bob_issue.linked_commits[0].commit, linked_commit.to_string()); | 1925 | assert_eq!( |
| 1926 | bob_issue.linked_commits[0].commit, | ||
| 1927 | linked_commit.to_string() | ||
| 1928 | ); | ||
| 1917 | } | 1929 | } |
| 1918 | 1930 | ||
| 1919 | #[test] | 1931 | #[test] |
| @@ -2260,8 +2272,7 @@ fn one_unreadable_patch_does_not_take_down_the_sync() { | |||
| 2260 | }; | 2272 | }; |
| 2261 | 2273 | ||
| 2262 | let alice_repo = Repository::open(cluster.alice_dir.path()).unwrap(); | 2274 | let alice_repo = Repository::open(cluster.alice_dir.path()).unwrap(); |
| 2263 | sync::sync(&alice_repo, "origin") | 2275 | sync::sync(&alice_repo, "origin").expect("one unreadable patch must not fail the whole sync"); |
| 2264 | .expect("one unreadable patch must not fail the whole sync"); | ||
| 2265 | 2276 | ||
| 2266 | // Everything else went through. | 2277 | // Everything else went through. |
| 2267 | let bob_repo = cluster.bob_repo(); | 2278 | let bob_repo = cluster.bob_repo(); |
| @@ -2379,7 +2390,10 @@ fn sync_does_not_adopt_a_revision_ref_the_signed_dag_does_not_vouch_for() { | |||
| 2379 | .args([ | 2390 | .args([ |
| 2380 | "push", | 2391 | "push", |
| 2381 | "origin", | 2392 | "origin", |
| 2382 | &format!("{}:refs/collab/patches/{}/rev/{}", planted, orphan_id, planted), | 2393 | &format!( |
| 2394 | "{}:refs/collab/patches/{}/rev/{}", | ||
| 2395 | planted, orphan_id, planted | ||
| 2396 | ), | ||
| 2383 | &format!("{}:refs/collab/patches/{}/rev/{}", planted, id, planted), | 2397 | &format!("{}:refs/collab/patches/{}/rev/{}", planted, id, planted), |
| 2384 | &format!("{}:refs/collab/patches/{}/rev/{}", planted, id, feat), | 2398 | &format!("{}:refs/collab/patches/{}/rev/{}", planted, id, feat), |
| 2385 | &format!("{}:refs/collab/patches/{}/r/9", planted, id), | 2399 | &format!("{}:refs/collab/patches/{}/r/9", planted, id), |
| @@ -2460,9 +2474,17 @@ fn concurrent_revise_leaves_every_revision_reachable() { | |||
| 2460 | &["patch", "revise", short, "-B", "feat"], | 2474 | &["patch", "revise", short, "-B", "feat"], |
| 2461 | ); | 2475 | ); |
| 2462 | 2476 | ||
| 2463 | sync::sync(&Repository::open(cluster.alice_dir.path()).unwrap(), "origin").unwrap(); | 2477 | sync::sync( |
| 2478 | &Repository::open(cluster.alice_dir.path()).unwrap(), | ||
| 2479 | "origin", | ||
| 2480 | ) | ||
| 2481 | .unwrap(); | ||
| 2464 | sync::sync(&Repository::open(cluster.bob_dir.path()).unwrap(), "origin").unwrap(); | 2482 | sync::sync(&Repository::open(cluster.bob_dir.path()).unwrap(), "origin").unwrap(); |
| 2465 | sync::sync(&Repository::open(cluster.alice_dir.path()).unwrap(), "origin").unwrap(); | 2483 | sync::sync( |
| 2484 | &Repository::open(cluster.alice_dir.path()).unwrap(), | ||
| 2485 | "origin", | ||
| 2486 | ) | ||
| 2487 | .unwrap(); | ||
| 2466 | 2488 | ||
| 2467 | for (label, dir) in [ | 2489 | for (label, dir) in [ |
| 2468 | ("alice", cluster.alice_dir.path()), | 2490 | ("alice", cluster.alice_dir.path()), |
| @@ -2541,10 +2563,7 @@ fn a_stale_revision_ref_on_the_remote_does_not_wedge_a_later_sync() { | |||
| 2541 | .args([ | 2563 | .args([ |
| 2542 | "push", | 2564 | "push", |
| 2543 | "origin", | 2565 | "origin", |
| 2544 | &format!( | 2566 | &format!("{}:refs/collab/patches/{}/rev/{}", stranded, id, stranded), |
| 2545 | "{}:refs/collab/patches/{}/rev/{}", | ||
| 2546 | stranded, id, stranded | ||
| 2547 | ), | ||
| 2548 | ]) | 2567 | ]) |
| 2549 | .current_dir(cluster.alice_dir.path()) | 2568 | .current_dir(cluster.alice_dir.path()) |
| 2550 | .status() | 2569 | .status() |
| @@ -2573,11 +2592,7 @@ fn a_stale_revision_ref_on_the_remote_does_not_wedge_a_later_sync() { | |||
| 2573 | 2592 | ||
| 2574 | // Bob's revision reached the remote, and a third clone picks it up cleanly. | 2593 | // Bob's revision reached the remote, and a third clone picks it up cleanly. |
| 2575 | let carol_dir = TempDir::new().unwrap(); | 2594 | let carol_dir = TempDir::new().unwrap(); |
| 2576 | let carol = Repository::clone( | 2595 | let carol = Repository::clone(cluster.bare_dir().to_str().unwrap(), carol_dir.path()).unwrap(); |
| 2577 | cluster.bare_dir().to_str().unwrap(), | ||
| 2578 | carol_dir.path(), | ||
| 2579 | ) | ||
| 2580 | .unwrap(); | ||
| 2581 | { | 2596 | { |
| 2582 | let mut config = carol.config().unwrap(); | 2597 | let mut config = carol.config().unwrap(); |
| 2583 | config.set_str("user.name", "Carol").unwrap(); | 2598 | config.set_str("user.name", "Carol").unwrap(); |
tests/timeline_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -23,7 +23,14 @@ use common::TestRepo; | |||
| 23 | fn patch_with_a_review_round(repo: &TestRepo) -> String { | 23 | fn patch_with_a_review_round(repo: &TestRepo) -> String { |
| 24 | repo.git(&["checkout", "-b", "feat-timeline"]); | 24 | repo.git(&["checkout", "-b", "feat-timeline"]); |
| 25 | repo.commit_file("a.txt", "first", "v1"); | 25 | repo.commit_file("a.txt", "first", "v1"); |
| 26 | let out = repo.run_ok(&["patch", "create", "-t", "Timeline patch", "-B", "feat-timeline"]); | 26 | let out = repo.run_ok(&[ |
| 27 | "patch", | ||
| 28 | "create", | ||
| 29 | "-t", | ||
| 30 | "Timeline patch", | ||
| 31 | "-B", | ||
| 32 | "feat-timeline", | ||
| 33 | ]); | ||
| 27 | let id = out | 34 | let id = out |
| 28 | .trim() | 35 | .trim() |
| 29 | .strip_prefix("Created patch ") | 36 | .strip_prefix("Created patch ") |
| @@ -62,14 +69,22 @@ fn timeline_interleaves_revisions_comments_and_reviews_in_order() { | |||
| 62 | // Every kind of event is present. `patch log` alone shows only the | 69 | // Every kind of event is present. `patch log` alone shows only the |
| 63 | // revisions; `patch show` alone shows only the comment and the reviews. | 70 | // revisions; `patch show` alone shows only the comment and the reviews. |
| 64 | assert!(out.contains("r1"), "timeline must show revision 1: {}", out); | 71 | assert!(out.contains("r1"), "timeline must show revision 1: {}", out); |
| 65 | assert!(out.contains("needs a test"), "timeline must show the comment: {}", out); | 72 | assert!( |
| 73 | out.contains("needs a test"), | ||
| 74 | "timeline must show the comment: {}", | ||
| 75 | out | ||
| 76 | ); | ||
| 66 | assert!( | 77 | assert!( |
| 67 | out.contains("request-changes"), | 78 | out.contains("request-changes"), |
| 68 | "timeline must show the review verdict: {}", | 79 | "timeline must show the review verdict: {}", |
| 69 | out | 80 | out |
| 70 | ); | 81 | ); |
| 71 | assert!(out.contains("r2"), "timeline must show revision 2: {}", out); | 82 | assert!(out.contains("r2"), "timeline must show revision 2: {}", out); |
| 72 | assert!(out.contains("approve"), "timeline must show the approval: {}", out); | 83 | assert!( |
| 84 | out.contains("approve"), | ||
| 85 | "timeline must show the approval: {}", | ||
| 86 | out | ||
| 87 | ); | ||
| 73 | 88 | ||
| 74 | // ...and in the order they happened. This is the assertion that makes it a | 89 | // ...and in the order they happened. This is the assertion that makes it a |
| 75 | // timeline rather than two lists printed one after the other. | 90 | // timeline rather than two lists printed one after the other. |
| @@ -206,7 +221,10 @@ fn timeline_json_carries_the_same_sequence() { | |||
| 206 | assert_eq!(entries[1]["revision"], 1, "the comment was made against r1"); | 221 | assert_eq!(entries[1]["revision"], 1, "the comment was made against r1"); |
| 207 | assert_eq!(entries[2]["revision"], 1, "the review was made against r1"); | 222 | assert_eq!(entries[2]["revision"], 1, "the review was made against r1"); |
| 208 | assert_eq!(entries[3]["revision"], 2); | 223 | assert_eq!(entries[3]["revision"], 2); |
| 209 | assert_eq!(entries[4]["revision"], 2, "the approval was made against r2"); | 224 | assert_eq!( |
| 225 | entries[4]["revision"], 2, | ||
| 226 | "the approval was made against r2" | ||
| 227 | ); | ||
| 210 | 228 | ||
| 211 | assert_eq!(entries[2]["verdict"], "request-changes"); | 229 | assert_eq!(entries[2]["verdict"], "request-changes"); |
| 212 | assert_eq!(entries[4]["verdict"], "approve"); | 230 | assert_eq!(entries[4]["verdict"], "approve"); |
| @@ -362,7 +380,9 @@ fn timeline_ignores_a_correction_the_fold_refused() { | |||
| 362 | .filter_map(|r| r.ok()) | 380 | .filter_map(|r| r.ok()) |
| 363 | .find_map(|r| { | 381 | .find_map(|r| { |
| 364 | let name = r.name()?.to_string(); | 382 | let name = r.name()?.to_string(); |
| 365 | let full = name.strip_prefix("refs/collab/patches/")?.strip_suffix("/events")?; | 383 | let full = name |
| 384 | .strip_prefix("refs/collab/patches/")? | ||
| 385 | .strip_suffix("/events")?; | ||
| 366 | full.starts_with(&id).then(|| full.to_string()) | 386 | full.starts_with(&id).then(|| full.to_string()) |
| 367 | }) | 387 | }) |
| 368 | .expect("patch events ref") | 388 | .expect("patch events ref") |
tests/trailer_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -38,7 +38,10 @@ fn empty_message() { | |||
| 38 | 38 | ||
| 39 | #[test] | 39 | #[test] |
| 40 | fn single_trailer_in_pure_block() { | 40 | fn single_trailer_in_pure_block() { |
| 41 | both_tokens("Fix thing\n\nSome context in the body.\n\n{}: abc", &["abc"]); | 41 | both_tokens( |
| 42 | "Fix thing\n\nSome context in the body.\n\n{}: abc", | ||
| 43 | &["abc"], | ||
| 44 | ); | ||
| 42 | } | 45 | } |
| 43 | 46 | ||
| 44 | #[test] | 47 | #[test] |
tests/tui_review_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -17,7 +17,10 @@ use std::time::Duration; | |||
| 17 | fn editor_writing(repo: &TestRepo, name: &str, body: &str) -> String { | 17 | fn editor_writing(repo: &TestRepo, name: &str, body: &str) -> String { |
| 18 | repo.write_script( | 18 | repo.write_script( |
| 19 | name, | 19 | name, |
| 20 | &format!("#!/bin/sh\ncat > \"$1\" <<'GITCOLLABEOF'\n{}\nGITCOLLABEOF\n", body), | 20 | &format!( |
| 21 | "#!/bin/sh\ncat > \"$1\" <<'GITCOLLABEOF'\n{}\nGITCOLLABEOF\n", | ||
| 22 | body | ||
| 23 | ), | ||
| 21 | ) | 24 | ) |
| 22 | } | 25 | } |
| 23 | 26 | ||
| @@ -26,8 +29,19 @@ fn repo_with_patch() -> (TestRepo, String) { | |||
| 26 | let repo = TestRepo::new("Reviewer", "reviewer@example.com"); | 29 | let repo = TestRepo::new("Reviewer", "reviewer@example.com"); |
| 27 | repo.commit_file("src/lib.rs", "one\ntwo\nthree\nfour\n", "seed"); | 30 | repo.commit_file("src/lib.rs", "one\ntwo\nthree\nfour\n", "seed"); |
| 28 | repo.git(&["checkout", "-b", "feature/x"]); | 31 | repo.git(&["checkout", "-b", "feature/x"]); |
| 29 | repo.commit_file("src/lib.rs", "one\ntwo changed\nthree\nfour\nfive\n", "work"); | 32 | repo.commit_file( |
| 30 | let out = repo.run_ok(&["patch", "create", "-t", "A patch to review", "-B", "feature/x"]); | 33 | "src/lib.rs", |
| 34 | "one\ntwo changed\nthree\nfour\nfive\n", | ||
| 35 | "work", | ||
| 36 | ); | ||
| 37 | let out = repo.run_ok(&[ | ||
| 38 | "patch", | ||
| 39 | "create", | ||
| 40 | "-t", | ||
| 41 | "A patch to review", | ||
| 42 | "-B", | ||
| 43 | "feature/x", | ||
| 44 | ]); | ||
| 31 | let id = out | 45 | let id = out |
| 32 | .trim() | 46 | .trim() |
| 33 | .strip_prefix("Created patch ") | 47 | .strip_prefix("Created patch ") |
| @@ -158,8 +172,19 @@ fn a_diff_line_wider_than_the_pane_is_not_clipped_away() { | |||
| 158 | // Far wider than the ~63 columns the detail pane gets at 100 across, with | 172 | // Far wider than the ~63 columns the detail pane gets at 100 across, with |
| 159 | // the far end named so the test can look for it rather than for a length. | 173 | // the far end named so the test can look for it rather than for a length. |
| 160 | let wide = format!("let x = \"{}\"; // ENDOFTHEWIDELINE", "w".repeat(300)); | 174 | let wide = format!("let x = \"{}\"; // ENDOFTHEWIDELINE", "w".repeat(300)); |
| 161 | repo.commit_file("src/lib.rs", &format!("one\ntwo\n{}\n", wide), "a wide line"); | 175 | repo.commit_file( |
| 162 | repo.run_ok(&["patch", "create", "-t", "A wide patch", "-B", "feature/wide"]); | 176 | "src/lib.rs", |
| 177 | &format!("one\ntwo\n{}\n", wide), | ||
| 178 | "a wide line", | ||
| 179 | ); | ||
| 180 | repo.run_ok(&[ | ||
| 181 | "patch", | ||
| 182 | "create", | ||
| 183 | "-t", | ||
| 184 | "A wide patch", | ||
| 185 | "-B", | ||
| 186 | "feature/wide", | ||
| 187 | ]); | ||
| 163 | repo.git(&["checkout", "main"]); | 188 | repo.git(&["checkout", "main"]); |
| 164 | 189 | ||
| 165 | let out = repo.run_dashboard_driven(100, 45, &[], |dash| { | 190 | let out = repo.run_dashboard_driven(100, 45, &[], |dash| { |
tests/version_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -79,7 +79,10 @@ fn server_binary_reports_version_with_build_commit() { | |||
| 79 | "git-collab-server --version should exit 0: {}", | 79 | "git-collab-server --version should exit 0: {}", |
| 80 | String::from_utf8_lossy(&output.stderr) | 80 | String::from_utf8_lossy(&output.stderr) |
| 81 | ); | 81 | ); |
| 82 | assert_version_output("git-collab-server", &String::from_utf8_lossy(&output.stdout)); | 82 | assert_version_output( |
| 83 | "git-collab-server", | ||
| 84 | &String::from_utf8_lossy(&output.stdout), | ||
| 85 | ); | ||
| 83 | } | 86 | } |
| 84 | 87 | ||
| 85 | /// `--version` is a question about the binary, not about a repository, so it | 88 | /// `--version` is a question about the binary, not about a repository, so it |
| @@ -201,7 +204,10 @@ fn a_passed_commit_is_used_when_there_is_no_checkout() { | |||
| 201 | /// wrapper that knows more than the build tree does. | 204 | /// wrapper that knows more than the build tree does. |
| 202 | #[test] | 205 | #[test] |
| 203 | fn a_passed_commit_overrides_the_discovered_one() { | 206 | fn a_passed_commit_overrides_the_discovered_one() { |
| 204 | assert_eq!(resolve_build_commit(Some(FULL), Some(OTHER)), Ok(Some(FULL))); | 207 | assert_eq!( |
| 208 | resolve_build_commit(Some(FULL), Some(OTHER)), | ||
| 209 | Ok(Some(FULL)) | ||
| 210 | ); | ||
| 205 | } | 211 | } |
| 206 | 212 | ||
| 207 | #[test] | 213 | #[test] |
| @@ -349,7 +355,9 @@ fn main() { | |||
| 349 | for (key, value) in env { | 355 | for (key, value) in env { |
| 350 | cmd.env(key, value); | 356 | cmd.env(key, value); |
| 351 | } | 357 | } |
| 352 | let out = cmd.output().expect("failed to run cargo for the probe crate"); | 358 | let out = cmd |
| 359 | .output() | ||
| 360 | .expect("failed to run cargo for the probe crate"); | ||
| 353 | assert!( | 361 | assert!( |
| 354 | out.status.success(), | 362 | out.status.success(), |
| 355 | "probe build failed: {}", | 363 | "probe build failed: {}", |
tests/web_rendering_test.rs
| Old | New | ||
|---|---|---|---|
| @@ -101,7 +101,10 @@ fn list_timestamps_are_short_with_the_full_value_on_hover() { | |||
| 101 | format!("/{}/patches", harness.repo_name()), | 101 | format!("/{}/patches", harness.repo_name()), |
| 102 | &full_patch_stamp, | 102 | &full_patch_stamp, |
| 103 | ), | 103 | ), |
| 104 | (format!("/{}/issues", harness.repo_name()), &full_issue_stamp), | 104 | ( |
| 105 | format!("/{}/issues", harness.repo_name()), | ||
| 106 | &full_issue_stamp, | ||
| 107 | ), | ||
| 105 | ] { | 108 | ] { |
| 106 | let body = harness.get_ok(&path).body; | 109 | let body = harness.get_ok(&path).body; |
| 107 | assert!( | 110 | assert!( |
| @@ -125,9 +128,9 @@ fn list_timestamps_are_short_with_the_full_value_on_hover() { | |||
| 125 | fn patch_detail_timestamps_are_short_with_the_full_value_on_hover() { | 128 | fn patch_detail_timestamps_are_short_with_the_full_value_on_hover() { |
| 126 | let harness = ServerHarness::new("render-detail-stamps"); | 129 | let harness = ServerHarness::new("render-detail-stamps"); |
| 127 | let patch_id = patch_on_ephemeral_branch(&harness, "Reviewed patch", "feat/reviewed"); | 130 | let patch_id = patch_on_ephemeral_branch(&harness, "Reviewed patch", "feat/reviewed"); |
| 128 | harness | 131 | harness.work_repo().run_ok(&[ |
| 129 | .work_repo() | 132 | "patch", "review", &patch_id, "-v", "approve", "-b", "Looks ok", |
| 130 | .run_ok(&["patch", "review", &patch_id, "-v", "approve", "-b", "Looks ok"]); | 133 | ]); |
| 131 | harness | 134 | harness |
| 132 | .work_repo() | 135 | .work_repo() |
| 133 | .run_ok(&["patch", "comment", &patch_id, "-b", "A thread comment"]); | 136 | .run_ok(&["patch", "comment", &patch_id, "-b", "A thread comment"]); |
| @@ -135,11 +138,7 @@ fn patch_detail_timestamps_are_short_with_the_full_value_on_hover() { | |||
| 135 | harness.push_collab_refs(); | 138 | harness.push_collab_refs(); |
| 136 | 139 | ||
| 137 | let body = harness | 140 | let body = harness |
| 138 | .get_ok(&format!( | 141 | .get_ok(&format!("/{}/patches/{}", harness.repo_name(), patch_id)) |
| 139 | "/{}/patches/{}", | ||
| 140 | harness.repo_name(), | ||
| 141 | patch_id | ||
| 142 | )) | ||
| 143 | .body; | 142 | .body; |
| 144 | assert!( | 143 | assert!( |
| 145 | !has_nanosecond_timestamp(&body), | 144 | !has_nanosecond_timestamp(&body), |
| @@ -171,11 +170,7 @@ fn merged_in_is_abbreviated_like_the_revision_commits_beside_it() { | |||
| 171 | assert_eq!(merge_commit.len(), 40, "the event records the full oid"); | 170 | assert_eq!(merge_commit.len(), 40, "the event records the full oid"); |
| 172 | 171 | ||
| 173 | let body = harness | 172 | let body = harness |
| 174 | .get_ok(&format!( | 173 | .get_ok(&format!("/{}/patches/{}", harness.repo_name(), patch_id)) |
| 175 | "/{}/patches/{}", | ||
| 176 | harness.repo_name(), | ||
| 177 | patch_id | ||
| 178 | )) | ||
| 179 | .body; | 174 | .body; |
| 180 | 175 | ||
| 181 | // The link still goes to the full oid; only the text is abbreviated. | 176 | // The link still goes to the full oid; only the text is abbreviated. |
| @@ -308,11 +303,7 @@ fn the_patch_detail_page_still_records_the_branch_it_came_from() { | |||
| 308 | harness.push_collab_refs(); | 303 | harness.push_collab_refs(); |
| 309 | 304 | ||
| 310 | let body = harness | 305 | let body = harness |
| 311 | .get_ok(&format!( | 306 | .get_ok(&format!("/{}/patches/{}", harness.repo_name(), patch_id)) |
| 312 | "/{}/patches/{}", | ||
| 313 | harness.repo_name(), | ||
| 314 | patch_id | ||
| 315 | )) | ||
| 316 | .body; | 307 | .body; |
| 317 | assert!( | 308 | assert!( |
| 318 | body.contains(EPHEMERAL_BRANCH), | 309 | body.contains(EPHEMERAL_BRANCH), |