diff --git a/radicle-cli/examples/rad-patch-checkout-force.md b/radicle-cli/examples/rad-patch-checkout-force.md index 279959e1..ff88d7eb 100644 --- a/radicle-cli/examples/rad-patch-checkout-force.md +++ b/radicle-cli/examples/rad-patch-checkout-force.md @@ -46,6 +46,10 @@ $ git commit --message "Add README, just for the fun" ``` ~alice (stderr) $ git push rad -o patch.message="Add README, just for the fun" ✓ Patch aa45913 updated to revision 3156bed9d64d4675d6cf56612d217fc5f4e8a53a +To compare against your previous revision aa45913, run: + + git range-diff f2de534[..] 3e674d1[..] 27857ec[..] + ✓ Synced with 1 node(s) To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 3e674d1..27857ec flux-capacitor-power -> patches/aa45913e757cacd46972733bddee5472c78fa32a diff --git a/radicle-cli/examples/rad-patch-checkout.md b/radicle-cli/examples/rad-patch-checkout.md index eadb67fc..044e6c26 100644 --- a/radicle-cli/examples/rad-patch-checkout.md +++ b/radicle-cli/examples/rad-patch-checkout.md @@ -60,6 +60,10 @@ We can now finish off the update: ``` (stderr) $ git push rad -o patch.message="Add README, just for the fun" ✓ Patch aa45913 updated to revision 3156bed9d64d4675d6cf56612d217fc5f4e8a53a +To compare against your previous revision aa45913, run: + + git range-diff f2de534[..] 3e674d1[..] 27857ec[..] + To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 3e674d1..27857ec patch/aa45913 -> patches/aa45913e757cacd46972733bddee5472c78fa32a ``` diff --git a/radicle-cli/examples/rad-patch-delete.md b/radicle-cli/examples/rad-patch-delete.md index 5d33d685..06289021 100644 --- a/radicle-cli/examples/rad-patch-delete.md +++ b/radicle-cli/examples/rad-patch-delete.md @@ -69,6 +69,10 @@ $ git commit -am "Add MIT License" ``` ~alice (stderr) $ git push -f ✓ Patch 6c61ef1 updated to revision 93915b9afa94a9dc4f52f12cdf077d4613ea3eb3 +To compare against your previous revision 6c61ef1, run: + + git range-diff f2de534[..] 717c900[..] 1cc8cd9[..] + ✓ Synced with 2 node(s) To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 717c900..1cc8cd9 prepare-license -> patches/6c61ef1716ad8a5c11e04dd7a3fec51e01fba70b diff --git a/radicle-cli/examples/rad-patch-edit.md b/radicle-cli/examples/rad-patch-edit.md index 0a6afe30..53c8570b 100644 --- a/radicle-cli/examples/rad-patch-edit.md +++ b/radicle-cli/examples/rad-patch-edit.md @@ -33,6 +33,10 @@ $ git commit -v -m "Define the LICENSE" ``` (stderr) $ git push -f -o patch.message="Add License" ✓ Patch 89f7afb updated to revision 5d78dd5376453e25df5988ec86951c99cb73742c +To compare against your previous revision 89f7afb, run: + + git range-diff f2de534[..] 03c02af[..] 8945f61[..] + To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 03c02af..8945f61 changes -> patches/89f7afb1511b976482b21f6b2f39aef7f4fb88a2 ``` diff --git a/radicle-cli/examples/rad-patch-open-explore.md b/radicle-cli/examples/rad-patch-open-explore.md index 9073f7f3..dee83571 100644 --- a/radicle-cli/examples/rad-patch-open-explore.md +++ b/radicle-cli/examples/rad-patch-open-explore.md @@ -19,6 +19,10 @@ If we update the patch, the URL is also output. $ git commit --amend --allow-empty -q -m "Other changes" $ git push -f ✓ Patch acab0ec updated to revision f7a830d829d0cdf398f63a32b0d5ee31f08e21ab +To compare against your previous revision acab0ec, run: + + git range-diff f2de534[..] e12525d[..] b2b6432[..] + ✓ Synced with 1 node(s) https://app.radicle.xyz/nodes/[..]/rad:z3yXbb1sR6UG6ixxV2YF9jUP7ABra/patches/acab0ec777a97d013f30be5d5d1aec32562ecb02 diff --git a/radicle-cli/examples/rad-patch-pull-update.md b/radicle-cli/examples/rad-patch-pull-update.md index 8a280467..6eef5e71 100644 --- a/radicle-cli/examples/rad-patch-pull-update.md +++ b/radicle-cli/examples/rad-patch-pull-update.md @@ -83,6 +83,10 @@ Bob then updates the patch. $ git commit --allow-empty -m "Bob's commit #2" -q $ git push rad -o sync -o patch.message="Updated." ✓ Patch 55b9721 updated to revision f91e056da05b2d9a58af1160c76245bc3debf7a8 +To compare against your previous revision 55b9721, run: + + git range-diff f2de534[..] bdcdb30[..] cad2666[..] + ✓ Synced with 1 node(s) To rad://zhbMU4DUXrzB8xT6qAJh6yZ7bFMK/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bdcdb30..cad2666 bob/feature -> patches/55b9721ed7f6bfec38f43729e9b6631c5dc812fb diff --git a/radicle-cli/examples/rad-patch-via-push.md b/radicle-cli/examples/rad-patch-via-push.md index 5aba56af..b21a8cc4 100644 --- a/radicle-cli/examples/rad-patch-via-push.md +++ b/radicle-cli/examples/rad-patch-via-push.md @@ -113,6 +113,10 @@ $ git commit -a -m "Improve code" -q --allow-empty ``` (stderr) $ git push rad ✓ Patch 9580891 updated to revision d7040c6c97629c2b94f86fb639bebbff5de39697 +To compare against your previous revision 9580891, run: + + git range-diff f2de534[..] 8b0ea80[..] 02bef3f[..] + To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 8b0ea80..02bef3f feature/2 -> patches/95808913573cead52ad7b42c7b475260ec45c4b2 ``` @@ -201,6 +205,10 @@ use `--force` to force the update. ``` (stderr) $ git push --force ✓ Patch 9580891 updated to revision 670d02794aa05afd6e0851f4aa848bc87c4712c7 +To compare against your previous revision d7040c6, run: + + git range-diff f2de534[..] 02bef3f[..] 9304dbc[..] + To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi + 02bef3f...9304dbc feature/2 -> patches/95808913573cead52ad7b42c7b475260ec45c4b2 (forced update) ``` diff --git a/radicle-cli/examples/rad-patch.md b/radicle-cli/examples/rad-patch.md index 503fa738..aaef9fef 100644 --- a/radicle-cli/examples/rad-patch.md +++ b/radicle-cli/examples/rad-patch.md @@ -127,6 +127,10 @@ $ git commit --message "Add README, just for the fun" ``` (stderr) $ git push rad -o patch.message="Add README, just for the fun" ✓ Patch aa45913 updated to revision 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 +To compare against your previous revision aa45913, run: + + git range-diff f2de534[..] 3e674d1[..] 27857ec[..] + To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 3e674d1..27857ec flux-capacitor-power -> patches/aa45913e757cacd46972733bddee5472c78fa32a ``` diff --git a/radicle-cli/examples/rad-push-and-pull-patches.md b/radicle-cli/examples/rad-push-and-pull-patches.md index 1fee53d3..0d365443 100644 --- a/radicle-cli/examples/rad-push-and-pull-patches.md +++ b/radicle-cli/examples/rad-push-and-pull-patches.md @@ -33,6 +33,10 @@ $ git checkout patch/d004b67 -q $ git commit --allow-empty -m "Changes #2" -q $ git push ✓ Patch d004b67 updated to revision 2eb705c3da98e05c083df15be5b1bd6856a0bd77 +To compare against your previous revision d004b67, run: + + git range-diff f2de534[..] 8d5f1ba[..] c2aaf1c[..] + ✓ Synced with 1 node(s) To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi * [new branch] patch/d004b67 -> patches/d004b67355456c46de10c0d287e4a791ad1a6945 @@ -52,6 +56,10 @@ $ git checkout - -q $ git commit --allow-empty -m "Changes #3" -q $ git push ✓ Patch d004b67 updated to revision 7b5015a8dac188bb0d44a334aa68a51298750b07 +To compare against your previous revision d004b67, run: + + git range-diff f2de534[..] 8d5f1ba[..] d9f8caf[..] + ✓ Synced with 1 node(s) To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi c2aaf1c..d9f8caf patch/d004b67 -> patches/d004b67355456c46de10c0d287e4a791ad1a6945 diff --git a/radicle-cli/examples/workflow/4-patching-contributor.md b/radicle-cli/examples/workflow/4-patching-contributor.md index 626081cd..181072c9 100644 --- a/radicle-cli/examples/workflow/4-patching-contributor.md +++ b/radicle-cli/examples/workflow/4-patching-contributor.md @@ -78,6 +78,10 @@ $ git commit --message "Add README, just for the fun" ``` (stderr) RAD_SOCKET=/dev/null $ git push -o patch.message="Add README, just for the fun" ✓ Patch e4934b6 updated to revision 773b9aab58b11e9fa83d0ed0baca2bea6ff889c9 +To compare against your previous revision e4934b6, run: + + git range-diff f2de534[..] 3e674d1[..] 27857ec[..] + To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 3e674d1..27857ec flux-capacitor-power -> patches/e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 ``` diff --git a/radicle-cli/examples/workflow/5-patching-maintainer.md b/radicle-cli/examples/workflow/5-patching-maintainer.md index b20a2e34..85078ed3 100644 --- a/radicle-cli/examples/workflow/5-patching-maintainer.md +++ b/radicle-cli/examples/workflow/5-patching-maintainer.md @@ -63,6 +63,10 @@ $ git commit -m "Use markdown for requirements" ``` (stderr) $ git push rad -o no-sync -o patch.message="Use markdown for requirements" ✓ Patch e4934b6 updated to revision 9d62420e779e5cfe1dc02c51eddec9a0907aa844 +To compare against your previous revision 773b9aa, run: + + git range-diff f2de534[..] 27857ec[..] f567f69[..] + To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi * [new branch] patch/e4934b6 -> patches/e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 ``` diff --git a/radicle-remote-helper/src/push.rs b/radicle-remote-helper/src/push.rs index ef36dca1..2ccdd850 100644 --- a/radicle-remote-helper/src/push.rs +++ b/radicle-remote-helper/src/push.rs @@ -97,6 +97,9 @@ pub enum Error { /// Patch not found in store. #[error("patch `{0}` not found")] NotFound(patch::PatchId), + /// Revision not found in store. + #[error("revision `{0}` not found")] + RevisionNotFound(patch::RevisionId), /// Patch is empty. #[error("patch commits are already included in the base branch")] EmptyPatch, @@ -528,7 +531,7 @@ fn patch_update( push_ref(src, &dst, force, working, stored.raw())?; - let Ok(mut patch) = patches.get_mut(&patch_id) else { + let Ok(Some(patch)) = patches.get(&patch_id) else { return Err(Error::NotFound(patch_id)); }; @@ -536,10 +539,14 @@ fn patch_update( if patch.revisions().any(|(_, r)| *r.head() == commit.id()) { return Ok(None); } + + let (latest_id, latest) = patch.latest(); + let latest = latest.clone(); + let message = term::patch::get_update_message( opts.message, &stored.backend, - patch.latest().1, + &latest, &commit.id().into(), )?; @@ -550,13 +557,18 @@ fn patch_update( } else { stored.merge_base(&target, &head)? }; - let revision = patch.update(message, base, head, signer)?; + + let mut patch_mut = patch::PatchMut::new(patch_id, patch, &mut patches); + let revision = patch_mut.update(message, base, head, signer)?; + let Some(revision) = patch_mut.revision(&revision).cloned() else { + return Err(Error::RevisionNotFound(revision)); + }; eprintln!( "{} Patch {} updated to revision {}", term::format::positive("✓"), term::format::tertiary(term::format::cob(&patch_id)), - term::format::dim(revision) + term::format::dim(revision.id()) ); // In this case, the patch was already merged via git, and pushed to storage. @@ -565,8 +577,16 @@ fn patch_update( // This can happen if for eg. a patch commit is amended, the patch branch is merged // and pushed, but the patch hasn't yet been updated. On push to the patch branch, // it'll seem like the patch is "empty", because the changes are already in the base branch. - if base == head && patch.is_open() { - patch_merge(patch, revision, head, working, signer)?; + if base == head && patch_mut.is_open() { + patch_merge(patch_mut, revision.id(), head, working, signer)?; + } else { + eprintln!( + "To compare against your previous revision {}, run:\n\n {}\n", + term::format::tertiary(term::format::cob(&cob::ObjectId::from(git::Oid::from( + latest_id + )))), + patch::RangeDiff::new(&latest, &revision).to_command() + ); } Ok(Some(ExplorerResource::Patch { id: patch_id })) diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs index 370b54b1..831100ab 100644 --- a/radicle/src/cob/patch.rs +++ b/radicle/src/cob/patch.rs @@ -2609,6 +2609,67 @@ where } } +/// Models a comparison between two commit ranges, +/// commonly obtained from two revisions (likely of the same patch). +/// This can be used to generate a `git range-diff` command. +/// See . +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub struct RangeDiff { + old: (git::Oid, git::Oid), + new: (git::Oid, git::Oid), +} + +impl RangeDiff { + const COMMAND: &str = "git"; + const SUBCOMMAND: &str = "range-diff"; + + pub fn new(old: &Revision, new: &Revision) -> Self { + Self { + old: old.range(), + new: new.range(), + } + } + + pub fn to_command(&self) -> String { + let range = if self.has_same_base() { + format!("{} {} {}", self.old.0, self.old.1, self.new.1) + } else { + format!( + "{}..{} {}..{}", + self.old.0, self.old.1, self.new.0, self.new.1, + ) + }; + + Self::COMMAND.to_string() + " " + Self::SUBCOMMAND + " " + &range + } + + fn has_same_base(&self) -> bool { + self.old.0 == self.new.0 + } +} + +impl From for std::process::Command { + fn from(range_diff: RangeDiff) -> Self { + let mut command = std::process::Command::new(RangeDiff::COMMAND); + + command.arg(RangeDiff::SUBCOMMAND); + + if range_diff.has_same_base() { + command.args([ + range_diff.old.0.to_string(), + range_diff.old.1.to_string(), + range_diff.new.1.to_string(), + ]); + } else { + command.args([ + format!("{}..{}", range_diff.old.0, range_diff.old.1), + format!("{}..{}", range_diff.new.0, range_diff.new.1), + ]); + } + command + } +} + /// Helpers for de/serialization of patch data types. mod ser { use std::collections::{BTreeMap, BTreeSet};