git_adr/cli/
log.rs

1//! Show git log with ADR annotations.
2
3use anyhow::Result;
4use clap::Args as ClapArgs;
5use colored::Colorize;
6
7use crate::core::{ConfigManager, Git, NotesManager};
8
9/// Arguments for the log command.
10#[derive(ClapArgs, Debug)]
11pub struct Args {
12    /// Number of commits to show.
13    #[arg(short = 'n', default_value = "10")]
14    pub count: usize,
15
16    /// Show only commits with linked ADRs.
17    #[arg(long)]
18    pub linked_only: bool,
19
20    /// Commit range or ref (default: HEAD).
21    #[arg(default_value = "HEAD")]
22    pub revision: String,
23}
24
25/// Run the log command.
26///
27/// # Errors
28///
29/// Returns an error if log fails.
30pub fn run(args: Args) -> Result<()> {
31    let git = Git::new();
32    git.check_repository()?;
33
34    let config = ConfigManager::new(git.clone()).load()?;
35    let notes = NotesManager::new(git.clone(), config);
36
37    // Get ADRs indexed by commit
38    let adrs = notes.list()?;
39    let adr_map: std::collections::HashMap<String, Vec<_>> =
40        adrs.into_iter()
41            .fold(std::collections::HashMap::new(), |mut acc, adr| {
42                acc.entry(adr.commit.clone()).or_default().push(adr);
43                acc
44            });
45
46    // Get recent commits
47    let log_output = git.run_output(&[
48        "log",
49        "--format=%H|%h|%s|%an|%ar",
50        &format!("-{}", args.count),
51        &args.revision,
52    ])?;
53
54    let mut displayed = 0;
55
56    for line in log_output.lines() {
57        let parts: Vec<&str> = line.splitn(5, '|').collect();
58        if parts.len() < 5 {
59            continue;
60        }
61
62        let (full_hash, short_hash, subject, author, date) =
63            (parts[0], parts[1], parts[2], parts[3], parts[4]);
64
65        let linked_adrs = adr_map.get(full_hash);
66
67        // Skip if --linked-only and no ADRs
68        if args.linked_only && linked_adrs.is_none() {
69            continue;
70        }
71
72        // Print commit info
73        println!(
74            "{} {} - {} ({}, {})",
75            short_hash.yellow(),
76            subject,
77            author.dimmed(),
78            date.dimmed(),
79            if linked_adrs.is_some() {
80                "📋".to_string()
81            } else {
82                "  ".to_string()
83            }
84        );
85
86        // Print linked ADRs
87        if let Some(adrs) = linked_adrs {
88            for adr in adrs {
89                println!(
90                    "  {} {} [{}] - {}",
91                    "└─".dimmed(),
92                    adr.id.cyan().bold(),
93                    adr.frontmatter.status.to_string().dimmed(),
94                    adr.frontmatter.title
95                );
96            }
97        }
98
99        displayed += 1;
100    }
101
102    if displayed == 0 {
103        eprintln!("{} No commits found", "!".yellow());
104    } else {
105        eprintln!();
106        eprintln!(
107            "{} {} commit(s) shown, {} with ADRs",
108            "→".blue(),
109            displayed,
110            adr_map.len()
111        );
112    }
113
114    Ok(())
115}