Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Make `patch checkout` fetch missing objects
Alexis Sellier committed 3 years ago
commit 5ccf38079c7a3b51b83105acd6fe83d97a8f8ec0
parent 8f57d6070619f82c990f85b266db62a2e38b1b68
4 files changed +71 -23
modified Cargo.lock
@@ -1823,6 +1823,7 @@ version = "0.8.0"
dependencies = [
 "anyhow",
 "chrono",
+
 "git-ref-format",
 "json-color",
 "lexopt",
 "log",
modified radicle-cli/Cargo.toml
@@ -13,6 +13,7 @@ path = "src/main.rs"
[dependencies]
anyhow = { version = "1" }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
+
git-ref-format = { version = "0", features = ["macro"] }
json-color = { version = "0.7" }
lexopt = { version = "0.2" }
log = { version = "0.4", features = ["std"] }
modified radicle-cli/examples/rad-patch.md
@@ -112,6 +112,5 @@ Now, let's checkout the patch that we just created:

```
$ rad patch checkout 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
-
✓ Performing patch checkout...
✓ Switched to branch patch/191a14e520f
```
modified radicle-cli/src/commands/patch/checkout.rs
@@ -1,38 +1,85 @@
-
use crate::terminal as term;
use anyhow::anyhow;
-
use radicle::cob::patch::{self, PatchId};
-
use radicle::git::{self, RefString};
+

+
use radicle::cob::patch;
+
use radicle::cob::patch::{Patch, PatchId};
+
use radicle::git;
+
use radicle::git::RefString;
use radicle::storage::git::Repository;
+
use radicle::storage::ReadRepository;
+

+
use crate::terminal as term;

pub fn run(
-
    storage: &Repository,
-
    git_workdir: &git::raw::Repository,
+
    stored: &Repository,
+
    working: &git::raw::Repository,
    patch_id: &PatchId,
) -> anyhow::Result<()> {
-
    let patches = patch::Patches::open(storage)?;
+
    let patches = patch::Patches::open(stored)?;
    let patch = patches
        .get(patch_id)?
        .ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;

-
    let spinner = term::spinner("Performing patch checkout...");
+
    let mut spinner = term::spinner("Performing checkout...");
+
    let patch_branch =
+
        // SAFETY: Patch IDs are valid refstrings.
+
        git::refname!("patch").join(RefString::try_from(term::format::cob(patch_id)).unwrap());
+
    let commit = find_patch_commit(&patch, &patch_branch, stored, working)?;

-
    // Getting the patch obj!
-
    let patch_head = *patch.head();
-
    let commit = git_workdir.find_commit(patch_head.into())?;
-

-
    let name = RefString::try_from(format!("patch/{}", term::format::cob(patch_id)))?;
-
    let branch = git::refs::workdir::branch(&name);
-
    // checkout the patch in a new branch!
-
    git_workdir.branch(branch.as_str(), &commit, false)?;
-
    // and then point the current `HEAD` inside the new branch.
-
    git_workdir.set_head(branch.as_str())?;
-
    spinner.finish();
+
    // Create patch branch and switch to it.
+
    working.branch(patch_branch.as_str(), &commit, false)?;
+
    working.checkout_tree(commit.as_object(), None)?;
+
    working.set_head(&git::refs::workdir::branch(&patch_branch))?;

-
    // 3. Write to the UI Terminal
-
    term::success!(
+
    spinner.message(format!(
        "Switched to branch {}",
-
        term::format::highlight(name.as_str())
-
    );
+
        term::format::highlight(patch_branch.as_str())
+
    ));
+
    spinner.finish();

    Ok(())
}
+

+
/// Try to find the patch head in our working copy, and if we don't find it,
+
/// fetch it from storage first.
+
fn find_patch_commit<'a>(
+
    patch: &Patch,
+
    patch_branch: &RefString,
+
    stored: &Repository,
+
    working: &'a git::raw::Repository,
+
) -> anyhow::Result<git::raw::Commit<'a>> {
+
    let patch_head = *patch.head();
+

+
    match working.find_commit(patch_head.into()) {
+
        Ok(commit) => Ok(commit),
+
        Err(e) if git::ext::is_not_found_err(&e) => {
+
            // TODO: Handle case of concurrent revisions.
+
            let (_, rev) = patch
+
                .latest()
+
                .ok_or(anyhow!("patch does not have any revisions"))?;
+
            let author = **rev.author.id();
+
            let remote = stored.remote(&author)?;
+

+
            // Find a ref in storage that points to our patch, so that we can fetch the patch
+
            // objects into our working copy.
+
            let (refstr, _) = remote
+
                .refs
+
                .iter()
+
                .find(|(_, o)| **o == patch_head)
+
                .ok_or(anyhow!("patch ref for {patch_head} not found in storage"))?;
+
            let remote_branch = git::refs::workdir::remote_branch(
+
                &RefString::try_from(author.to_string())?,
+
                patch_branch,
+
            );
+
            let url = git::Url::from(stored.id).with_namespace(author);
+

+
            // Fetch only the ref pointing to the patch revision.
+
            working.remote_anonymous(url.to_string().as_str())?.fetch(
+
                &[&format!("{refstr}:{remote_branch}")],
+
                None,
+
                None,
+
            )?;
+
            working.find_commit(patch_head.into()).map_err(|e| e.into())
+
        }
+
        Err(e) => Err(e.into()),
+
    }
+
}