git-editor 2.0.1

A command-line tool to edit git commit timestamps, messages, and author information
Documentation
use crate::utils::types::Result;
use crate::{args::Args, utils::commit_history::get_commit_history};
use chrono::NaiveDateTime;
use colored::Colorize;
use git2::{Repository, Signature, Sort, Time};
use std::collections::HashMap;

pub fn rewrite_all_commits(args: &Args, timestamps: Vec<NaiveDateTime>) -> Result<()> {
    let repo = Repository::open(args.repo_path.as_ref().unwrap())?;
    let head_ref = repo.head()?;
    let branch_name = head_ref
        .shorthand()
        .ok_or("Detached HEAD or invalid branch")?;
    let full_ref = format!("refs/heads/{branch_name}");

    let mut revwalk = repo.revwalk()?;
    revwalk.push_head()?;
    revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
    let mut orig_oids: Vec<_> = revwalk.filter_map(|id| id.ok()).collect();
    orig_oids.reverse();

    let mut new_map: HashMap<git2::Oid, git2::Oid> = HashMap::new();
    let mut last_new_oid = None;

    for (i, &oid) in orig_oids.iter().enumerate() {
        let orig = repo.find_commit(oid)?;
        let tree = orig.tree()?;

        let new_parents: Result<Vec<_>> = orig
            .parent_ids()
            .map(|pid| {
                let new_pid = *new_map.get(&pid).unwrap_or(&pid);
                repo.find_commit(new_pid).map_err(|e| e.into())
            })
            .collect();

        let timestamp: i64 = timestamps[i].and_utc().timestamp();
        let sig = Signature::new(
            args.name.as_ref().unwrap(),
            args.email.as_ref().unwrap(),
            &Time::new(timestamp, 0),
        )?;

        let new_oid = repo.commit(
            None,
            &sig,
            &sig,
            orig.message().unwrap_or_default(),
            &tree,
            &new_parents?.iter().collect::<Vec<_>>(),
        )?;

        new_map.insert(oid, new_oid);
        last_new_oid = Some(new_oid);
    }

    if let Some(new_head) = last_new_oid {
        repo.reference(&full_ref, new_head, true, "history rewritten")?;
        println!(
            "{} '{}' -> {}",
            "Rewritten branch".green(),
            branch_name.cyan(),
            new_head.to_string().cyan()
        );
        if args.show_history {
            get_commit_history(args, true)?;
        }
    }

    Ok(())
}