cli: Improve `rad diff`

* Properly show the user when a file was moved or copied.
* Don't require a radicle project in the current repo.
This commit is contained in:
cloudhead 2023-11-06 16:01:05 +01:00
parent 947f1bfc01
commit 9b7c48ab40
No known key found for this signature in database
9 changed files with 173 additions and 41 deletions

View File

@ -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 } │
╰──────────────────────────────────────────────╯
```

View File

@ -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()

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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();
.padding(1)
.child(term::Line::default().extend(header));
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();
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
}

View File

@ -869,6 +869,15 @@ fn rad_fork() {
.unwrap();
}
#[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() {

View File

@ -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()?;

View File

@ -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,