cli: Add `rad patch diff` command
This commit is contained in:
parent
04bfbca15d
commit
91684cc9ca
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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! │
|
||||
╰───────────────────────────╯
|
||||
|
||||
```
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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())?;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -464,6 +464,20 @@ fn rad_patch() {
|
|||
test("examples/rad-patch.md", working.path(), Some(home), []).unwrap();
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue