cli: Make `patch checkout` fetch missing objects

Before this change, `rad patch checkout` would fail if the patch commits
were not in the working copy.
This commit is contained in:
Alexis Sellier 2023-03-17 16:58:29 +01:00
parent 8f57d60706
commit 5ccf38079c
No known key found for this signature in database
4 changed files with 71 additions and 23 deletions

1
Cargo.lock generated
View File

@ -1823,6 +1823,7 @@ version = "0.8.0"
dependencies = [
"anyhow",
"chrono",
"git-ref-format",
"json-color",
"lexopt",
"log",

View File

@ -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"] }

View File

@ -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
```

View File

@ -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())?;
// 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))?;
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();
// 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()),
}
}