Make primary command output identify commits by both their abbreviated object ID and equally sized reverse-hex change ID. This covers mutation results, rewritten refs, pins, notices, raw ref-tree tips, and CLI rebase documents while leaving diagnostics, serialized todo state, and TUI editor output unchanged.
Unified · Split
| @@ -67,6 +67,12 @@ without trading responsiveness for metadata that is not visible. · 66 unmodified lines | ||
| 67 | 67 | line. Editing that title preserves an existing message body byte-for-byte, |
| 68 | 68 | while commenting, deleting, or emptying the header removes the whole message. |
| 69 | 69 | Explicit `-m` and `-f` messages preserve enrichments. |
| 70 | +- Primary command output follows every displayed abbreviated commit hash with | |
| 71 | + its reverse-hex change ID. The two abbreviations have equal widths. This | |
| 72 | + applies to mutation results, rewritten-ref mappings, pins, stash and travel | |
| 73 | + notices, raw commit labels in `ref-tree`, and visible commit identifiers in | |
| 74 | + rebase todos. Diagnostics on stderr and the full object IDs in the hidden | |
| 75 | + `tix-rebase-state-v2` block remain unchanged. | |
| 70 | 76 | - `-x/--hide REVSPEC` excludes the revision and its reachable ancestry. The |
| 71 | 77 | option may be repeated. |
| 72 | 78 | - `-h/--help` prints Clap's standard help for `tix` and every subcommand. |
| @@ -9,6 +9,7 @@ use gix::{ · 8 unmodified lines | ||
| 9 | 9 | ObjectId, |
| 10 | 10 | bstr::{BStr, BString}, |
| 11 | 11 | hash::ChangeId, |
| 12 | + prelude::ObjectIdExt, | |
| 12 | 13 | }; |
| 13 | 14 | |
| 14 | 15 | pub(crate) const HEADER: &str = "change-id"; |
| @@ -27,6 +28,26 @@ pub(crate) fn for_commit(repo: &gix::Repository, id: ObjectId) -> Result<ChangeI · 12 unmodified lines | ||
| 27 | 28 | Ok(effective(id, commit.extra_headers().find_all(HEADER))) |
| 28 | 29 | } |
| 29 | 30 | |
| 31 | +pub(crate) fn display(repo: &gix::Repository, id: ObjectId, len: usize) -> Result<String> { | |
| 32 | + Ok(format!( | |
| 33 | + "{} {}", | |
| 34 | + id.to_hex_with_len(len), | |
| 35 | + for_commit(repo, id)?.to_reverse_hex_with_len(len) | |
| 36 | + )) | |
| 37 | +} | |
| 38 | + | |
| 39 | +pub(crate) fn display_short(repo: &gix::Repository, id: ObjectId) -> Result<String> { | |
| 40 | + let hash = id | |
| 41 | + .attach(repo) | |
| 42 | + .shorten() | |
| 43 | + .context("could not shorten commit ID")? | |
| 44 | + .to_string(); | |
| 45 | + Ok(format!( | |
| 46 | + "{hash} {}", | |
| 47 | + for_commit(repo, id)?.to_reverse_hex_with_len(hash.len()) | |
| 48 | + )) | |
| 49 | +} | |
| 50 | + | |
| 30 | 51 | pub(crate) fn abbreviations( |
| 31 | 52 | repo: &gix::Repository, |
| 32 | 53 | ids: impl IntoIterator<Item = ObjectId>, |
| @@ -7,7 +7,7 @@ use std::{ · 6 unmodified lines | ||
| 7 | 7 | |
| 8 | 8 | use anyhow::{Context, Result}; |
| 9 | 9 | use clap::Parser; |
| 10 | −use gix::prelude::{ObjectIdExt, ReferenceExt}; | |
| 10 | +use gix::prelude::ReferenceExt; | |
| 11 | 11 | use ratatui::text::Line; |
| 12 | 12 | |
| 13 | 13 | mod new; |
| @@ -148,6 +148,7 @@ impl Platform { · 134 unmodified lines | ||
| 148 | 148 | Command::RefTree(_) | Command::Show(_) => unreachable!("display commands return before logging"), |
| 149 | 149 | Command::Amend(args) => { |
| 150 | 150 | let graph = crate::edit::loaded_view_graph(&repository)?; |
| 151 | + let output_repository = repository.clone(); | |
| 151 | 152 | let amended = if args.index { |
| 152 | 153 | crate::edit::head::amend_index_reporting(repository, &graph)? |
| 153 | 154 | } else { |
| @@ -155,14 +156,9 @@ impl Platform { · 1 unmodified line | ||
| 155 | 156 | }; |
| 156 | 157 | match amended { |
| 157 | 158 | Some(outcome) => { |
| 158 | − println!( | |
| 159 | − "{}", | |
| 160 | − outcome | |
| 161 | − .selected | |
| 162 | − .context("amending did not produce a selection")? | |
| 163 | − .to_hex_with_len(7) | |
| 164 | − ); | |
| 165 | − print_ref_rewrites(&outcome.ref_rewrites); | |
| 159 | + let selected = outcome.selected.context("amending did not produce a selection")?; | |
| 160 | + println!("{}", crate::change_id::display(&output_repository, selected, 7)?); | |
| 161 | + print_ref_rewrites(&output_repository, &outcome.ref_rewrites)?; | |
| 166 | 162 | } |
| 167 | 163 | None => println!("nothing to amend"), |
| 168 | 164 | } |
| @@ -180,10 +176,8 @@ impl Platform { · 11 unmodified lines | ||
| 180 | 176 | .head_id() |
| 181 | 177 | .context("stashing changes requires a born HEAD")? |
| 182 | 178 | .detach(); |
| 183 | − println!( | |
| 184 | − "{}", | |
| 185 | − crate::edit::stash::save_manual(repository.git_dir(), repository.is_bare(), id)? | |
| 186 | − ); | |
| 179 | + let notice = crate::edit::stash::save_manual(repository.git_dir(), repository.is_bare(), id)?; | |
| 180 | + println!("{}", notice_with_change_id(&repository, ¬ice, id)?); | |
| 187 | 181 | } |
| 188 | 182 | Command::Pin(args) => pin(&repository, args)?, |
| 189 | 183 | Command::Travel(args) => return travel::run(repository, args), |
| @@ -458,10 +452,7 @@ fn display_pin(repository: &gix::Repository, pin: &crate::history::Pin) -> Resul · 268 unmodified lines | ||
| 458 | 452 | Ok(format!( |
| 459 | 453 | "{} {}", |
| 460 | 454 | crate::edit::time_travel::pin_label(pin), |
| 461 | − pin.id | |
| 462 | − .attach(repository) | |
| 463 | − .shorten() | |
| 464 | − .context("could not shorten pinned commit ID")? | |
| 455 | + crate::change_id::display_short(repository, pin.id)? | |
| 465 | 456 | )) |
| 466 | 457 | } |
| 467 | 458 | |
| @@ -471,16 +462,12 @@ fn edit_head( · 3 unmodified lines | ||
| 471 | 462 | kind: crate::edit::head::Kind, |
| 472 | 463 | verb: &str, |
| 473 | 464 | ) -> Result<()> { |
| 465 | + let output_repository = repository.clone(); | |
| 474 | 466 | match crate::edit::head::perform_reporting(repository, graph, kind)? { |
| 475 | 467 | Some(outcome) => { |
| 476 | − println!( | |
| 477 | − "{}", | |
| 478 | − outcome | |
| 479 | − .selected | |
| 480 | − .context("editing HEAD did not produce a selection")? | |
| 481 | − .to_hex_with_len(7) | |
| 482 | − ); | |
| 483 | − print_ref_rewrites(&outcome.ref_rewrites); | |
| 468 | + let selected = outcome.selected.context("editing HEAD did not produce a selection")?; | |
| 469 | + println!("{}", crate::change_id::display(&output_repository, selected, 7)?); | |
| 470 | + print_ref_rewrites(&output_repository, &outcome.ref_rewrites)?; | |
| 484 | 471 | } |
| 485 | 472 | None => println!("nothing to {verb}"), |
| 486 | 473 | } |
| @@ -504,40 +491,46 @@ fn split(repository: gix::Repository, graph: &crate::history::HistoryGraph, args · 17 unmodified lines | ||
| 504 | 491 | .context("could not reopen repository after editing split")?; |
| 505 | 492 | repository.object_cache_size(None); |
| 506 | 493 | let outcome = crate::edit::split::apply_reporting(repository, graph, prepared, &edited)?; |
| 507 | − println!( | |
| 508 | − "{}", | |
| 509 | − outcome | |
| 510 | − .selected | |
| 511 | − .context("splitting did not produce a selection")? | |
| 512 | − .to_hex_with_len(7) | |
| 513 | − ); | |
| 514 | − print_ref_rewrites(&outcome.ref_rewrites); | |
| 494 | + let output_repository = | |
| 495 | + crate::open_repository(&repository_path, bare, false).context("could not reopen repository after splitting")?; | |
| 496 | + let selected = outcome.selected.context("splitting did not produce a selection")?; | |
| 497 | + println!("{}", crate::change_id::display(&output_repository, selected, 7)?); | |
| 498 | + print_ref_rewrites(&output_repository, &outcome.ref_rewrites)?; | |
| 515 | 499 | Ok(()) |
| 516 | 500 | } |
| 517 | 501 | |
| 518 | −fn print_ref_rewrites(rewrites: &[crate::edit::rebase::RefRewrite]) { | |
| 519 | − for line in ref_rewrite_lines(rewrites) { | |
| 502 | +fn print_ref_rewrites(repository: &gix::Repository, rewrites: &[crate::edit::rebase::RefRewrite]) -> Result<()> { | |
| 503 | + for line in ref_rewrite_lines(repository, rewrites)? { | |
| 520 | 504 | println!("{line}"); |
| 521 | 505 | } |
| 506 | + Ok(()) | |
| 522 | 507 | } |
| 523 | 508 | |
| 524 | −fn ref_rewrite_lines(rewrites: &[crate::edit::rebase::RefRewrite]) -> Vec<String> { | |
| 509 | +fn ref_rewrite_lines( | |
| 510 | + repository: &gix::Repository, | |
| 511 | + rewrites: &[crate::edit::rebase::RefRewrite], | |
| 512 | +) -> Result<Vec<String>> { | |
| 525 | 513 | let mut rewrites = rewrites.to_vec(); |
| 526 | 514 | rewrites.sort_by(|a, b| a.name.cmp(&b.name)); |
| 527 | 515 | rewrites.dedup(); |
| 528 | 516 | rewrites |
| 529 | 517 | .into_iter() |
| 530 | 518 | .map(|rewrite| { |
| 531 | − format!( | |
| 519 | + Ok(format!( | |
| 532 | 520 | "{}: {} -> {}", |
| 533 | 521 | rewrite.name, |
| 534 | − rewrite.old.to_hex_with_len(7), | |
| 535 | − rewrite.new.to_hex_with_len(7) | |
| 536 | − ) | |
| 522 | + crate::change_id::display(repository, rewrite.old, 7)?, | |
| 523 | + crate::change_id::display(repository, rewrite.new, 7)? | |
| 524 | + )) | |
| 537 | 525 | }) |
| 538 | 526 | .collect() |
| 539 | 527 | } |
| 540 | 528 | |
| 529 | +fn notice_with_change_id(repository: &gix::Repository, notice: &str, id: gix::ObjectId) -> Result<String> { | |
| 530 | + let hash = id.to_hex_with_len(7).to_string(); | |
| 531 | + Ok(notice.replacen(&hash, &crate::change_id::display(repository, id, 7)?, 1)) | |
| 532 | +} | |
| 533 | + | |
| 541 | 534 | #[cfg(test)] |
| 542 | 535 | mod tests { |
| 543 | 536 | use std::{path::Path, process::Command as ProcessCommand}; |
| @@ -547,13 +540,11 @@ mod tests { · 3 unmodified lines | ||
| 547 | 540 | use super::*; |
| 548 | 541 | |
| 549 | 542 | #[test] |
| 550 | − fn rewritten_ref_lines_are_sorted_and_show_the_commit_mapping() { | |
| 551 | − let old: gix::ObjectId = "1111111111111111111111111111111111111111" | |
| 552 | − .parse() | |
| 553 | − .expect("valid object ID"); | |
| 554 | − let new: gix::ObjectId = "2222222222222222222222222222222222222222" | |
| 555 | − .parse() | |
| 556 | − .expect("valid object ID"); | |
| 543 | + fn rewritten_ref_lines_are_sorted_and_show_the_commit_mapping() -> gix_testtools::Result { | |
| 544 | + let fixture = gix_testtools::scripted_fixture_writable("history.sh")?; | |
| 545 | + let repository = crate::test_repository::open(fixture.path())?; | |
| 546 | + let old = repository.rev_parse_single("main~1")?.detach(); | |
| 547 | + let new = repository.rev_parse_single("main")?.detach(); | |
| 557 | 548 | let branch = crate::edit::rebase::RefRewrite { |
| 558 | 549 | name: "refs/heads/z".try_into().expect("valid ref name"), |
| 559 | 550 | old, |
| @@ -565,11 +556,44 @@ mod tests { · 5 unmodified lines | ||
| 565 | 556 | new, |
| 566 | 557 | }; |
| 567 | 558 | assert_eq!( |
| 568 | − ref_rewrite_lines(&[branch.clone(), first, branch]), | |
| 569 | − ["refs/heads/a: 1111111 -> 2222222", "refs/heads/z: 1111111 -> 2222222"], | |
| 559 | + ref_rewrite_lines(&repository, &[branch.clone(), first, branch])?, | |
| 560 | + [ | |
| 561 | + format!( | |
| 562 | + "refs/heads/a: {} -> {}", | |
| 563 | + crate::change_id::display(&repository, old, 7)?, | |
| 564 | + crate::change_id::display(&repository, new, 7)? | |
| 565 | + ), | |
| 566 | + format!( | |
| 567 | + "refs/heads/z: {} -> {}", | |
| 568 | + crate::change_id::display(&repository, old, 7)?, | |
| 569 | + crate::change_id::display(&repository, new, 7)? | |
| 570 | + ) | |
| 571 | + ], | |
| 570 | 572 | "ref mappings are stable and duplicate-free" |
| 571 | 573 | ); |
| 572 | − assert!(ref_rewrite_lines(&[]).is_empty(), "unchanged refs add no output"); | |
| 574 | + assert!( | |
| 575 | + ref_rewrite_lines(&repository, &[])?.is_empty(), | |
| 576 | + "unchanged refs add no output" | |
| 577 | + ); | |
| 578 | + Ok(()) | |
| 579 | + } | |
| 580 | + | |
| 581 | + #[test] | |
| 582 | + fn commit_notices_pair_their_hash_with_the_change_id() -> gix_testtools::Result { | |
| 583 | + let fixture = gix_testtools::scripted_fixture_writable("history.sh")?; | |
| 584 | + let repository = crate::test_repository::open(fixture.path())?; | |
| 585 | + let id = repository.head_id()?.detach(); | |
| 586 | + let notice = format!("stashed changes at {}; retained warning", id.to_hex_with_len(7)); | |
| 587 | + | |
| 588 | + assert_eq!( | |
| 589 | + notice_with_change_id(&repository, ¬ice, id)?, | |
| 590 | + format!( | |
| 591 | + "stashed changes at {}; retained warning", | |
| 592 | + crate::change_id::display(&repository, id, 7)? | |
| 593 | + ), | |
| 594 | + "the change ID stays adjacent to the hash without disturbing later text" | |
| 595 | + ); | |
| 596 | + Ok(()) | |
| 573 | 597 | } |
| 574 | 598 | |
| 575 | 599 | #[test] |
| @@ -951,12 +975,12 @@ mod tests { · 375 unmodified lines | ||
| 951 | 975 | assert_eq!(repeated[0].name, pins[0].name, "an existing symbolic pin is reused"); |
| 952 | 976 | assert_eq!(crate::history::all_pins(&repository)?.len(), 4); |
| 953 | 977 | let display = display_pin(&repository, &pins[0])?; |
| 954 | − let (label, short_id) = display.split_once(' ').context("pin output has a label and ID")?; | |
| 978 | + let (label, ids) = display.split_once(' ').context("pin output has a label and IDs")?; | |
| 955 | 979 | assert!(label.starts_with("pin:"), "output names the pin"); |
| 956 | 980 | assert_eq!( |
| 957 | − short_id, | |
| 958 | − main.attach(&repository).shorten()?.to_string(), | |
| 959 | − "output uses the repository's abbreviated ID" | |
| 981 | + ids, | |
| 982 | + crate::change_id::display_short(&repository, main)?, | |
| 983 | + "output uses matching repository-abbreviated commit and change IDs" | |
| 960 | 984 | ); |
| 961 | 985 | repository.reference( |
| 962 | 986 | "refs/heads/main", |
| @@ -72,14 +72,13 @@ pub(super) fn run(repository: gix::Repository, args: Args) -> Result<()> { · 71 unmodified lines | ||
| 72 | 72 | repository.object_cache_size(None); |
| 73 | 73 | crate::edit::create::apply_reporting(repository, &graph, prepared, &edited)? |
| 74 | 74 | }; |
| 75 | − println!( | |
| 76 | − "{}", | |
| 77 | − outcome | |
| 78 | − .selected | |
| 79 | − .context("creating a commit did not produce a selection")? | |
| 80 | − .to_hex_with_len(7) | |
| 81 | − ); | |
| 82 | − super::print_ref_rewrites(&outcome.ref_rewrites); | |
| 75 | + let repository = crate::open_repository(&repository_path, bare, false) | |
| 76 | + .context("could not reopen repository after creating commit")?; | |
| 77 | + let selected = outcome | |
| 78 | + .selected | |
| 79 | + .context("creating a commit did not produce a selection")?; | |
| 80 | + println!("{}", crate::change_id::display(&repository, selected, 7)?); | |
| 81 | + super::print_ref_rewrites(&repository, &outcome.ref_rewrites)?; | |
| 83 | 82 | Ok(()) |
| 84 | 83 | } |
| 85 | 84 | |
| @@ -210,7 +210,7 @@ fn prepare(repo: &gix::Repository, args: &Todo) -> Result<todo::Prepared> { · 209 unmodified lines | ||
| 210 | 210 | }) |
| 211 | 211 | }) |
| 212 | 212 | .collect::<Result<Vec<_>>>()?; |
| 213 | − todo::prepare(repo, base, onto, &commits, &refs.view_tips, onto_kind) | |
| 213 | + todo::prepare(repo, base, onto, &commits, &refs.view_tips, onto_kind, true) | |
| 214 | 214 | } |
| 215 | 215 | |
| 216 | 216 | fn resolve_commit(repo: &gix::Repository, revision: &OsStr, description: &str) -> Result<ObjectId> { |
| @@ -259,13 +259,14 @@ fn apply_document(repo: gix::Repository, document: &[u8], materialize_conflicts: · 42 unmodified lines | ||
| 259 | 259 | match rebase::perform_plan(&repo, &graph, parsed.plan)? { |
| 260 | 260 | rebase::PlanPerform::Complete(outcome) => { |
| 261 | 261 | let revisions = mapped_revisions(&tips, |id| outcome.map(id)); |
| 262 | − if outcome.selected.is_some() { | |
| 262 | + if let Some(selected) = outcome.selected { | |
| 263 | 263 | let notice = edit::time_travel::checkout_plan(&repository_path, bare, &outcome, &revisions, false)?; |
| 264 | − println!("{}", notice.unwrap_or_else(|| "rebased history".into())); | |
| 264 | + let notice = notice.unwrap_or_else(|| "rebased history".into()); | |
| 265 | + println!("{}", super::notice_with_change_id(&repo, ¬ice, selected)?); | |
| 265 | 266 | } else { |
| 266 | 267 | println!("rebased history"); |
| 267 | 268 | } |
| 268 | − super::print_ref_rewrites(&outcome.ref_rewrites); | |
| 269 | + super::print_ref_rewrites(&repo, &outcome.ref_rewrites)?; | |
| 269 | 270 | Ok(()) |
| 270 | 271 | } |
| 271 | 272 | rebase::PlanPerform::Conflict(mut conflict) => { |
| @@ -283,7 +284,7 @@ fn apply_document(repo: gix::Repository, document: &[u8], materialize_conflicts: · 11 unmodified lines | ||
| 283 | 284 | conflict.persist_objects()?; |
| 284 | 285 | let plan = conflict.continuation_plan(); |
| 285 | 286 | let mapped_tips = tips.iter().filter_map(|id| conflict.map(*id)).collect(); |
| 286 | − let continuation = todo::prepare_continuation(conflict.repository(), &plan, mapped_tips)?.document; | |
| 287 | + let continuation = todo::prepare_continuation(conflict.repository(), &plan, mapped_tips, true)?.document; | |
| 287 | 288 | let revisions = mapped_revisions(&tips, |id| conflict.map(id)); |
| 288 | 289 | if destination == Path::new("-") { |
| 289 | 290 | let mut stdout = std::io::stdout().lock(); |
| @@ -314,7 +315,7 @@ fn apply_document(repo: gix::Repository, document: &[u8], materialize_conflicts: · 24 unmodified lines | ||
| 314 | 315 | return Err(err); |
| 315 | 316 | } |
| 316 | 317 | }; |
| 317 | − super::print_ref_rewrites(&ref_rewrites); | |
| 318 | + super::print_ref_rewrites(&repo, &ref_rewrites)?; | |
| 318 | 319 | eprintln!("{notice}; continue with `tix rebase apply {}`", destination.display()); |
| 319 | 320 | anyhow::bail!("rebase stopped at a materialized conflict") |
| 320 | 321 | } |
| @@ -483,7 +484,10 @@ mod tests { · 162 unmodified lines | ||
| 483 | 484 | "moving to the updated base makes an unchanged todo actionable" |
| 484 | 485 | ); |
| 485 | 486 | assert!( |
| 486 | − document.contains(&format!("{} (updated-base)", updated.to_hex_with_len(7))), | |
| 487 | + document.contains(&format!( | |
| 488 | + "{} (updated-base)", | |
| 489 | + crate::change_id::display_short(&repo, updated)? | |
| 490 | + )), | |
| 487 | 491 | "the TUI-selected hidden tip is labelled as the updated base: {document}" |
| 488 | 492 | ); |
| 489 | 493 | |
| @@ -556,6 +560,7 @@ mod tests { · 66 unmodified lines | ||
| 556 | 560 | ], |
| 557 | 561 | &[after], |
| 558 | 562 | todo::OntoKind::Onto, |
| 563 | + true, | |
| 559 | 564 | )?; |
| 560 | 565 | let generated = std::str::from_utf8(&prepared.document)?; |
| 561 | 566 | let state = &generated[generated |
| @@ -59,13 +59,17 @@ pub(super) fn run(repository: gix::Repository, args: Args) -> Result<()> { · 58 unmodified lines | ||
| 59 | 59 | .context("author is not valid UTF-8")?; |
| 60 | 60 | |
| 61 | 61 | if let Some(message) = explicit_message(&args.edit, std::io::stdin())? { |
| 62 | − return finish(crate::edit::reword::apply_message_reporting( | |
| 63 | − repository, | |
| 64 | − &graph, | |
| 65 | − target, | |
| 66 | − &message, | |
| 67 | − author.map(AsRef::as_ref), | |
| 68 | − )?); | |
| 62 | + let output_repository = repository.clone(); | |
| 63 | + return finish( | |
| 64 | + &output_repository, | |
| 65 | + crate::edit::reword::apply_message_reporting( | |
| 66 | + repository, | |
| 67 | + &graph, | |
| 68 | + target, | |
| 69 | + &message, | |
| 70 | + author.map(AsRef::as_ref), | |
| 71 | + )?, | |
| 72 | + ); | |
| 69 | 73 | } |
| 70 | 74 | |
| 71 | 75 | let repository_path = repository.git_dir().to_owned(); |
| @@ -89,7 +93,12 @@ pub(super) fn run(repository: gix::Repository, args: Args) -> Result<()> { · 17 unmodified lines | ||
| 89 | 93 | let mut repository = crate::open_repository(&repository_path, bare, false) |
| 90 | 94 | .context("could not reopen repository after editing commit")?; |
| 91 | 95 | repository.object_cache_size(None); |
| 92 | − finish_editor(crate::edit::reword::apply(repository, &graph, target, &edited)?, target) | |
| 96 | + let output_repository = repository.clone(); | |
| 97 | + finish_editor( | |
| 98 | + &output_repository, | |
| 99 | + crate::edit::reword::apply(repository, &graph, target, &edited)?, | |
| 100 | + target, | |
| 101 | + ) | |
| 93 | 102 | } |
| 94 | 103 | |
| 95 | 104 | pub(super) fn explicit_message(args: &MessageArgs, mut stdin: impl Read) -> Result<Option<Vec<u8>>> { |
| @@ -122,22 +131,26 @@ pub(super) fn explicit_message(args: &MessageArgs, mut stdin: impl Read) -> Resu · 26 unmodified lines | ||
| 122 | 131 | } |
| 123 | 132 | } |
| 124 | 133 | |
| 125 | −fn finish(outcome: crate::edit::reword::Outcome) -> Result<()> { | |
| 134 | +fn finish(repository: &gix::Repository, outcome: crate::edit::reword::Outcome) -> Result<()> { | |
| 126 | 135 | match outcome.commit { |
| 127 | − Some(id) => println!("{}", id.to_hex_with_len(7)), | |
| 136 | + Some(id) => println!("{}", crate::change_id::display(repository, id, 7)?), | |
| 128 | 137 | None => println!("no reword performed: the edited commit was unchanged"), |
| 129 | 138 | } |
| 130 | − super::print_ref_rewrites(&outcome.ref_rewrites); | |
| 139 | + super::print_ref_rewrites(repository, &outcome.ref_rewrites)?; | |
| 131 | 140 | Ok(()) |
| 132 | 141 | } |
| 133 | 142 | |
| 134 | −fn finish_editor(outcome: crate::edit::reword::Outcome, original: gix::ObjectId) -> Result<()> { | |
| 143 | +fn finish_editor( | |
| 144 | + repository: &gix::Repository, | |
| 145 | + outcome: crate::edit::reword::Outcome, | |
| 146 | + original: gix::ObjectId, | |
| 147 | +) -> Result<()> { | |
| 135 | 148 | match (outcome.commit, outcome.enrichment) { |
| 136 | − (Some(id), _) => println!("{}", id.to_hex_with_len(7)), | |
| 137 | − (None, Some(_)) => println!("{}", original.to_hex_with_len(7)), | |
| 149 | + (Some(id), _) => println!("{}", crate::change_id::display(repository, id, 7)?), | |
| 150 | + (None, Some(_)) => println!("{}", crate::change_id::display(repository, original, 7)?), | |
| 138 | 151 | (None, None) => println!("no reword performed: the edited commit was unchanged"), |
| 139 | 152 | } |
| 140 | − super::print_ref_rewrites(&outcome.ref_rewrites); | |
| 153 | + super::print_ref_rewrites(repository, &outcome.ref_rewrites)?; | |
| 141 | 154 | Ok(()) |
| 142 | 155 | } |
| 143 | 156 | |
| @@ -22,7 +22,7 @@ pub(super) fn run(repository: gix::Repository, args: Args) -> Result<()> { · 21 unmodified lines | ||
| 22 | 22 | let detached = head.is_detached(); |
| 23 | 23 | drop(head); |
| 24 | 24 | if selected == head_id { |
| 25 | − println!("already at {}", selected.to_hex_with_len(7)); | |
| 25 | + println!("already at {}", crate::change_id::display(&repository, selected, 7)?); | |
| 26 | 26 | return Ok(()); |
| 27 | 27 | } |
| 28 | 28 | |
| @@ -51,16 +51,28 @@ pub(super) fn run(repository: gix::Repository, args: Args) -> Result<()> { · 22 unmodified lines | ||
| 51 | 51 | let bare = repository.is_bare(); |
| 52 | 52 | drop(repository); |
| 53 | 53 | match crate::edit::time_travel::perform(&repository_path, bare, selected, &graph, &reviews, &[], false)? { |
| 54 | − crate::edit::time_travel::Perform::Complete { notice, ref_rewrites } => { | |
| 54 | + crate::edit::time_travel::Perform::Complete { | |
| 55 | + notice, | |
| 56 | + selected, | |
| 57 | + ref_rewrites, | |
| 58 | + } => { | |
| 59 | + let repository = crate::open_repository(&repository_path, bare, false) | |
| 60 | + .context("could not reopen repository after time-travel")?; | |
| 55 | 61 | println!( |
| 56 | 62 | "{}", |
| 57 | − notice.unwrap_or_else(|| format!("already at {}", selected.to_hex_with_len(7))) | |
| 63 | + super::notice_with_change_id( | |
| 64 | + &repository, | |
| 65 | + ¬ice.unwrap_or_else(|| format!("already at {}", selected.to_hex_with_len(7))), | |
| 66 | + selected, | |
| 67 | + )? | |
| 58 | 68 | ); |
| 59 | − super::print_ref_rewrites(&ref_rewrites); | |
| 69 | + super::print_ref_rewrites(&repository, &ref_rewrites)?; | |
| 60 | 70 | } |
| 61 | 71 | crate::edit::time_travel::Perform::Conflict(conflict) if args.materialize_conflicts => { |
| 62 | 72 | let (notice, _, ref_rewrites) = conflict.accept()?; |
| 63 | − super::print_ref_rewrites(&ref_rewrites); | |
| 73 | + let repository = crate::open_repository(&repository_path, bare, false) | |
| 74 | + .context("could not reopen repository after materializing time-travel")?; | |
| 75 | + super::print_ref_rewrites(&repository, &ref_rewrites)?; | |
| 64 | 76 | anyhow::bail!("{notice}"); |
| 65 | 77 | } |
| 66 | 78 | crate::edit::time_travel::Perform::Conflict(_) => { |
| @@ -24,6 +24,7 @@ use super::stash::SavedStash; · 23 unmodified lines | ||
| 24 | 24 | pub(crate) enum Perform { |
| 25 | 25 | Complete { |
| 26 | 26 | notice: Option<String>, |
| 27 | + selected: ObjectId, | |
| 27 | 28 | ref_rewrites: Vec<super::rebase::RefRewrite>, |
| 28 | 29 | }, |
| 29 | 30 | Conflict(Conflict), |
| @@ -477,7 +478,11 @@ pub(crate) fn perform_reporting_rebased( · 447 unmodified lines | ||
| 477 | 478 | super::stash::apply(repository_path, bare, &workdir, stash)?, |
| 478 | 479 | ); |
| 479 | 480 | } |
| 480 | − Ok(Perform::Complete { notice, ref_rewrites }) | |
| 481 | + Ok(Perform::Complete { | |
| 482 | + notice, | |
| 483 | + selected, | |
| 484 | + ref_rewrites, | |
| 485 | + }) | |
| 481 | 486 | } |
| 482 | 487 | |
| 483 | 488 | #[derive(Clone, Debug, Eq, PartialEq)] |
| @@ -84,6 +84,7 @@ pub(crate) fn prepare( · 83 unmodified lines | ||
| 84 | 84 | commits: &[Commit], |
| 85 | 85 | resolved_tips: &[ObjectId], |
| 86 | 86 | onto_kind: OntoKind, |
| 87 | + show_change_ids: bool, | |
| 87 | 88 | ) -> Result<Prepared> { |
| 88 | 89 | repo.find_commit(base) |
| 89 | 90 | .context("could not find the selected rebase base")?; |
| @@ -160,11 +161,14 @@ pub(crate) fn prepare( · 70 unmodified lines | ||
| 160 | 161 | ref_points.dedup(); |
| 161 | 162 | let expected_refs = rebase::capture_refs(repo, &ref_points, &tips)?; |
| 162 | 163 | |
| 163 | − let source = short(repo, base)?; | |
| 164 | + let source = short(repo, base, show_change_ids)?; | |
| 164 | 165 | let title = if base == onto { |
| 165 | 166 | format!("# Rebase from `{source}`") |
| 166 | 167 | } else { |
| 167 | − format!("# Rebase from `{source}` onto `{}`", short(repo, onto)?) | |
| 168 | + format!( | |
| 169 | + "# Rebase from `{source}` onto `{}`", | |
| 170 | + short(repo, onto, show_change_ids)? | |
| 171 | + ) | |
| 168 | 172 | }; |
| 169 | 173 | let state = State { |
| 170 | 174 | base, |
| @@ -206,6 +210,7 @@ pub(crate) fn prepare( · 35 unmodified lines | ||
| 206 | 210 | repo, |
| 207 | 211 | section.parent, |
| 208 | 212 | (section.parent == onto).then_some((anchor_kind, anchor_title.as_str())), |
| 213 | + show_change_ids, | |
| 209 | 214 | )?; |
| 210 | 215 | if !scope_set.contains(§ion.parent) { |
| 211 | 216 | write_refs_at(&mut body, &state.expected_refs, section.parent)?; |
| @@ -218,12 +223,25 @@ pub(crate) fn prepare( · 6 unmodified lines | ||
| 218 | 223 | "pick" |
| 219 | 224 | }; |
| 220 | 225 | let states = commit_states(repo, *id)?; |
| 221 | − body.extend_from_slice(format!("`{verb} {}` {states}{}\n", short(repo, *id)?, commit.info).as_bytes()); | |
| 226 | + body.extend_from_slice( | |
| 227 | + format!( | |
| 228 | + "`{verb} {}` {states}{}\n", | |
| 229 | + short(repo, *id, show_change_ids)?, | |
| 230 | + commit.info | |
| 231 | + ) | |
| 232 | + .as_bytes(), | |
| 233 | + ); | |
| 222 | 234 | write_refs_at(&mut body, &state.expected_refs, *id)?; |
| 223 | 235 | } |
| 224 | 236 | } |
| 225 | 237 | if sections.is_empty() { |
| 226 | − write_fork_heading(&mut body, repo, onto, Some((anchor_kind, anchor_title.as_str())))?; | |
| 238 | + write_fork_heading( | |
| 239 | + &mut body, | |
| 240 | + repo, | |
| 241 | + onto, | |
| 242 | + Some((anchor_kind, anchor_title.as_str())), | |
| 243 | + show_change_ids, | |
| 244 | + )?; | |
| 227 | 245 | write_refs_at(&mut body, &state.expected_refs, onto)?; |
| 228 | 246 | } |
| 229 | 247 | write_bottom_up(&mut document, &body)?; |
| @@ -239,6 +257,7 @@ pub(crate) fn prepare_continuation( · 9 unmodified lines | ||
| 239 | 257 | repo: &gix::Repository, |
| 240 | 258 | plan: &rebase::Plan, |
| 241 | 259 | tips: Vec<ObjectId>, |
| 260 | + show_change_ids: bool, | |
| 242 | 261 | ) -> Result<Prepared> { |
| 243 | 262 | let resolved = plan.steps.iter().find_map(|step| match step.commit { |
| 244 | 263 | rebase::PlanCommit::Resolved(id) => Some(id), |
| @@ -283,6 +302,7 @@ pub(crate) fn prepare_continuation( · 38 unmodified lines | ||
| 283 | 302 | repo, |
| 284 | 303 | parent, |
| 285 | 304 | (parent == plan.base).then_some(("base", base_title.as_str())), |
| 305 | + show_change_ids, | |
| 286 | 306 | )?; |
| 287 | 307 | if matches!(step.parent, rebase::PlanParent::Existing(_)) { |
| 288 | 308 | write_plan_refs_at(&mut body, &plan.expected_refs, step.parent)?; |
| @@ -300,9 +320,17 @@ pub(crate) fn prepare_continuation( · 11 unmodified lines | ||
| 300 | 320 | match step.commit { |
| 301 | 321 | rebase::PlanCommit::Pick(id) | rebase::PlanCommit::Resolved(id) => { |
| 302 | 322 | let value = if matches!(step.commit, rebase::PlanCommit::Resolved(_)) { |
| 303 | − ObjectId::null(id.kind()).to_string() | |
| 323 | + let hash = ObjectId::null(id.kind()).to_string(); | |
| 324 | + if show_change_ids { | |
| 325 | + format!( | |
| 326 | + "{hash} {}", | |
| 327 | + crate::change_id::for_commit(repo, id)?.to_reverse_hex_with_len(hash.len()) | |
| 328 | + ) | |
| 329 | + } else { | |
| 330 | + hash | |
| 331 | + } | |
| 304 | 332 | } else { |
| 305 | − short(repo, id)? | |
| 333 | + short(repo, id, show_change_ids)? | |
| 306 | 334 | }; |
| 307 | 335 | let title = anchor_title(repo, id)?; |
| 308 | 336 | body.extend_from_slice( |
| @@ -318,7 +346,7 @@ pub(crate) fn prepare_continuation( · 9 unmodified lines | ||
| 318 | 346 | body.extend_from_slice( |
| 319 | 347 | format!( |
| 320 | 348 | "`squash {}` {}{}\n", |
| 321 | − short(repo, *id)?, | |
| 349 | + short(repo, *id, show_change_ids)?, | |
| 322 | 350 | commit_states(repo, *id)?, |
| 323 | 351 | title |
| 324 | 352 | ) |
| @@ -521,8 +549,9 @@ fn write_fork_heading( · 196 unmodified lines | ||
| 521 | 549 | repo: &gix::Repository, |
| 522 | 550 | id: ObjectId, |
| 523 | 551 | annotation: Option<(&str, &str)>, |
| 552 | + show_change_ids: bool, | |
| 524 | 553 | ) -> Result<()> { |
| 525 | − out.extend_from_slice(format!("fork {}", short(repo, id)?).as_bytes()); | |
| 554 | + out.extend_from_slice(format!("fork {}", short(repo, id, show_change_ids)?).as_bytes()); | |
| 526 | 555 | if let Some((kind, title)) = annotation { |
| 527 | 556 | out.extend_from_slice(format!(" ({kind}) {title}").as_bytes()); |
| 528 | 557 | } |
| @@ -573,12 +602,16 @@ fn walk(id: ObjectId, children: &HashMap<ObjectId, Vec<ObjectId>>, section: &mut · 44 unmodified lines | ||
| 573 | 602 | } |
| 574 | 603 | } |
| 575 | 604 | |
| 576 | −fn short(repo: &gix::Repository, id: ObjectId) -> Result<String> { | |
| 577 | − Ok(id | |
| 578 | − .attach(repo) | |
| 579 | − .shorten() | |
| 580 | − .context("could not shorten a rebase todo ID")? | |
| 581 | − .to_string()) | |
| 605 | +fn short(repo: &gix::Repository, id: ObjectId, show_change_id: bool) -> Result<String> { | |
| 606 | + if show_change_id { | |
| 607 | + crate::change_id::display_short(repo, id).context("could not format a rebase todo ID") | |
| 608 | + } else { | |
| 609 | + Ok(id | |
| 610 | + .attach(repo) | |
| 611 | + .shorten() | |
| 612 | + .context("could not shorten a rebase todo ID")? | |
| 613 | + .to_string()) | |
| 614 | + } | |
| 582 | 615 | } |
| 583 | 616 | |
| 584 | 617 | fn parse_state(repo: &gix::Repository, input: &str) -> Result<Option<State>> { |
| @@ -1178,7 +1211,7 @@ mod tests { · 593 unmodified lines | ||
| 1178 | 1211 | commits: &[Commit], |
| 1179 | 1212 | _head: Option<ObjectId>, |
| 1180 | 1213 | ) -> Result<Prepared> { |
| 1181 | − prepare(repo, base, onto, commits, &[], OntoKind::UpdatedBase) | |
| 1214 | + prepare(repo, base, onto, commits, &[], OntoKind::UpdatedBase, true) | |
| 1182 | 1215 | } |
| 1183 | 1216 | |
| 1184 | 1217 | fn parse_plan(repo: &gix::Repository, document: &[u8]) -> Result<rebase::Plan> { |
| @@ -1213,8 +1246,14 @@ mod tests { · 28 unmodified lines | ||
| 1213 | 1246 | "the first line explains that saving unchanged is a no-op" |
| 1214 | 1247 | ); |
| 1215 | 1248 | assert!(document.contains(STATE_START), "the todo carries its transaction state"); |
| 1216 | − assert!(document.contains(&format!("# Rebase from `{}`", base.to_hex_with_len(7)))); | |
| 1217 | − assert!(document.contains(&format!("fork {} (base) base", base.to_hex_with_len(7)))); | |
| 1249 | + assert!(document.contains(&format!( | |
| 1250 | + "# Rebase from `{}`", | |
| 1251 | + crate::change_id::display_short(&repo, base)? | |
| 1252 | + ))); | |
| 1253 | + assert!(document.contains(&format!( | |
| 1254 | + "fork {} (base) base", | |
| 1255 | + crate::change_id::display_short(&repo, base)? | |
| 1256 | + ))); | |
| 1218 | 1257 | let middle = document.find("`pick ").expect("the oldest pick is shown"); |
| 1219 | 1258 | let tip = document.find("`@pick ").expect("HEAD is marked"); |
| 1220 | 1259 | let base = document.find("fork ").expect("the base separator is shown"); |
| @@ -1450,9 +1489,12 @@ mod tests { · 229 unmodified lines | ||
| 1450 | 1489 | |
| 1451 | 1490 | let prepared = prepare_test(&repo, base, base, &commits, Some(tip))?; |
| 1452 | 1491 | let document = String::from_utf8(prepared.document.clone())?; |
| 1453 | − assert!(document.contains(&format!("fork {} (base) base", base.to_hex_with_len(7)))); | |
| 1492 | + assert!(document.contains(&format!( | |
| 1493 | + "fork {} (base) base", | |
| 1494 | + crate::change_id::display_short(&repo, base)? | |
| 1495 | + ))); | |
| 1454 | 1496 | assert!( |
| 1455 | − document.contains(&format!("fork {} ", middle.to_hex_with_len(7))), | |
| 1497 | + document.contains(&format!("fork {} ", crate::change_id::display_short(&repo, middle)?)), | |
| 1456 | 1498 | "a fork within the editable tree has no external-anchor annotation" |
| 1457 | 1499 | ); |
| 1458 | 1500 | let plan = parse_plan(&repo, document.as_bytes())?; |
| @@ -1483,15 +1525,15 @@ mod tests { · 24 unmodified lines | ||
| 1483 | 1525 | assert!( |
| 1484 | 1526 | document.contains(&format!( |
| 1485 | 1527 | "# Rebase from `{}` onto `{}`", |
| 1486 | − base.to_hex_with_len(7), | |
| 1487 | − onto.to_hex_with_len(7) | |
| 1528 | + crate::change_id::display_short(&repo, base)?, | |
| 1529 | + crate::change_id::display_short(&repo, onto)? | |
| 1488 | 1530 | )), |
| 1489 | 1531 | "the update target is explicit in the document title" |
| 1490 | 1532 | ); |
| 1491 | 1533 | assert!( |
| 1492 | 1534 | document.contains(&format!( |
| 1493 | 1535 | "fork {} (updated-base) [A] [N] updated * _ [hidden] <base> `raw` \\ base", |
| 1494 | − onto.to_hex_with_len(7) | |
| 1536 | + crate::change_id::display_short(&repo, onto)? | |
| 1495 | 1537 | )), |
| 1496 | 1538 | "the unfamiliar fork target carries its raw UI title" |
| 1497 | 1539 | ); |
| @@ -1627,6 +1669,7 @@ mod tests { · 129 unmodified lines | ||
| 1627 | 1669 | }], |
| 1628 | 1670 | }, |
| 1629 | 1671 | vec![middle], |
| 1672 | + true, | |
| 1630 | 1673 | )?; |
| 1631 | 1674 | assert!( |
| 1632 | 1675 | prepared |
| @@ -1642,7 +1685,7 @@ mod tests { · 9 unmodified lines | ||
| 1642 | 1685 | ); |
| 1643 | 1686 | assert!(document.contains(&"0".repeat(40)), "the conflict uses the full null ID"); |
| 1644 | 1687 | assert!( |
| 1645 | − document.contains(&format!("`squash {}`", tip.to_hex_with_len(7))), | |
| 1688 | + document.contains(&format!("`squash {}`", crate::change_id::display_short(&repo, tip)?)), | |
| 1646 | 1689 | "unapplied squash sources remain editable" |
| 1647 | 1690 | ); |
| 1648 | 1691 | let plan = parse_plan(&repo, &prepared.document)?; |
| @@ -3881,6 +3881,7 @@ fn rebase_history( · 3880 unmodified lines | ||
| 3881 | 3881 | &commits, |
| 3882 | 3882 | &[], |
| 3883 | 3883 | edit::todo::OntoKind::UpdatedBase, |
| 3884 | + false, | |
| 3884 | 3885 | )?; |
| 3885 | 3886 | (prepared, editor) |
| 3886 | 3887 | }; |
| @@ -1512,13 +1512,17 @@ pub(crate) fn render_full( · 1511 unmodified lines | ||
| 1512 | 1512 | )?; |
| 1513 | 1513 | let graph = graph.ok_or_else(|| anyhow::anyhow!("history traversal did not produce a graph"))?; |
| 1514 | 1514 | let decorations = decorations.unwrap_or_default(); |
| 1515 | − Ok(render_overview( | |
| 1516 | − &Overview::new(&graph, &refs, &decorations, show_tags), | |
| 1517 | − unicode, | |
| 1518 | − )) | |
| 1515 | + let overview = Overview::new(&graph, &refs, &decorations, show_tags); | |
| 1516 | + let labels = overview | |
| 1517 | + .nodes | |
| 1518 | + .iter() | |
| 1519 | + .filter(|node| node.raw_tip && node.decorations.is_empty()) | |
| 1520 | + .map(|node| Ok((node.id, crate::change_id::display(repository, node.id, 7)?))) | |
| 1521 | + .collect::<anyhow::Result<HashMap<_, _>>>()?; | |
| 1522 | + Ok(render_overview(&overview, unicode, &labels)) | |
| 1519 | 1523 | } |
| 1520 | 1524 | |
| 1521 | −fn render_overview(overview: &Overview, unicode: bool) -> String { | |
| 1525 | +fn render_overview(overview: &Overview, unicode: bool, commit_labels: &HashMap<ObjectId, String>) -> String { | |
| 1522 | 1526 | if overview.nodes.is_empty() { |
| 1523 | 1527 | return String::new(); |
| 1524 | 1528 | } |
| @@ -1548,7 +1552,11 @@ fn render_overview(overview: &Overview, unicode: bool) -> String { · 23 unmodified lines | ||
| 1548 | 1552 | out.push_str(&lane); |
| 1549 | 1553 | out.extend(std::iter::repeat_n(' ', placed.rail_width.saturating_sub(width))); |
| 1550 | 1554 | if let Some(node) = node { |
| 1551 | − let label = rail_label(&overview.nodes[node], None); | |
| 1555 | + let node = &overview.nodes[node]; | |
| 1556 | + let label = commit_labels | |
| 1557 | + .get(&node.id) | |
| 1558 | + .cloned() | |
| 1559 | + .unwrap_or_else(|| rail_label(node, None)); | |
| 1552 | 1560 | if unicode { |
| 1553 | 1561 | out.push_str(&label); |
| 1554 | 1562 | } else { |
| @@ -2043,8 +2051,8 @@ mod tests { · 488 unmodified lines | ||
| 2043 | 2051 | !overview.nodes.iter().any(|node| node.id == id(5)), |
| 2044 | 2052 | "ordinary pin-only tips are absent" |
| 2045 | 2053 | ); |
| 2046 | − let unicode = render_overview(overview, true); | |
| 2047 | − let ascii = render_overview(overview, false); | |
| 2054 | + let unicode = render_overview(overview, true, &HashMap::new()); | |
| 2055 | + let ascii = render_overview(overview, false, &HashMap::new()); | |
| 2048 | 2056 | assert_eq!(unicode.matches('📌').count(), 1, "detached HEAD has one marker"); |
| 2049 | 2057 | assert!( |
| 2050 | 2058 | unicode.contains("★main"), |
| @@ -2414,7 +2422,7 @@ mod tests { · 363 unmodified lines | ||
| 2414 | 2422 | fn full_rendering_is_unstyled_ascii() { |
| 2415 | 2423 | let (graph, refs, decorations) = fixture(); |
| 2416 | 2424 | let overview = Overview::new(&graph, &refs, &decorations, true); |
| 2417 | − let rendered = render_overview(&overview, false); | |
| 2425 | + let rendered = render_overview(&overview, false, &HashMap::new()); | |
| 2418 | 2426 | |
| 2419 | 2427 | assert!(rendered.contains("main"), "HEAD is rendered without selection state"); |
| 2420 | 2428 | assert!(rendered.contains("topic"), "ordinary nodes retain their label"); |
| @@ -2424,12 +2432,28 @@ mod tests { · 3 unmodified lines | ||
| 2424 | 2432 | assert!(!rendered.contains('\u{1b}'), "plain output has no terminal styles"); |
| 2425 | 2433 | } |
| 2426 | 2434 | |
| 2435 | + #[test] | |
| 2436 | + fn full_rendering_pairs_raw_commit_tips_with_change_ids() -> gix_testtools::Result { | |
| 2437 | + let fixture = gix_testtools::scripted_fixture_writable("history.sh")?; | |
| 2438 | + let repo = crate::test_repository::open(fixture.path())?; | |
| 2439 | + let mut commit = repo.head_commit()?.decode()?.into_owned()?; | |
| 2440 | + commit.message = "unreferenced raw tip".into(); | |
| 2441 | + let id = repo.write_object(&commit)?.detach(); | |
| 2442 | + let rendered = render_full(&repo, &[id.to_string().into()], &[], true, false)?; | |
| 2443 | + | |
| 2444 | + assert!( | |
| 2445 | + rendered.contains(&crate::change_id::display(&repo, id, 7)?), | |
| 2446 | + "a displayed raw hash is immediately followed by its change ID: {rendered:?}" | |
| 2447 | + ); | |
| 2448 | + Ok(()) | |
| 2449 | + } | |
| 2450 | + | |
| 2427 | 2451 | #[test] |
| 2428 | 2452 | fn rail_layout_uses_rounded_unicode_connections_and_ascii_fallbacks() { |
| 2429 | 2453 | let (graph, refs, decorations) = fixture(); |
| 2430 | 2454 | let overview = Overview::new(&graph, &refs, &decorations, true); |
| 2431 | − let unicode = render_overview(&overview, true); | |
| 2432 | − let ascii = render_overview(&overview, false); | |
| 2455 | + let unicode = render_overview(&overview, true, &HashMap::new()); | |
| 2456 | + let ascii = render_overview(&overview, false, &HashMap::new()); | |
| 2433 | 2457 | |
| 2434 | 2458 | assert!( |
| 2435 | 2459 | unicode.contains("│ ● topic"), |