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:
parent
8f57d60706
commit
5ccf38079c
|
|
@ -1823,6 +1823,7 @@ version = "0.8.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"git-ref-format",
|
||||
"json-color",
|
||||
"lexopt",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue