Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Implement `rad diff` command
cloudhead committed 2 years ago
commit dc7e126c85543f9e5d90854b9607c608c93eb895
parent 5442e6699d322ecb1b35d9448b0d305f71333276
3 files changed +154 -0
modified radicle-cli/src/commands.rs
@@ -10,6 +10,8 @@ pub mod rad_clone;
pub mod rad_cob;
#[path = "commands/delegate.rs"]
pub mod rad_delegate;
+
#[path = "commands/diff.rs"]
+
pub mod rad_diff;
#[path = "commands/edit.rs"]
pub mod rad_edit;
#[path = "commands/fork.rs"]
added radicle-cli/src/commands/diff.rs
@@ -0,0 +1,145 @@
+
use std::ffi::OsString;
+

+
use anyhow::anyhow;
+

+
use radicle::git;
+
use radicle::rad;
+
use radicle_surf as surf;
+

+
use crate::git::pretty_diff::ToPretty as _;
+
use crate::git::Rev;
+
use crate::terminal as term;
+
use crate::terminal::args::{Args, Error, Help};
+
use crate::terminal::highlight::Highlighter;
+
use crate::terminal::{Constraint, Element as _};
+

+
pub const HELP: Help = Help {
+
    name: "diff",
+
    description: "Show changes between commits",
+
    version: env!("CARGO_PKG_VERSION"),
+
    usage: r#"
+
Usage
+

+
    rad diff [<commit>] [--staged] [<option>...]
+
    rad diff <commit> [<commit>] [<option>...]
+

+
    This command is meant to operate as closely as possible to `git diff`,
+
    except its output is optimized for human-readability.
+

+
Options
+

+
    --staged        View staged changes
+
    --help          Print help
+
"#,
+
};
+

+
pub struct Options {
+
    pub commits: Vec<Rev>,
+
    pub staged: bool,
+
    pub unified: usize,
+
}
+

+
impl Args for Options {
+
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
+
        use lexopt::prelude::*;
+

+
        let mut parser = lexopt::Parser::from_args(args);
+
        let mut commits = Vec::new();
+
        let mut staged = false;
+
        let mut unified = 5;
+

+
        while let Some(arg) = parser.next()? {
+
            match arg {
+
                Long("unified") | Short('U') => {
+
                    let val = parser.value()?;
+
                    unified = term::args::number(&val)?;
+
                }
+
                Long("staged") | Long("cached") => staged = true,
+
                Long("help") | Short('h') => return Err(Error::Help.into()),
+
                Value(val) => {
+
                    let rev = term::args::rev(&val)?;
+

+
                    commits.push(rev);
+
                }
+
                _ => return Err(anyhow::anyhow!(arg.unexpected())),
+
            }
+
        }
+

+
        Ok((
+
            Options {
+
                commits,
+
                staged,
+
                unified,
+
            },
+
            vec![],
+
        ))
+
    }
+
}
+

+
pub fn run(options: Options, _ctx: impl term::Context) -> anyhow::Result<()> {
+
    let (repo, _) = rad::cwd()?;
+
    let oids = options
+
        .commits
+
        .into_iter()
+
        .map(|rev| {
+
            repo.revparse_single(rev.as_str())
+
                .map_err(|e| anyhow!("unknown object {rev}: {e}"))
+
                .and_then(|o| {
+
                    o.into_commit()
+
                        .map_err(|_| anyhow!("object {rev} is not a commit"))
+
                })
+
        })
+
        .collect::<Result<Vec<_>, _>>()?;
+

+
    let mut opts = git::raw::DiffOptions::new();
+
    opts.patience(true)
+
        .minimal(true)
+
        .context_lines(options.unified as u32);
+

+
    let mut find_opts = git::raw::DiffFindOptions::new();
+
    find_opts.exact_match_only(true);
+
    find_opts.all(true);
+
    find_opts.copies(false); // We don't support finding copies at the moment.
+

+
    let mut diff = match oids.as_slice() {
+
        [] => {
+
            if options.staged {
+
                let head = repo.head()?.peel_to_tree()?;
+
                // HEAD vs. index.
+
                repo.diff_tree_to_index(Some(&head), None, Some(&mut opts))
+
            } else {
+
                // Working tree vs. index.
+
                repo.diff_index_to_workdir(None, None)
+
            }
+
        }
+
        [commit] => {
+
            let commit = commit.tree()?;
+
            if options.staged {
+
                // Commit vs. index.
+
                repo.diff_tree_to_index(Some(&commit), None, Some(&mut opts))
+
            } else {
+
                // Commit vs. working tree.
+
                repo.diff_tree_to_workdir(Some(&commit), Some(&mut opts))
+
            }
+
        }
+
        [left, right] => {
+
            // Commit vs. commit.
+
            let left = left.tree()?;
+
            let right = right.tree()?;
+

+
            repo.diff_tree_to_tree(Some(&left), Some(&right), Some(&mut opts))
+
        }
+
        _ => {
+
            anyhow::bail!("Too many commits given. See `rad diff --help` for usage.");
+
        }
+
    }?;
+
    diff.find_similar(Some(&mut find_opts))?;
+

+
    let diff = surf::diff::Diff::try_from(diff)?;
+
    let mut hi = Highlighter::default();
+
    let pretty = diff.pretty(&mut hi, &(), &repo);
+

+
    pretty.write(Constraint::from_env().maximize());
+

+
    Ok(())
+
}
modified radicle-cli/src/main.rs
@@ -139,6 +139,13 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
                args.to_vec(),
            );
        }
+
        "diff" => {
+
            term::run_command_args::<rad_diff::Options, _>(
+
                rad_diff::HELP,
+
                rad_diff::run,
+
                args.to_vec(),
+
            );
+
        }
        "edit" => {
            term::run_command_args::<rad_edit::Options, _>(
                rad_edit::HELP,