Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: add force flag to rad patch checkout
Fintan Halpenny committed 2 years ago
commit 30ad7e4d67a079bd129d8cbf75504f6118d56947
parent 358324883c0aed8acc40e7b565663f89b236692d
4 files changed +180 -14
added radicle-cli/examples/rad-patch-checkout-force.md
@@ -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
+
```
modified radicle-cli/src/commands/patch.rs
@@ -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,
modified radicle-cli/src/commands/patch/checkout.rs
@@ -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))?;

modified radicle-cli/tests/commands.rs
@@ -441,6 +441,55 @@ fn rad_patch_checkout() {
}

#[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();
    let profile = environment.profile("alice");