Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Add `rad patch diff` command
cloudhead committed 2 years ago
commit 91684cc9ca43362db0e546674d53a6322dda039e
parent 04bfbca15d5fd6b8ae5d540a8c3166af463db653
9 files changed +190 -19
modified rad-patch.1.adoc
@@ -15,6 +15,7 @@ rad-patch - Manage radicle patches.
*rad patch* [<option>...] +
*rad patch* _list_ [--all|--merged|--open|--archived|--draft] [<option>...] +
*rad patch* _show_ <patch-id> [<option>...] +
+
*rad patch* _diff_ <patch-id> [--revision <revision-id>] [<option>...] +
*rad patch* _archive_ <patch-id> [<option>...] +
*rad patch* _update_ <patch-id> [<option>...] +
*rad patch* _checkout_ <patch-id> [<option>...] +
@@ -52,6 +53,13 @@ Shows information on the given patch.
*--patch*, *-p*::                    Show the patch changes in git patch format
*--verbose*, *-v*::                  Show additional information about the patch

+
=== diff
+

+
Outputs the patch diff, using Radicle's diffing tool.
+

+
*<patch-id>*::                       The patch to diff
+
*--revision*, *-r <revision-id>*::   The revision to diff (default: latest)
+

=== edit

Edits a patch revision comment. To edit the patch title or description, pass
added radicle-cli/examples/rad-patch-diff.md
@@ -0,0 +1,63 @@
+
Using `rad patch diff`, we can output the patch diff:
+

+
``` ./README.md
+
Hello World!
+
```
+
```
+
$ git checkout -b feature/1
+
$ git add README.md
+
$ git commit -m "Add README" -q
+
$ git push rad HEAD:refs/patches
+
```
+
```
+
$ rad patch diff e432b04
+
╭───────────────────────────╮
+
│ README.md +1 ❲created❳    │
+
├───────────────────────────┤
+
│ @@ -0,0 +1,1 @@           │
+
│      1     + Hello World! │
+
╰───────────────────────────╯
+

+
```
+

+
If we add another file and update the patch, we can see it in the diff.
+

+
``` ./RADICLE.md
+
Hello Radicle!
+
```
+
```
+
$ git add RADICLE.md
+
$ git commit --amend -q
+
$ git push -f
+
```
+
```
+
$ rad patch diff e432b04
+
╭─────────────────────────────╮
+
│ RADICLE.md +1 ❲created❳     │
+
├─────────────────────────────┤
+
│ @@ -0,0 +1,1 @@             │
+
│      1     + Hello Radicle! │
+
╰─────────────────────────────╯
+

+
╭─────────────────────────────╮
+
│ README.md +1 ❲created❳      │
+
├─────────────────────────────┤
+
│ @@ -0,0 +1,1 @@             │
+
│      1     + Hello World!   │
+
╰─────────────────────────────╯
+

+
```
+

+
Buf if we only want to see the changes from the first revision, we can do that
+
too.
+

+
```
+
$ rad patch diff e432b04 --revision e432b04
+
╭───────────────────────────╮
+
│ README.md +1 ❲created❳    │
+
├───────────────────────────┤
+
│ @@ -0,0 +1,1 @@           │
+
│      1     + Hello World! │
+
╰───────────────────────────╯
+

+
```
modified radicle-cli/src/commands/patch.rs
@@ -10,6 +10,8 @@ mod comment;
mod common;
#[path = "patch/delete.rs"]
mod delete;
+
#[path = "patch/diff.rs"]
+
mod diff;
#[path = "patch/edit.rs"]
mod edit;
#[path = "patch/label.rs"]
@@ -53,6 +55,7 @@ Usage
    rad patch [<option>...]
    rad patch list [--all|--merged|--open|--archived|--draft|--authored] [--author <did>]... [<option>...]
    rad patch show <patch-id> [<option>...]
+
    rad patch diff <patch-id> [<option>...]
    rad patch archive <patch-id> [<option>...]
    rad patch update <patch-id> [<option>...]
    rad patch checkout <patch-id> [<option>...]
@@ -71,6 +74,10 @@ Show options
    -p, --patch                Show the actual patch diff
    -v, --verbose              Show additional information about the patch

+
Diff options
+

+
    -r, --revision <id>        The revision to diff (default: latest)
+

Comment options

    -m, --message <string>     Provide a comment message via the command-line
@@ -81,14 +88,13 @@ Edit options

Review options

-
        --revision <id>        Review the given revision of the patch
+
    -r, --revision <id>        Review the given revision of the patch
    -p, --patch                Review by patch hunks
        --hunk <index>         Only review a specific hunk
        --accept               Accept a patch or set of hunks
        --reject               Reject a patch or set of hunks
    -U, --unified <n>          Generate diffs with <n> lines of context instead of the usual three
    -d, --delete               Delete a review draft
-
    -r, --revision             Review a patch revision
    -m, --message [<string>]   Provide a comment with the review (default: prompt)

