cli: add force flag to rad patch checkout

Add a `--force` flag to forcibly checkout the branch to the latest
revision, if the branch already exists.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2023-11-16 14:27:12 +00:00 committed by cloudhead
parent 358324883c
commit 30ad7e4d67
No known key found for this signature in database
4 changed files with 180 additions and 14 deletions

View File

@ -0,0 +1,87 @@
A common workflow is to use `rad patch checkout` to view a
collaborator's changes. So, first off, we create a patch:
``` ~alice
$ git checkout -b flux-capacitor-power
$ touch REQUIREMENTS
$ git add REQUIREMENTS
$ git commit -v -m "Define power requirements"
[flux-capacitor-power 3e674d1] Define power requirements
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 REQUIREMENTS
```
``` ~alice (stderr)
$ git push rad -o patch.message="Define power requirements" -o patch.message="See details." HEAD:refs/patches
✓ Patch 6ff4f09c1b5a81347981f59b02ef43a31a07cdae opened
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
On the other end, Bob uses `rad patch checkout` to view the patch:
``` ~bob
$ cd heartwood
$ rad sync -f
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
$ rad patch checkout 6ff4f09
✓ Switched to branch patch/6ff4f09
✓ Branch patch/6ff4f09 setup to track rad/patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae
```
Meanwhile, we may see some more changes that we need to make, so we
add a `README.md`:
``` ~alice
$ touch README.md
$ git add README.md
$ git commit --message "Add README, just for the fun"
[flux-capacitor-power 27857ec] Add README, just for the fun
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
```
``` ~alice (stderr)
$ git push rad -o patch.message="Add README, just for the fun"
✓ Patch 6ff4f09 updated to 0c0942e2ff2488617d950ede15567ca39a29972e
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
3e674d1..27857ec flux-capacitor-power -> patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae
```
Bob fetches these new changes and can see their branch is now behind:
``` ~bob (stderr)
$ git fetch rad
✓ Synced with 1 peer(s)
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
3e674d1..27857ec patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae -> rad/patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae
```
``` ~bob
$ git status
On branch patch/6ff4f09
Your branch is behind 'rad/patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
```
If Bob was to run `rad patch checkout` again, their branch would not
update to the latest commit. This is because it sees that the branch
already exists and does not want overwrite any changes. Bob can choose
to use the `--force` (`-f`) flag to ensure that they are looking at
the latest changes:
``` ~bob
$ rad patch checkout 6ff4f09 -f
✓ Switched to branch patch/6ff4f09
✓ Branch patch/6ff4f09 setup to track rad/patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae
$ git status
On branch patch/6ff4f09
Your branch is up to date with 'rad/patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae'.
nothing to commit, working tree clean
```

View File

@ -85,6 +85,10 @@ Ready options
--undo Convert a patch back to a draft
Checkout options
-f, --force Checkout the head of the revision, even if the branch already exists
Other options
-q, --quiet Quiet output
@ -151,6 +155,7 @@ pub enum Operation {
},
Checkout {
revision_id: Rev,
force: bool,
},
Comment {
revision_id: Rev,
@ -198,6 +203,7 @@ impl Args for Options {
let mut diff = false;
let mut undo = false;
let mut reply_to: Option<Rev> = None;
let mut force = false;
while let Some(arg) = parser.next()? {
match arg {
@ -251,6 +257,11 @@ impl Args for Options {
reply_to = Some(rev);
}
// Checkout options
Long("force") | Short('f') if op == Some(OperationName::Checkout) => {
force = true;
}
// List options.
Long("all") => {
filter = Filter::all();
@ -340,6 +351,7 @@ impl Args for Options {
},
OperationName::Checkout => Operation::Checkout {
revision_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
force,
},
OperationName::Comment => Operation::Comment {
revision_id: patch_id
@ -419,9 +431,14 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let patch_id = patch_id.resolve::<PatchId>(&repository.backend)?;
delete::run(&patch_id, &profile, &repository)?;
}
Operation::Checkout { revision_id } => {
Operation::Checkout { revision_id, force } => {
let revision_id = revision_id.resolve::<radicle::git::Oid>(&repository.backend)?;
checkout::run(&patch::RevisionId::from(revision_id), &repository, &workdir)?;
checkout::run(
&patch::RevisionId::from(revision_id),
&repository,
&workdir,
force,
)?;
}
Operation::Comment {
revision_id,

View File

@ -13,6 +13,7 @@ pub fn run(
revision_id: &RevisionId,
stored: &Repository,
working: &git::raw::Repository,
force: bool,
) -> anyhow::Result<()> {
let patches = patch::Patches::open(stored)?;
@ -35,18 +36,30 @@ pub fn run(
// SAFETY: Patch IDs are valid refstrings.
git::refname!("patch").join(RefString::try_from(term::format::cob(&patch_id)).unwrap());
match working.find_branch(patch_branch.as_str(), radicle::git::raw::BranchType::Local) {
Ok(branch) => {
let commit = branch.get().peel_to_commit()?;
working.checkout_tree(commit.as_object(), None)?;
}
Err(e) if radicle::git::is_not_found_err(&e) => {
let commit = find_patch_commit(revision, stored, working)?;
// Create patch branch and switch to it.
working.branch(patch_branch.as_str(), &commit, true)?;
working.checkout_tree(commit.as_object(), None)?;
}
Err(e) => return Err(e.into()),
let commit =
match working.find_branch(patch_branch.as_str(), radicle::git::raw::BranchType::Local) {
Ok(branch) if !force => branch.get().peel_to_commit()?,
Ok(branch) => {
let commit = find_patch_commit(revision, stored, working)?;
let mut r = branch.into_reference();
r.set_target(commit.id(), &format!("force update '{patch_branch}'"))?;
commit
}
Err(e) if radicle::git::is_not_found_err(&e) => {
let commit = find_patch_commit(revision, stored, working)?;
// Create patch branch and switch to it.
working.branch(patch_branch.as_str(), &commit, true)?;
commit
}
Err(e) => return Err(e.into()),
};
if force {
let mut opts = radicle::git::raw::build::CheckoutBuilder::new();
opts.force();
working.checkout_tree(commit.as_object(), Some(&mut opts))?;
} else {
working.checkout_tree(commit.as_object(), None)?;
}
working.set_head(&git::refs::workdir::branch(&patch_branch))?;

View File

@ -440,6 +440,55 @@ fn rad_patch_checkout() {
.unwrap();
}
#[test]
fn rad_patch_checkout_force() {
let mut environment = Environment::new();
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let working = environment.tmp().join("working");
let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
test(
"examples/rad-init.md",
working.join("alice"),
Some(&alice.home),
[],
)
.unwrap();
let mut alice = alice.spawn();
let mut bob = bob.spawn();
bob.handle.track_repo(acme, Scope::All).unwrap();
alice.connect(&bob).converge([&bob]);
test(
"examples/rad-clone.md",
working.join("bob"),
Some(&bob.home),
[],
)
.unwrap();
formula(&environment.tmp(), "examples/rad-patch-checkout-force.md")
.unwrap()
.home(
"alice",
working.join("alice"),
[("RAD_HOME", alice.home.path().display())],
)
.home(
"bob",
working.join("bob"),
[("RAD_HOME", bob.home.path().display())],
)
.run()
.unwrap();
}
#[test]
fn rad_patch_update() {
let mut environment = Environment::new();