From 9df09b41c7fe645ab631179504c54a1a0b936327 Mon Sep 17 00:00:00 2001 From: Blair Conrad Date: Wed, 10 Sep 2025 21:32:37 -0400 Subject: [PATCH 1/5] Remove timestamp and log level from announcements --- src/main.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 59e88cc..38daea4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,10 @@ extern crate slog; use clap::{CommandFactory, Parser as _}; use clap_complete::{generate, Shell}; use clap_complete_nushell::Nushell; -use slog::Drain; +use slog::{Drain, Record}; +use slog_term::{CountingWriter, RecordDecorator, ThreadSafeTimestampFn}; use std::io; +use std::io::Write; /// Automatically absorb staged changes into your current branch #[derive(Debug, clap::Parser)] @@ -85,7 +87,10 @@ fn main() { } let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); + let drain = slog_term::FullFormat::new(decorator) + .use_custom_header_print(print_msg_header) + .build() + .fuse(); let drain = std::sync::Mutex::new(drain).fuse(); let drain = slog::LevelFilter::new( @@ -127,3 +132,15 @@ fn main() { ::std::process::exit(1); } } + +pub fn print_msg_header( + _fn_timestamp: &dyn ThreadSafeTimestampFn>, + mut rd: &mut dyn RecordDecorator, + record: &Record, + _use_file_location: bool, +) -> io::Result { + rd.start_level()?; // color-codes the message + let mut count_rd = CountingWriter::new(&mut rd); + write!(count_rd, "{}", record.msg())?; + Ok(count_rd.count() != 0) +} From 22fa578939bd57ec46656188802e3a8316c6ae41 Mon Sep 17 00:00:00 2001 From: Ariel Fridman Date: Fri, 11 Apr 2025 18:56:55 +0300 Subject: [PATCH 2/5] Use short commit ids in announcements --- src/lib.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 156c1b5..3270255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -592,12 +592,18 @@ enum Announcement<'r> { fn announce(logger: &slog::Logger, announcement: Announcement) { match announcement { - Announcement::Committed(commit, diff) => info!( - logger, - "committed"; - "commit" => &commit.id().to_string(), - "header" => format!("+{},-{}", &diff.insertions(), &diff.deletions()) - ), + Announcement::Committed(commit, diff) => { + let commit_short_id = commit.as_object().short_id().unwrap(); + let commit_short_id = commit_short_id + .as_str() + .expect("the commit short id is always a valid ASCII string"); + info!( + logger, + "committed"; + "commit" => commit_short_id, + "header" => format!("+{},-{}", diff.insertions(), diff.deletions()), + ); + } Announcement::WouldHaveCommitted(fixup, diff) => info!( logger, "would have committed"; From 536a6d4608e757cb5ccf37fcca62556590fe5e37 Mon Sep 17 00:00:00 2001 From: Ariel Fridman Date: Fri, 11 Apr 2025 18:58:14 +0300 Subject: [PATCH 3/5] Announce the target commit's summary If using absorb.fixupTargetAlwaysSHA, the original commit hash will be shown instead. --- src/lib.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3270255..be97093 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -337,7 +337,10 @@ fn run_with_repo(logger: &slog::Logger, config: &Config, repo: &git2::Repository &head_tree, &[&head_commit], )?)?; - announce(logger, Announcement::Committed(&head_commit, &diff)); + announce( + logger, + Announcement::Committed(&head_commit, dest_commit_locator, &diff), + ); } else { announce( logger, @@ -572,7 +575,7 @@ fn index_stats(repo: &git2::Repository) -> Result { // Messages that will be shown to users during normal operations (not debug messages). enum Announcement<'r> { - Committed(&'r git2::Commit<'r>, &'r git2::DiffStats), + Committed(&'r git2::Commit<'r>, &'r str, &'r git2::DiffStats), WouldHaveCommitted(&'r str, &'r git2::DiffStats), WouldHaveRebased(&'r std::process::Command), HowToSquash(String), @@ -592,7 +595,7 @@ enum Announcement<'r> { fn announce(logger: &slog::Logger, announcement: Announcement) { match announcement { - Announcement::Committed(commit, diff) => { + Announcement::Committed(commit, destination, diff) => { let commit_short_id = commit.as_object().short_id().unwrap(); let commit_short_id = commit_short_id .as_str() @@ -600,6 +603,7 @@ fn announce(logger: &slog::Logger, announcement: Announcement) { info!( logger, "committed"; + "fixup" => destination, "commit" => commit_short_id, "header" => format!("+{},-{}", diff.insertions(), diff.deletions()), ); @@ -738,8 +742,8 @@ mod tests { log_utils::assert_log_messages_are( capturing_logger.visible_logs(), vec![ - &json!({"level": "INFO", "msg": "committed"}), - &json!({"level": "INFO", "msg": "committed"}), + &json!({"level": "INFO", "msg": "committed", "fixup": "Initial commit."}), + &json!({"level": "INFO", "msg": "committed", "fixup": "Initial commit."}), &json!({ "level": "INFO", "msg": "To squash the new commits, rebase:", @@ -1171,7 +1175,7 @@ mod tests { log_utils::assert_log_messages_are( capturing_logger.visible_logs(), vec![ - &json!({"level": "INFO", "msg": "committed"}), + &json!({"level": "INFO", "msg": "committed","fixup": "Initial commit.",}), &json!({ "level": "INFO", "msg": "To squash the new commits, rebase:", @@ -1606,11 +1610,11 @@ mod tests { vec![ &json!({ "level": "INFO", - "msg": "would have committed", "fixup": "Initial commit.", + "msg": "would have committed", "fixup": "Initial commit." }), &json!({ "level": "INFO", - "msg": "would have committed", "fixup": "Initial commit.", + "msg": "would have committed", "fixup": "Initial commit." }), ], ); From b4a0c386bc080d5ac81870ac460d242a723f6204 Mon Sep 17 00:00:00 2001 From: Blair Conrad Date: Thu, 11 Sep 2025 19:21:57 -0400 Subject: [PATCH 4/5] Characterize header message format --- src/lib.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index be97093..e0497e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -689,6 +689,7 @@ mod tests { use git2::message_trailers_strs; use serde_json::json; use std::path::PathBuf; + use tests::repo_utils::add; use super::*; mod log_utils; @@ -753,6 +754,101 @@ mod tests { ); } + #[test] + fn one_deletion() { + let (ctx, file_path) = repo_utils::prepare_repo(); + std::fs::write( + ctx.join(&file_path), + br#" +line +line +"#, + ) + .unwrap(); + add(&ctx.repo, &file_path); + + let actual_pre_absorb_commit = ctx.repo.head().unwrap().peel_to_commit().unwrap().id(); + + // run 'git-absorb' + let mut capturing_logger = log_utils::CapturingLogger::new(); + run_with_repo(&capturing_logger.logger, &DEFAULT_CONFIG, &ctx.repo).unwrap(); + + let mut revwalk = ctx.repo.revwalk().unwrap(); + revwalk.push_head().unwrap(); + assert_eq!(revwalk.count(), 2); + + assert!(nothing_left_in_index(&ctx.repo).unwrap()); + + let pre_absorb_ref_commit = ctx.repo.refname_to_id("PRE_ABSORB_HEAD").unwrap(); + assert_eq!(pre_absorb_ref_commit, actual_pre_absorb_commit); + + log_utils::assert_log_messages_are( + capturing_logger.visible_logs(), + vec![ + &json!({ + "level": "INFO", + "msg": "committed", + "fixup": "Initial commit.", + "header": "+0,-3", + }), + &json!({ + "level": "INFO", + "msg": "To squash the new commits, rebase:", + "command": "git rebase --interactive --autosquash --autostash --root", + }), + ], + ); + } + + #[test] + fn one_insertion_and_one_deletion() { + let (ctx, file_path) = repo_utils::prepare_repo(); + std::fs::write( + ctx.join(&file_path), + br#" +line +line + +even more +lines +"#, + ) + .unwrap(); + add(&ctx.repo, &file_path); + + let actual_pre_absorb_commit = ctx.repo.head().unwrap().peel_to_commit().unwrap().id(); + + // run 'git-absorb' + let mut capturing_logger = log_utils::CapturingLogger::new(); + run_with_repo(&capturing_logger.logger, &DEFAULT_CONFIG, &ctx.repo).unwrap(); + + let mut revwalk = ctx.repo.revwalk().unwrap(); + revwalk.push_head().unwrap(); + assert_eq!(revwalk.count(), 2); + + assert!(nothing_left_in_index(&ctx.repo).unwrap()); + + let pre_absorb_ref_commit = ctx.repo.refname_to_id("PRE_ABSORB_HEAD").unwrap(); + assert_eq!(pre_absorb_ref_commit, actual_pre_absorb_commit); + + log_utils::assert_log_messages_are( + capturing_logger.visible_logs(), + vec![ + &json!({ + "level": "INFO", + "msg": "committed", + "fixup": "Initial commit.", + "header": "+1,-1", + }), + &json!({ + "level": "INFO", + "msg": "To squash the new commits, rebase:", + "command": "git rebase --interactive --autosquash --autostash --root", + }), + ], + ); + } + #[test] fn exceed_stack_limit_with_modified_hunk() { let (ctx, file_path) = repo_utils::prepare_repo(); @@ -1175,7 +1271,12 @@ mod tests { log_utils::assert_log_messages_are( capturing_logger.visible_logs(), vec![ - &json!({"level": "INFO", "msg": "committed","fixup": "Initial commit.",}), + &json!({ + "level": "INFO", + "msg": "committed", + "fixup": "Initial commit.", + "header": "+3,-0", + }), &json!({ "level": "INFO", "msg": "To squash the new commits, rebase:", @@ -1610,11 +1711,11 @@ mod tests { vec![ &json!({ "level": "INFO", - "msg": "would have committed", "fixup": "Initial commit." + "msg": "would have committed", "fixup": "Initial commit.", "header": "+1,-0" }), &json!({ "level": "INFO", - "msg": "would have committed", "fixup": "Initial commit." + "msg": "would have committed", "fixup": "Initial commit.", "header": "+2,-0" }), ], ); From 8ef9791d0a13fad55249999a375d33aaf2f17ebf Mon Sep 17 00:00:00 2001 From: Blair Conrad Date: Thu, 11 Sep 2025 20:51:39 -0400 Subject: [PATCH 5/5] Announce commit header in human friendly format --- src/lib.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e0497e4..f147596 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ mod config; mod owned; mod stack; +use git2::DiffStats; use std::io::Write; use std::path::Path; @@ -600,19 +601,21 @@ fn announce(logger: &slog::Logger, announcement: Announcement) { let commit_short_id = commit_short_id .as_str() .expect("the commit short id is always a valid ASCII string"); + let change_header = format_change_header(diff); + info!( logger, "committed"; "fixup" => destination, "commit" => commit_short_id, - "header" => format!("+{},-{}", diff.insertions(), diff.deletions()), + "header" => change_header, ); } Announcement::WouldHaveCommitted(fixup, diff) => info!( logger, "would have committed"; "fixup" => fixup, - "header" => format!("+{},-{}", &diff.insertions(), &diff.deletions()) + "header" => format_change_header(diff), ), Announcement::WouldHaveRebased(command) => info!( logger, "would have run git rebase"; "command" => format!("{:?}", command) @@ -684,6 +687,39 @@ fn announce(logger: &slog::Logger, announcement: Announcement) { } } +fn format_change_header(diff: &DiffStats) -> String { + let insertions = diff.insertions(); + let deletions = diff.deletions(); + + let mut header = String::new(); + if insertions > 0 { + header.push_str(&format!( + "{} {}(+)", + insertions, + if insertions == 1 { + "insertion" + } else { + "insertions" + } + )); + } + if deletions > 0 { + if !header.is_empty() { + header.push_str(", "); + } + header.push_str(&format!( + "{} {}(-)", + deletions, + if deletions == 1 { + "deletion" + } else { + "deletions" + } + )); + } + header +} + #[cfg(test)] mod tests { use git2::message_trailers_strs; @@ -743,8 +779,18 @@ mod tests { log_utils::assert_log_messages_are( capturing_logger.visible_logs(), vec![ - &json!({"level": "INFO", "msg": "committed", "fixup": "Initial commit."}), - &json!({"level": "INFO", "msg": "committed", "fixup": "Initial commit."}), + &json!({ + "level": "INFO", + "msg": "committed", + "fixup": "Initial commit.", + "header": "1 insertion(+)", + }), + &json!({ + "level": "INFO", + "msg": "committed", + "fixup": "Initial commit.", + "header": "2 insertions(+)", + }), &json!({ "level": "INFO", "msg": "To squash the new commits, rebase:", @@ -789,7 +835,7 @@ line "level": "INFO", "msg": "committed", "fixup": "Initial commit.", - "header": "+0,-3", + "header": "3 deletions(-)", }), &json!({ "level": "INFO", @@ -838,7 +884,7 @@ lines "level": "INFO", "msg": "committed", "fixup": "Initial commit.", - "header": "+1,-1", + "header": "1 insertion(+), 1 deletion(-)", }), &json!({ "level": "INFO", @@ -1275,7 +1321,7 @@ lines "level": "INFO", "msg": "committed", "fixup": "Initial commit.", - "header": "+3,-0", + "header": "3 insertions(+)", }), &json!({ "level": "INFO", @@ -1711,11 +1757,15 @@ lines vec![ &json!({ "level": "INFO", - "msg": "would have committed", "fixup": "Initial commit.", "header": "+1,-0" + "msg": "would have committed", + "fixup": "Initial commit.", + "header": "1 insertion(+)", }), &json!({ "level": "INFO", - "msg": "would have committed", "fixup": "Initial commit.", "header": "+2,-0" + "msg": "would have committed", + "fixup": "Initial commit.", + "header": "2 insertions(+)", }), ], );