Assign options
@@ -146,6 +152,7 @@ Other options
pub enum OperationName {
    Assign,
    Show,
+
    Diff,
    Update,
    Archive,
    Delete,
@@ -200,6 +207,10 @@ pub enum Operation {
        patch_id: Rev,
        diff: bool,
    },
+
    Diff {
+
        patch_id: Rev,
+
        revision_id: Option<Rev>,
+
    },
    Update {
        patch_id: Rev,
        base_id: Option<Rev>,
@@ -267,7 +278,10 @@ impl Operation {
            | Operation::Edit { .. }
            | Operation::Redact { .. }
            | Operation::Set { .. } => true,
-
            Operation::Show { .. } | Operation::Checkout { .. } | Operation::List { .. } => false,
+
            Operation::Show { .. }
+
            | Operation::Diff { .. }
+
            | Operation::Checkout { .. }
+
            | Operation::List { .. } => false,
        }
    }
}
@@ -360,8 +374,10 @@ impl Args for Options {
                    reply_to = Some(rev);
                }

-
                // Review options.
-
                Long("revision") if op == Some(OperationName::Review) => {
+
                // Review/diff options.
+
                Long("revision") | Short('r')
+
                    if op == Some(OperationName::Review) || op == Some(OperationName::Diff) =>
+
                {
                    let val = parser.value()?;
                    let rev = term::args::rev(&val)?;

@@ -511,6 +527,7 @@ impl Args for Options {
                    "y" | "ready" => op = Some(OperationName::Ready),
                    "e" | "edit" => op = Some(OperationName::Edit),
                    "r" | "redact" => op = Some(OperationName::Redact),
+
                    "diff" => op = Some(OperationName::Diff),
                    "assign" => op = Some(OperationName::Assign),
                    "label" => op = Some(OperationName::Label),
                    "comment" => op = Some(OperationName::Comment),
@@ -526,6 +543,7 @@ impl Args for Options {
                    if patch_id.is_none()
                        && [
                            Some(OperationName::Show),
+
                            Some(OperationName::Diff),
                            Some(OperationName::Update),
                            Some(OperationName::Delete),
                            Some(OperationName::Archive),
@@ -553,6 +571,10 @@ impl Args for Options {
                patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
                diff,
            },
+
            OperationName::Diff => Operation::Diff {
+
                patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
+
                revision_id,
+
            },
            OperationName::Delete => Operation::Delete {
                patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
            },
@@ -652,6 +674,17 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                &workdir,
            )?;
        }
+
        Operation::Diff {
+
            patch_id,
+
            revision_id,
+
        } => {
+
            let patch_id = patch_id.resolve(&repository.backend)?;
+
            let revision_id = revision_id
+
                .map(|rev| rev.resolve::<radicle::git::Oid>(&repository.backend))
+
                .transpose()?
+
                .map(patch::RevisionId::from);
+
            diff::run(&patch_id, revision_id, &repository)?;
+
        }
        Operation::Update {
            ref patch_id,
            ref base_id,
added radicle-cli/src/commands/patch/diff.rs
@@ -0,0 +1,36 @@
+
use std::process;
+

+
use radicle::cob::patch;
+
use radicle::storage::git::Repository;
+

+
use super::*;
+

+
pub fn run(
+
    patch_id: &PatchId,
+
    revision_id: Option<patch::RevisionId>,
+
    stored: &Repository,
+
) -> anyhow::Result<()> {
+
    let patches = patch::Patches::open(stored)?;
+
    let Some(patch) = patches.get(patch_id)? else {
+
        anyhow::bail!("Patch `{patch_id}` not found");
+
    };
+
    let revision = if let Some(r) = revision_id {
+
        patch
+
            .revision(&r)
+
            .ok_or(anyhow!("revision `{r}` not found"))?
+
    } else {
+
        let (_, r) = patch.latest();
+
        r
+
    };
+
    let (from, to) = revision.range();
+

+
    process::Command::new("rad")
+
        .current_dir(stored.path())
+
        .args(["diff", from.to_string().as_str(), to.to_string().as_str()])
+
        .stdout(process::Stdio::inherit())
+
        .stderr(process::Stdio::inherit())
+
        .spawn()?
+
        .wait()?;
+

+
    Ok(())
+
}
modified radicle-cli/src/commands/patch/list.rs
@@ -100,7 +100,7 @@ pub fn row(
) -> anyhow::Result<[term::Line; 9]> {
    let state = patch.state();
    let (_, revision) = patch.latest();
-
    let (from, to) = patch.range(repository)?;
+
    let (from, to) = revision.range();
    let stats = common::diff_stats(repository.raw(), &from, &to)?;
    let author = patch.author().id;
    let (alias, did) = Author::new(&author, profile).labels();
modified radicle-cli/src/commands/patch/show.rs
@@ -13,7 +13,7 @@ use crate::terminal as term;
use super::*;

fn show_patch_diff(patch: &patch::Patch, stored: &Repository) -> anyhow::Result<()> {
-
    let (from, to) = patch.range(stored)?;
+
    let (from, to) = patch.range()?;
    let range = format!("{}..{}", from, to);

    process::Command::new("git")
@@ -28,7 +28,7 @@ fn show_patch_diff(patch: &patch::Patch, stored: &Repository) -> anyhow::Result<
}

fn patch_commits(patch: &patch::Patch, stored: &Repository) -> anyhow::Result<Vec<term::Line>> {
-
    let (from, to) = patch.range(stored)?;
+
    let (from, to) = patch.range()?;
    let range = format!("{}..{}", from, to);

    let mut revwalk = stored.revwalk(*patch.head())?;
modified radicle-cli/src/git/pretty_diff.rs
@@ -13,8 +13,10 @@ use crate::terminal::highlight::{Highlighter, Theme};
use super::unified_diff::{Decode, HunkHeader};

/// Blob returned by the [`Repo`] trait.
+
#[derive(PartialEq, Eq)]
pub enum Blob {
    Binary,
+
    Empty,
    Plain(Vec<u8>),
}

@@ -33,7 +35,13 @@ impl Repo for git::raw::Repository {
        if blob.is_binary() {
            Ok(Blob::Binary)
        } else {
-
            Ok(Blob::Plain(blob.content().to_vec()))
+
            let content = blob.content();
+

+
            if content.is_empty() {
+
                Ok(Blob::Empty)
+
            } else {
+
                Ok(Blob::Plain(blob.content().to_vec()))
+
            }
        }
    }

@@ -226,6 +234,14 @@ impl ToPretty for DiffContent {

        match context {
            FileDiff::Moved(_) | FileDiff::Copied(_) => {}
+
            FileDiff::Added(_) if blobs.new.is_none() => {
+
                vstack = vstack.divider();
+
                vstack.push(term::Line::new(term::format::italic("Empty file")));
+
            }
+
            FileDiff::Deleted(_) if blobs.old.is_none() => {
+
                vstack = vstack.divider();
+
                vstack.push(term::Line::new(term::format::italic("Empty file")));
+
            }
            FileDiff::Added(_) | FileDiff::Deleted(_) | FileDiff::Modified(_) => {
                vstack = vstack.divider();

modified radicle-cli/tests/commands.rs
@@ -465,6 +465,20 @@ fn rad_patch() {
}

#[test]
+
fn rad_patch_diff() {
+
    let mut environment = Environment::new();
+
    let profile = environment.profile(config::profile("alice"));
+
    let working = tempfile::tempdir().unwrap();
+
    let home = &profile.home;
+

+
    // Setup a test repository.
+
    fixtures::repository(working.path());
+

+
    test("examples/rad-init.md", working.path(), Some(home), []).unwrap();
+
    test("examples/rad-patch-diff.md", working.path(), Some(home), []).unwrap();
+
}
+

+
#[test]
fn rad_patch_checkout() {
    let mut environment = Environment::new();
    let profile = environment.profile(config::profile("alice"));
modified radicle/src/cob/patch.rs
@@ -99,7 +99,7 @@ pub enum Error {
    #[error("identity doc failed to load: {0}")]
    Doc(#[from] DocError),
    /// Identity document is missing.
-
    #[error("missing identity docuemnt")]
+
    #[error("missing identity document")]
    MissingIdentity,
    /// Error loading the document payload.
    #[error("payload failed to load: {0}")]
@@ -118,6 +118,9 @@ pub enum Error {
    /// An illegal action.
    #[error("action is not allowed: {0}")]
    NotAllowed(EntryId),
+
    /// Revision not found.
+
    #[error("revision not found: {0}")]
+
    RevisionNotFound(RevisionId),
    /// Initialization failed.
    #[error("initialization failed: {0}")]
    Init(&'static str),
@@ -521,15 +524,8 @@ impl Patch {
    }

    /// Get the commit range of this patch.
-
    pub fn range<R: ReadRepository>(
-
        &self,
-
        repo: &R,
-
    ) -> Result<(git::Oid, git::Oid), git::ext::Error> {
-
        if self.is_merged() {
-
            Ok((*self.base(), *self.head()))
-
        } else {
-
            Ok((self.merge_base(repo)?, *self.head()))
-
        }
+
    pub fn range(&self) -> Result<(git::Oid, git::Oid), git::ext::Error> {
+
        return Ok((*self.base(), *self.head()));
    }

    /// Index of latest revision in the revisions list.
@@ -1331,6 +1327,11 @@ impl Revision {
        self.oid
    }

+
    /// Get the commit range of this revision.
+
    pub fn range(&self) -> (git::Oid, git::Oid) {
+
        (self.base, self.oid)
+
    }
+

    /// When this revision was created.
    pub fn timestamp(&self) -> Timestamp {
        self.timestamp