cli: use canonical head for patch target
Correct assumptions made about how patching work in `rad patch`. Make the default Patch target a `git ref` in place of a target 'peer'. Use the canonical head as the git reference. This is head of the project's master branch which project delegates have concensus on.
This commit is contained in:
parent
aacacf872f
commit
34a2d1a9dc
|
|
@ -29,7 +29,7 @@ $ rad patch open --message "Define power requirements" --message "See details."
|
|||
✓ Pushing HEAD to storage...
|
||||
✓ Analyzing remotes...
|
||||
|
||||
z6MknSL…StBU8Vi/master (f2de534) <- z6MknSL…StBU8Vi/flux-capacitor-power (3e674d1)
|
||||
master (f2de534) <- z6MknSL…StBU8Vi/flux-capacitor-power (3e674d1)
|
||||
|
||||
1 commit(s) ahead, 0 commit(s) behind
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ use radicle::cob::patch::{Clock, MergeTarget, Patch, PatchId, Patches};
|
|||
use radicle::git;
|
||||
use radicle::git::raw::Oid;
|
||||
use radicle::prelude::*;
|
||||
use radicle::storage;
|
||||
use radicle::storage::git::Repository;
|
||||
use radicle::storage::Remote;
|
||||
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::Error;
|
||||
|
|
@ -33,68 +31,32 @@ pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
|
|||
Ok(oid.into())
|
||||
}
|
||||
|
||||
/// List of merge targets.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MergeTargets {
|
||||
/// Merge targets that have already merged the patch.
|
||||
pub merged: Vec<Remote>,
|
||||
/// Merge targets that haven't merged the patch.
|
||||
pub not_merged: Vec<(Remote, git::Oid)>,
|
||||
}
|
||||
|
||||
/// Find potential merge targets for the given head.
|
||||
pub fn find_merge_targets(
|
||||
head: &Oid,
|
||||
branch: &git::RefStr,
|
||||
storage: &Repository,
|
||||
) -> anyhow::Result<MergeTargets> {
|
||||
let mut targets = MergeTargets::default();
|
||||
|
||||
for remote in storage.remotes()? {
|
||||
let (_, remote) = remote?;
|
||||
let Some(target_oid) = remote.refs.head(branch) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if is_merged(storage.raw(), target_oid.into(), *head)? {
|
||||
targets.merged.push(remote);
|
||||
} else {
|
||||
targets.not_merged.push((remote, target_oid));
|
||||
}
|
||||
}
|
||||
Ok(targets)
|
||||
#[inline]
|
||||
fn get_branch(git_ref: git::Qualified) -> git::RefString {
|
||||
let (_, _, head, tail) = git_ref.non_empty_components();
|
||||
std::iter::once(head).chain(tail).collect()
|
||||
}
|
||||
|
||||
/// Determine the merge target for this patch. This can ben any tracked remote's "default"
|
||||
/// branch, as well as your own (eg. `rad/master`).
|
||||
pub fn get_merge_target(
|
||||
project: &Project,
|
||||
storage: &Repository,
|
||||
head_branch: &git::raw::Branch,
|
||||
) -> anyhow::Result<(storage::Remote, git::Oid)> {
|
||||
let head_oid = head_branch
|
||||
.get()
|
||||
.target()
|
||||
.ok_or(anyhow!("invalid HEAD ref; aborting"))?;
|
||||
let mut spinner = term::spinner("Analyzing remotes...");
|
||||
let targets = find_merge_targets(&head_oid, project.default_branch().as_refstr(), storage)?;
|
||||
) -> anyhow::Result<(git::RefString, git::Oid)> {
|
||||
let spinner = term::spinner("Analyzing remotes...");
|
||||
let (qualified_ref, target_oid) = storage.canonical_head()?;
|
||||
let head_oid = branch_oid(head_branch)?;
|
||||
let merge_base = storage.raw().merge_base(*head_oid, *target_oid)?;
|
||||
|
||||
if head_oid == merge_base.into() {
|
||||
anyhow::bail!("commits are already included in the target branch; nothing to do");
|
||||
}
|
||||
|
||||
// eg. `refs/namespaces/<peer>/refs/heads/master`
|
||||
let (target_peer, target_oid) = match targets.not_merged.as_slice() {
|
||||
[] => {
|
||||
spinner.message("All tracked peers are up to date.");
|
||||
todo!("handle case without target");
|
||||
}
|
||||
[target] => target,
|
||||
_ => {
|
||||
// TODO: Let user select which branch to use as a target.
|
||||
todo!();
|
||||
}
|
||||
};
|
||||
// TODO: Tell user how many peers don't have this change.
|
||||
spinner.finish();
|
||||
|
||||
Ok((target_peer.clone(), *target_oid))
|
||||
let branch = get_branch(qualified_ref);
|
||||
Ok((branch, (*target_oid).into()))
|
||||
}
|
||||
|
||||
/// Return the [`Oid`] of the merge target.
|
||||
|
|
@ -265,12 +227,3 @@ pub fn patch_commits<'a>(
|
|||
}
|
||||
Ok(commits)
|
||||
}
|
||||
|
||||
/// Check whether a commit has been merged into a target branch.
|
||||
pub fn is_merged(repo: &git::raw::Repository, target: Oid, commit: Oid) -> Result<bool, Error> {
|
||||
if let Ok(base) = repo.merge_base(target, commit) {
|
||||
Ok(base == commit)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use anyhow::{anyhow, Context};
|
||||
use anyhow::anyhow;
|
||||
|
||||
use radicle::cob::patch;
|
||||
use radicle::git;
|
||||
use radicle::prelude::*;
|
||||
use radicle::storage;
|
||||
use radicle::storage::git::Repository;
|
||||
|
||||
use crate::terminal as term;
|
||||
|
|
@ -46,11 +45,10 @@ pub fn handle_patch_message(
|
|||
}
|
||||
|
||||
fn show_patch_commit_info(
|
||||
project: &Project,
|
||||
workdir: &git::raw::Repository,
|
||||
node_id: &NodeId,
|
||||
head_branch: &git::raw::Branch,
|
||||
target_peer: &storage::Remote,
|
||||
target_ref: &git::RefStr,
|
||||
target_oid: git::Oid,
|
||||
) -> anyhow::Result<()> {
|
||||
let head_oid = branch_oid(head_branch)?;
|
||||
|
|
@ -60,9 +58,8 @@ fn show_patch_commit_info(
|
|||
|
||||
term::blank();
|
||||
term::info!(
|
||||
"{}/{} ({}) <- {}/{} ({})",
|
||||
term::format::dim(term::format::node(&target_peer.id)),
|
||||
term::format::highlight(project.default_branch().to_string()),
|
||||
"{} ({}) <- {}/{} ({})",
|
||||
term::format::highlight(target_ref),
|
||||
term::format::secondary(term::format::oid(*target_oid)),
|
||||
term::format::dim(term::format::node(node_id)),
|
||||
term::format::highlight(branch_name(head_branch)?),
|
||||
|
|
@ -91,27 +88,16 @@ pub fn run(
|
|||
options: Options,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut patches = patch::Patches::open(storage)?;
|
||||
let project = storage.project_of(&profile.public_key).context(format!(
|
||||
"couldn't load project {} from local state",
|
||||
storage.id
|
||||
))?;
|
||||
let head_branch = try_branch(workdir.head()?)?;
|
||||
push_to_storage(storage, &head_branch, &options)?;
|
||||
let (target_peer, target_oid) = get_merge_target(&project, storage, &head_branch)?;
|
||||
let (target_ref, target_oid) = get_merge_target(storage, &head_branch)?;
|
||||
|
||||
// TODO: Handle case where `rad/master` isn't up to date with the target.
|
||||
// In that case we should warn the user that their master branch is not up
|
||||
// to date, and error out, unless the user specifies manually the merge
|
||||
// base.
|
||||
|
||||
show_patch_commit_info(
|
||||
&project,
|
||||
workdir,
|
||||
profile.id(),
|
||||
&head_branch,
|
||||
&target_peer,
|
||||
target_oid,
|
||||
)?;
|
||||
show_patch_commit_info(workdir, profile.id(), &head_branch, &target_ref, target_oid)?;
|
||||
|
||||
term::blank();
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ pub fn run(
|
|||
|
||||
push_to_storage(storage, &head_branch, options)?;
|
||||
|
||||
let (_, target_oid) = get_merge_target(&project, storage, &head_branch)?;
|
||||
let (_, target_oid) = get_merge_target(storage, &head_branch)?;
|
||||
let mut patches = patch::Patches::open(storage)?;
|
||||
|
||||
let patch_id = match patch_id {
|
||||
|
|
|
|||
Loading…
Reference in New Issue