cli: Allow checking out a specific revision
Allow `rad patch checkout` to specify a revision to checkout that may not be the patch's latest.
This commit is contained in:
parent
c94bd50666
commit
1376ab63c0
|
|
@ -150,7 +150,7 @@ pub enum Operation {
|
||||||
patch_id: Rev,
|
patch_id: Rev,
|
||||||
},
|
},
|
||||||
Checkout {
|
Checkout {
|
||||||
patch_id: Rev,
|
revision_id: Rev,
|
||||||
},
|
},
|
||||||
Comment {
|
Comment {
|
||||||
revision_id: Rev,
|
revision_id: Rev,
|
||||||
|
|
@ -339,7 +339,7 @@ impl Args for Options {
|
||||||
patch_id: patch_id.ok_or_else(|| anyhow!("a patch id must be provided"))?,
|
patch_id: patch_id.ok_or_else(|| anyhow!("a patch id must be provided"))?,
|
||||||
},
|
},
|
||||||
OperationName::Checkout => Operation::Checkout {
|
OperationName::Checkout => Operation::Checkout {
|
||||||
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
|
revision_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
|
||||||
},
|
},
|
||||||
OperationName::Comment => Operation::Comment {
|
OperationName::Comment => Operation::Comment {
|
||||||
revision_id: patch_id
|
revision_id: patch_id
|
||||||
|
|
@ -419,9 +419,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let patch_id = patch_id.resolve::<PatchId>(&repository.backend)?;
|
let patch_id = patch_id.resolve::<PatchId>(&repository.backend)?;
|
||||||
delete::run(&patch_id, &profile, &repository)?;
|
delete::run(&patch_id, &profile, &repository)?;
|
||||||
}
|
}
|
||||||
Operation::Checkout { patch_id } => {
|
Operation::Checkout { revision_id } => {
|
||||||
let patch_id = patch_id.resolve(&repository.backend)?;
|
let revision_id = revision_id.resolve::<radicle::git::Oid>(&repository.backend)?;
|
||||||
checkout::run(&patch_id, &repository, &workdir)?;
|
checkout::run(&patch::RevisionId::from(revision_id), &repository, &workdir)?;
|
||||||
}
|
}
|
||||||
Operation::Comment {
|
Operation::Comment {
|
||||||
revision_id,
|
revision_id,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::cob::patch;
|
use radicle::cob::patch;
|
||||||
use radicle::cob::patch::{Patch, PatchId};
|
use radicle::cob::patch::RevisionId;
|
||||||
use radicle::git::RefString;
|
use radicle::git::RefString;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
use radicle::storage::ReadRepository;
|
use radicle::storage::ReadRepository;
|
||||||
|
|
@ -10,20 +10,31 @@ use radicle::{git, rad};
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
patch_id: &PatchId,
|
revision_id: &RevisionId,
|
||||||
stored: &Repository,
|
stored: &Repository,
|
||||||
working: &git::raw::Repository,
|
working: &git::raw::Repository,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let patches = patch::Patches::open(stored)?;
|
let patches = patch::Patches::open(stored)?;
|
||||||
let patch = patches
|
|
||||||
.get(patch_id)?
|
let (patch_id, patch, _, revision) = patches
|
||||||
.ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;
|
.find_by_revision(revision_id)?
|
||||||
|
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||||
|
let (root, _) = patch.root();
|
||||||
|
// If we passed in the root revision, it's more likely that the user was specifying the
|
||||||
|
// patch itself. Hence, we checkout the latest update on the patch instead of that specific
|
||||||
|
// revision.
|
||||||
|
let revision = if *revision_id == root {
|
||||||
|
let (_, revision) = patch.latest();
|
||||||
|
revision
|
||||||
|
} else {
|
||||||
|
&revision
|
||||||
|
};
|
||||||
|
|
||||||
let mut spinner = term::spinner("Performing checkout...");
|
let mut spinner = term::spinner("Performing checkout...");
|
||||||
let patch_branch =
|
let patch_branch =
|
||||||
// SAFETY: Patch IDs are valid refstrings.
|
// SAFETY: Patch IDs are valid refstrings.
|
||||||
git::refname!("patch").join(RefString::try_from(term::format::cob(patch_id)).unwrap());
|
git::refname!("patch").join(RefString::try_from(term::format::cob(&patch_id)).unwrap());
|
||||||
let commit = find_patch_commit(&patch, stored, working)?;
|
let commit = find_patch_commit(revision, stored, working)?;
|
||||||
|
|
||||||
// Create patch branch and switch to it.
|
// Create patch branch and switch to it.
|
||||||
working.branch(patch_branch.as_str(), &commit, true)?;
|
working.branch(patch_branch.as_str(), &commit, true)?;
|
||||||
|
|
@ -36,7 +47,7 @@ pub fn run(
|
||||||
));
|
));
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
if let Some(branch) = rad::setup_patch_upstream(patch_id, *patch.head(), working, false)? {
|
if let Some(branch) = rad::setup_patch_upstream(&patch_id, revision.head(), working, false)? {
|
||||||
let tracking = branch
|
let tracking = branch
|
||||||
.name()?
|
.name()?
|
||||||
.ok_or_else(|| anyhow!("failed to create tracking branch: invalid name"))?;
|
.ok_or_else(|| anyhow!("failed to create tracking branch: invalid name"))?;
|
||||||
|
|
@ -52,23 +63,23 @@ pub fn run(
|
||||||
/// Try to find the patch head in our working copy, and if we don't find it,
|
/// Try to find the patch head in our working copy, and if we don't find it,
|
||||||
/// fetch it from storage first.
|
/// fetch it from storage first.
|
||||||
fn find_patch_commit<'a>(
|
fn find_patch_commit<'a>(
|
||||||
patch: &Patch,
|
revision: &patch::Revision,
|
||||||
stored: &Repository,
|
stored: &Repository,
|
||||||
working: &'a git::raw::Repository,
|
working: &'a git::raw::Repository,
|
||||||
) -> anyhow::Result<git::raw::Commit<'a>> {
|
) -> anyhow::Result<git::raw::Commit<'a>> {
|
||||||
let patch_head = *patch.head();
|
let head = *revision.head();
|
||||||
|
|
||||||
match working.find_commit(patch_head.into()) {
|
match working.find_commit(head) {
|
||||||
Ok(commit) => Ok(commit),
|
Ok(commit) => Ok(commit),
|
||||||
Err(e) if git::ext::is_not_found_err(&e) => {
|
Err(e) if git::ext::is_not_found_err(&e) => {
|
||||||
let url = git::url::File::new(stored.path());
|
let url = git::url::File::new(stored.path());
|
||||||
|
|
||||||
working.remote_anonymous(url.to_string().as_str())?.fetch(
|
working.remote_anonymous(url.to_string().as_str())?.fetch(
|
||||||
&[patch_head.to_string()],
|
&[head.to_string()],
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
working.find_commit(patch_head.into()).map_err(|e| e.into())
|
working.find_commit(head).map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => Err(e.into()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ pub fn run(
|
||||||
let mut patches = patch::Patches::open(repo)?;
|
let mut patches = patch::Patches::open(repo)?;
|
||||||
|
|
||||||
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
|
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
|
||||||
let Some((patch_id, patch, revision_id, revision)) = patches.find_by_revision(revision_id)? else {
|
let (patch_id, patch, revision_id, revision) = patches
|
||||||
anyhow::bail!("patch revision `{revision_id}` not found");
|
.find_by_revision(&patch::RevisionId::from(revision_id))?
|
||||||
};
|
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||||
let (body, reply_to) = prompt(message, reply_to, &revision, repo)?;
|
let (body, reply_to) = prompt(message, reply_to, &revision, repo)?;
|
||||||
let comment_id = patch.comment(revision_id, body, reply_to, &signer)?;
|
let comment_id = patch.comment(revision_id, body, reply_to, &signer)?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use radicle::cob::patch;
|
use radicle::cob::patch;
|
||||||
|
use radicle::git::Oid;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
|
|
@ -14,10 +15,10 @@ pub fn run(
|
||||||
let signer = &term::signer(profile)?;
|
let signer = &term::signer(profile)?;
|
||||||
let mut patches = patch::Patches::open(repository)?;
|
let mut patches = patch::Patches::open(repository)?;
|
||||||
|
|
||||||
let revision_id = revision_id.resolve(&repository.backend)?;
|
let revision_id = revision_id.resolve::<Oid>(&repository.backend)?;
|
||||||
let Some((patch_id, _, revision_id, _)) = patches.find_by_revision(revision_id)? else {
|
let (patch_id, _, revision_id, _) = patches
|
||||||
anyhow::bail!("patch revision `{revision_id}` not found");
|
.find_by_revision(&patch::RevisionId::from(revision_id))?
|
||||||
};
|
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||||
let Ok(mut patch) = patches.get_mut(&patch_id) else {
|
let Ok(mut patch) = patches.get_mut(&patch_id) else {
|
||||||
anyhow::bail!("Patch `{patch_id}` not found");
|
anyhow::bail!("Patch `{patch_id}` not found");
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::ffi::OsString;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
|
|
||||||
use radicle::cob::patch::{PatchId, Patches, Verdict};
|
use radicle::cob::patch::{PatchId, Patches, RevisionId, Verdict};
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::{git, rad};
|
use radicle::{git, rad};
|
||||||
|
|
||||||
|
|
@ -209,9 +209,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let mut patches = Patches::open(&repository)?;
|
let mut patches = Patches::open(&repository)?;
|
||||||
|
|
||||||
let (patch_id, revision) = if options.revision {
|
let (patch_id, revision) = if options.revision {
|
||||||
let id = options.id.resolve(&repository.backend)?;
|
let id = options.id.resolve::<git::Oid>(&repository.backend)?;
|
||||||
let (patch_id, _, rev_id, rev) = patches
|
let (patch_id, _, rev_id, rev) = patches
|
||||||
.find_by_revision(id)?
|
.find_by_revision(&RevisionId::from(id))?
|
||||||
.ok_or_else(|| anyhow!("revision {} does not exist", id))?;
|
.ok_or_else(|| anyhow!("revision {} does not exist", id))?;
|
||||||
|
|
||||||
(patch_id, Some((rev_id, rev)))
|
(patch_id, Some((rev_id, rev)))
|
||||||
|
|
|
||||||
|
|
@ -2082,24 +2082,23 @@ where
|
||||||
/// Find the `Patch` containing the given `Revision`.
|
/// Find the `Patch` containing the given `Revision`.
|
||||||
pub fn find_by_revision(
|
pub fn find_by_revision(
|
||||||
&self,
|
&self,
|
||||||
id: EntryId,
|
revision: &RevisionId,
|
||||||
) -> Result<Option<(PatchId, Patch, RevisionId, Revision)>, Error> {
|
) -> Result<Option<(PatchId, Patch, RevisionId, Revision)>, Error> {
|
||||||
// Revision may be the patch's first, making it have the same ID.
|
// Revision may be the patch's first, making it have the same ID.
|
||||||
let p_id = ObjectId::from(id);
|
let p_id = ObjectId::from(revision.into_inner());
|
||||||
let revision = RevisionId(id);
|
|
||||||
if let Some(p) = self.get(&p_id)? {
|
if let Some(p) = self.get(&p_id)? {
|
||||||
return Ok(p
|
return Ok(p
|
||||||
.revision(&revision)
|
.revision(revision)
|
||||||
.map(|r| (p_id, p.clone(), revision, r.clone())));
|
.map(|r| (p_id, p.clone(), *revision, r.clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.all()?
|
.all()?
|
||||||
.filter_map(|result| result.ok())
|
.filter_map(|result| result.ok())
|
||||||
.find_map(|(p_id, p)| {
|
.find_map(|(p_id, p)| {
|
||||||
p.revision(&revision)
|
p.revision(revision)
|
||||||
.map(|r| (p_id, p.clone(), revision, r.clone()))
|
.map(|r| (p_id, p.clone(), *revision, r.clone()))
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2284,10 +2283,7 @@ mod test {
|
||||||
assert_eq!(revision.oid, branch.oid);
|
assert_eq!(revision.oid, branch.oid);
|
||||||
assert_eq!(revision.base, branch.base);
|
assert_eq!(revision.base, branch.base);
|
||||||
|
|
||||||
let (id, _, _, _) = patches
|
let (id, _, _, _) = patches.find_by_revision(&rev_id).unwrap().unwrap();
|
||||||
.find_by_revision(rev_id.into_inner())
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(id, patch_id);
|
assert_eq!(id, patch_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue