Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Improve `rad diff`
cloudhead committed 2 years ago
commit 9b7c48ab401cd4310f8cdc8bea4ed6b4235cccd2
parent 947f1bfc018a641aded13e7c0b4ab526cbd9b117
9 files changed +174 -42
added radicle-cli/examples/rad-diff.md
@@ -0,0 +1,83 @@
+
Exploring `rad diff`.
+

+
``` ./main.c
+
#include <stdio.h>
+

+
int main(void) {
+
    printf("Hello World!\n");
+
    return 0;
+
}
+
```
+

+
```
+
$ ls
+
README
+
main.c
+
```
+

+
```
+
$ git mv README README.md
+
$ git add main.c
+
$ git commit -m "Make changes"
+
[master 5f771e0] Make changes
+
 2 files changed, 6 insertions(+)
+
 rename README => README.md (100%)
+
 create mode 100644 main.c
+
```
+

+
```
+
$ rad diff HEAD^ HEAD
+
╭────────────────────────────────────────────╮
+
│ README -> README.md ❲moved❳                │
+
╰────────────────────────────────────────────╯
+

+
╭────────────────────────────────────────────╮
+
│ main.c +6 ❲created❳                        │
+
├────────────────────────────────────────────┤
+
│ @@ -0,0 +1,6 @@                            │
+
│      1     + #include <stdio.h>            │
+
│      2     +                               │
+
│      3     + int main(void) {              │
+
│      4     +     printf("Hello World!/n"); │
+
│      5     +     return 0;                 │
+
│      6     + }                             │
+
╰────────────────────────────────────────────╯
+

+
```
+

+
```
+
$ sed -i 's/Hello World/Hello Radicle/' main.c
+
$ rad diff
+
╭──────────────────────────────────────────────╮
+
│ main.c -1 +1                                 │
+
├──────────────────────────────────────────────┤
+
│ @@ -1,6 +1,6 @@                              │
+
│ 1    1       #include <stdio.h>              │
+
│ 2    2                                       │
+
│ 3    3       int main(void) {                │
+
│ 4          -     printf("Hello World!/n");   │
+
│      4     +     printf("Hello Radicle!/n"); │
+
│ 5    5           return 0;                   │
+
│ 6    6       }                               │
+
╰──────────────────────────────────────────────╯
+

+
```
+

+
```
+
$ git add main.c
+
$ rad diff
+
$ rad diff --staged
+
╭──────────────────────────────────────────────╮
+
│ main.c -1 +1                                 │
+
├──────────────────────────────────────────────┤
+
│ @@ -1,6 +1,6 @@                              │
+
│ 1    1       #include <stdio.h>              │
+
│ 2    2                                       │
+
│ 3    3       int main(void) {                │
+
│ 4          -     printf("Hello World!/n");   │
+
│      4     +     printf("Hello Radicle!/n"); │
+
│ 5    5           return 0;                   │
+
│ 6    6       }                               │
+
╰──────────────────────────────────────────────╯
+

+
```
modified radicle-cli/src/commands/diff.rs
@@ -82,7 +82,7 @@ impl Args for Options {
}

pub fn run(options: Options, _ctx: impl term::Context) -> anyhow::Result<()> {
-
    let (repo, _) = rad::cwd()?;
+
    let repo = rad::repo()?;
    let oids = options
        .commits
        .into_iter()
modified radicle-cli/src/commands/fork.rs
@@ -1,5 +1,4 @@
use std::ffi::OsString;
-
use std::path::Path;

use anyhow::Context as _;

@@ -60,8 +59,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let rid = match options.rid {
        Some(rid) => rid,
        None => {
-
            let (_, rid) = radicle::rad::repo(Path::new("."))
-
                .context("Current directory is not a radicle project")?;
+
            let (_, rid) =
+
                radicle::rad::cwd().context("Current directory is not a radicle project")?;

            rid
        }
modified radicle-cli/src/commands/inspect.rs
@@ -107,7 +107,7 @@ impl Args for Options {
                    if let Ok(val) = Id::from_str(&val) {
                        rid = Some(val);
                    } else if let Ok(val) = PathBuf::from_str(&val) {
-
                        rid = radicle::rad::repo(val)
+
                        rid = radicle::rad::at(val)
                            .map(|(_, id)| Some(id))
                            .context("Supplied argument is not a valid path")?;
                    } else {
modified radicle-cli/src/commands/sync.rs
@@ -1,5 +1,4 @@
use std::ffi::OsString;
-
use std::path::Path;
use std::time;

use anyhow::{anyhow, Context as _};
@@ -208,8 +207,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let rid = match options.rid {
        Some(rid) => rid,
        None => {
-
            let (_, rid) = radicle::rad::repo(Path::new("."))
-
                .context("Current directory is not a radicle project")?;
+
            let (_, rid) =
+
                radicle::rad::cwd().context("Current directory is not a radicle project")?;

            rid
        }
modified radicle-cli/src/git/pretty_diff.rs
@@ -104,10 +104,22 @@ impl ToPretty for FileHeader {
    ) -> Self::Output {
        match self {
            FileHeader::Added { path, .. } => term::Line::new(path.display().to_string()),
-
            FileHeader::Moved { new_path, .. } => term::Line::new(new_path.display().to_string()),
+
            FileHeader::Moved {
+
                old_path, new_path, ..
+
            } => term::Line::spaced([
+
                term::label(old_path.display().to_string()),
+
                term::label("->".to_string()),
+
                term::label(new_path.display().to_string()),
+
            ]),
            FileHeader::Deleted { path, .. } => term::Line::new(path.display().to_string()),
            FileHeader::Modified { path, .. } => term::Line::new(path.display().to_string()),
-
            FileHeader::Copied { new_path, .. } => term::Line::new(new_path.display().to_string()),
+
            FileHeader::Copied {
+
                old_path, new_path, ..
+
            } => term::Line::spaced([
+
                term::label(old_path.display().to_string()),
+
                term::label("->".to_string()),
+
                term::label(new_path.display().to_string()),
+
            ]),
        }
    }
}
@@ -149,20 +161,31 @@ impl ToPretty for DiffContent {
        let header = FileHeader::from(context);
        let theme = Theme::default();

-
        let (old, new) = match context {
-
            FileDiff::Added(f) => (None, Some((f.new.oid, f.path.clone()))),
+
        let (old, new, badge) = match context {
+
            FileDiff::Added(f) => (
+
                None,
+
                Some((f.new.oid, f.path.clone())),
+
                Some(term::format::badge_positive("created")),
+
            ),
            FileDiff::Moved(f) => (
                Some((f.old.oid, f.old_path.clone())),
                Some((f.new.oid, f.new_path.clone())),
+
                Some(term::format::badge_secondary("moved")),
+
            ),
+
            FileDiff::Deleted(f) => (
+
                Some((f.old.oid, f.path.clone())),
+
                None,
+
                Some(term::format::badge_negative("deleted")),
            ),
-
            FileDiff::Deleted(f) => (Some((f.old.oid, f.path.clone())), None),
            FileDiff::Modified(f) => (
                Some((f.old.oid, f.path.clone())),
                Some((f.new.oid, f.path.clone())),
+
                None,
            ),
            FileDiff::Copied(f) => (
                Some((f.old.oid, f.old_path.clone())),
                Some((f.old.oid, f.new_path.clone())),
+
                Some(term::format::badge_secondary("copied")),
            ),
        };
        let mut header = header.pretty(hi, &(), repo);
@@ -172,11 +195,18 @@ impl ToPretty for DiffContent {
        } else {
            (0, 0)
        };
+

        if deletions > 0 {
-
            header.push(term::label(format!(" -{deletions}")).fg(theme.color("negative.light")));
+
            header.push(term::Label::space());
+
            header.push(term::label(format!("-{deletions}")).fg(theme.color("negative.light")));
        }
        if additions > 0 {
-
            header.push(term::label(format!(" +{additions}")).fg(theme.color("positive.light")));
+
            header.push(term::Label::space());
+
            header.push(term::label(format!("+{additions}")).fg(theme.color("positive.light")));
+
        }
+
        if let Some(badge) = badge {
+
            header.push(term::Label::space());
+
            header.push(badge);
        }

        let old = old.and_then(|(oid, path)| repo.blob(oid).ok().or_else(|| repo.file(&path)));
@@ -191,25 +221,31 @@ impl ToPretty for DiffContent {
        }
        let mut vstack = term::VStack::default()
            .border(Some(term::colors::FAINT))
-
            .padding(0)
-
            .child(term::Line::new(term::Label::space()).extend(header))
-
            .divider();
-

-
        match self {
-
            DiffContent::Plain { hunks, .. } => {
-
                for (i, h) in hunks.iter().enumerate() {
-
                    vstack.push(h.pretty(hi, &blobs, repo));
-
                    if i != hunks.0.len() - 1 {
-
                        vstack = vstack.divider();
+
            .padding(1)
+
            .child(term::Line::default().extend(header));
+

+
        match context {
+
            FileDiff::Moved(_) | FileDiff::Copied(_) => {}
+
            FileDiff::Added(_) | FileDiff::Deleted(_) | FileDiff::Modified(_) => {
+
                vstack = vstack.divider();
+

+
                match self {
+
                    DiffContent::Plain { hunks, .. } => {
+
                        for (i, h) in hunks.iter().enumerate() {
+
                            vstack.push(h.pretty(hi, &blobs, repo));
+
                            if i != hunks.0.len() - 1 {
+
                                vstack = vstack.divider();
+
                            }
+
                        }
+
                    }
+
                    DiffContent::Empty => {
+
                        vstack.push(term::Line::new(term::format::italic("Empty file")));
+
                    }
+
                    DiffContent::Binary => {
+
                        vstack.push(term::Line::new(term::format::italic("Binary file")));
                    }
                }
            }
-
            DiffContent::Empty => {
-
                vstack.push(term::Line::new(term::format::italic("Empty file")));
-
            }
-
            DiffContent::Binary => {
-
                vstack.push(term::Line::new(term::format::italic("Binary file")));
-
            }
        }
        vstack
    }
modified radicle-cli/tests/commands.rs
@@ -870,6 +870,15 @@ fn rad_fork() {
}

#[test]
+
fn rad_diff() {
+
    let working = tempfile::tempdir().unwrap();
+

+
    fixtures::repository(&working);
+

+
    test("examples/rad-diff.md", working, None, []).unwrap();
+
}
+

+
#[test]
// User tries to clone; no seeds are available, but user has the repo locally.
fn test_clone_without_seeds() {
    let mut environment = Environment::new();
modified radicle-tools/src/rad-set-canonical-refs.rs
@@ -1,5 +1,3 @@
-
use std::path::Path;
-

use radicle::{
    storage::{WriteRepository, WriteStorage},
    Profile,
@@ -8,7 +6,7 @@ use radicle::{
fn main() -> anyhow::Result<()> {
    let profile = Profile::load()?;

-
    let (_, rid) = radicle::rad::repo(Path::new("."))?;
+
    let (_, rid) = radicle::rad::cwd()?;
    let repo = profile.storage.repository_mut(rid)?;

    let id_oid = repo.set_identity_head()?;
modified radicle/src/rad.rs
@@ -279,26 +279,34 @@ pub fn remove_remote(repo: &git2::Repository) -> Result<(), RemoteError> {
/// want to modify the wrong repository in the case that it found a
/// Git repository that is not a Radicle repository.
pub fn cwd() -> Result<(git2::Repository, Id), RemoteError> {
-
    let mut flags = git2::RepositoryOpenFlags::empty();
-
    // Allow to search upwards.
-
    flags.set(git2::RepositoryOpenFlags::NO_SEARCH, false);
-
    // Allow to use `GIT_DIR` env.
-
    flags.set(git2::RepositoryOpenFlags::FROM_ENV, true);
-
    let ceilings: &[&str] = &[];
-
    let repo = git2::Repository::open_ext(Path::new("."), flags, ceilings)?;
+
    let repo = repo()?;
    let (_, id) = remote(&repo)?;

    Ok((repo, id))
}

/// Get the repository of project in specified directory
-
pub fn repo(path: impl AsRef<Path>) -> Result<(git2::Repository, Id), RemoteError> {
+
pub fn at(path: impl AsRef<Path>) -> Result<(git2::Repository, Id), RemoteError> {
    let repo = git2::Repository::open(path)?;
    let (_, id) = remote(&repo)?;

    Ok((repo, id))
}

+
/// Get the current Git repository.
+
pub fn repo() -> Result<git2::Repository, git2::Error> {
+
    let mut flags = git2::RepositoryOpenFlags::empty();
+
    // Allow to search upwards.
+
    flags.set(git2::RepositoryOpenFlags::NO_SEARCH, false);
+
    // Allow to use `GIT_DIR` env.
+
    flags.set(git2::RepositoryOpenFlags::FROM_ENV, true);
+

+
    let ceilings: &[&str] = &[];
+
    let repo = git2::Repository::open_ext(Path::new("."), flags, ceilings)?;
+

+
    Ok(repo)
+
}
+

/// Setup patch upstream branch such that `git push` updates the patch.
pub fn setup_patch_upstream<'a>(
    patch: &ObjectId,