Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 175 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod config;
mod owned;
mod stack;

use git2::DiffStats;
use std::io::Write;
use std::path::Path;

Expand Down Expand Up @@ -337,7 +338,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),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output may look funny if people use an overly-long commit summary. We could truncate in case it's long or just say "well, they asked for it..."

);
} else {
announce(
logger,
Expand Down Expand Up @@ -572,7 +576,7 @@ fn index_stats(repo: &git2::Repository) -> Result<git2::DiffStats> {

// 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),
Expand All @@ -592,17 +596,26 @@ 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, destination, 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");
let change_header = format_change_header(diff);

info!(
logger,
"committed";
"fixup" => destination,
"commit" => commit_short_id,
"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)
Expand Down Expand Up @@ -674,11 +687,45 @@ 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;
use serde_json::json;
use std::path::PathBuf;
use tests::repo_utils::add;

use super::*;
mod log_utils;
Expand Down Expand Up @@ -732,8 +779,113 @@ 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.",
"header": "1 insertion(+)",
}),
&json!({
"level": "INFO",
"msg": "committed",
"fixup": "Initial commit.",
"header": "2 insertions(+)",
}),
&json!({
"level": "INFO",
"msg": "To squash the new commits, rebase:",
"command": "git rebase --interactive --autosquash --autostash --root",
}),
],
);
}

#[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": "3 deletions(-)",
}),
&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 insertion(+), 1 deletion(-)",
}),
&json!({
"level": "INFO",
"msg": "To squash the new commits, rebase:",
Expand Down Expand Up @@ -1165,7 +1317,12 @@ 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.",
"header": "3 insertions(+)",
}),
&json!({
"level": "INFO",
"msg": "To squash the new commits, rebase:",
Expand Down Expand Up @@ -1600,11 +1757,15 @@ mod tests {
vec![
&json!({
"level": "INFO",
"msg": "would have committed", "fixup": "Initial commit.",
"msg": "would have committed",
"fixup": "Initial commit.",
"header": "1 insertion(+)",
}),
&json!({
"level": "INFO",
"msg": "would have committed", "fixup": "Initial commit.",
"msg": "would have committed",
"fixup": "Initial commit.",
"header": "2 insertions(+)",
}),
],
);
Expand Down
21 changes: 19 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -127,3 +132,15 @@ fn main() {
::std::process::exit(1);
}
}

pub fn print_msg_header(
_fn_timestamp: &dyn ThreadSafeTimestampFn<Output = io::Result<()>>,
mut rd: &mut dyn RecordDecorator,
record: &Record,
_use_file_location: bool,
) -> io::Result<bool> {
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)
}
Loading