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:
parent
947f1bfc01
commit
9b7c48ab40
|
|
@ -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 } │
|
||||
╰──────────────────────────────────────────────╯
|
||||
|
||||
```
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()?;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue