grit/command/
since.rs

1use std::{env, ffi};
2
3use crate::{
4    error::{Error, Result},
5    git::git,
6    trunk,
7};
8
9struct Args {
10    /// Arguments to be forwarded to git.
11    git_args: Vec<ffi::OsString>,
12    /// The base branch name, if specified.
13    base: Option<ffi::OsString>,
14}
15
16impl Args {
17    fn new(our_args: env::ArgsOs) -> Result<Args> {
18        let mut base: Option<ffi::OsString> = None;
19        let mut git_args = [
20            "log",
21            "--color=always",
22            "--decorate",
23            "--first-parent",
24            "--graph",
25            "--oneline",
26        ]
27        .map(ffi::OsString::from)
28        .to_vec();
29        for os in our_args {
30            let Some(s) = os.to_str() else {
31                return Err(Error::Arg(os));
32            };
33            if s.starts_with('-') {
34                git_args.push(os);
35            } else if base.is_none() {
36                base = Some(os);
37            } else {
38                return Err(Error::Arg(os));
39            }
40        }
41        Ok(Args { git_args, base })
42    }
43}
44
45/// Lists commmits reachable from HEAD, but not from a specified base branch
46/// (which defaults to the local trunk).
47///
48/// TODO: Command-line arguments are fundamentally [`ffi::OsString`], not
49///  [`String`]. Converting the former to the latter and back, to build `git(1)`
50///  range arguments, is potentially lossy. Avoid it by concatenating byte
51///  encoded vectors rather than formatting strings.
52///
53/// # Errors
54///
55/// Returns an error if the base branch is unspecified, and a trunk cannot be
56/// identified; or, if `git` returns bad status.
57pub async fn short(our_args: env::ArgsOs) -> Result<()> {
58    let Args { mut git_args, base } = Args::new(our_args)?;
59    let range = match base {
60        Some(some) => format!("{}..", some.display()),
61        None => format!("{}..", trunk::local().await?),
62    };
63    git_args.push(range.into());
64    print!("{}", git(git_args).await?);
65    Ok(())
66}
67
68/// Summarizes changes to the working copy (relative to HEAD), then lists
69/// commmits from trunk (or a specified base) to HEAD, inclusive.
70///
71/// # Errors
72///
73/// Returns an error if the base branch cannot be identified, or `git` fails.
74pub async fn long(our_args: env::ArgsOs) -> Result<()> {
75    let Args { mut git_args, base } = Args::new(our_args)?;
76    print!("{}", git(["diff", "--stat"]).await?);
77    let range = match base {
78        Some(some) => format!("{}^..", some.display()),
79        None => format!("{}^..", trunk::local().await?),
80    };
81    git_args.push(range.into());
82    print!("{}", git(git_args).await?);
83    Ok(())
84}