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:
Alexis Sellier 2023-03-08 17:36:18 +01:00
parent aacacf872f
commit 34a2d1a9dc
No known key found for this signature in database
4 changed files with 23 additions and 84 deletions

View File

@ -29,7 +29,7 @@ $ rad patch open --message "Define power requirements" --message "See details."
✓ Pushing HEAD to storage...
✓ Analyzing remotes...
z6MknSL…StBU8Vi/master (f2de534) <- z6MknSLStBU8Vi/flux-capacitor-power (3e674d1)
master (f2de534) <- z6MknSLStBU8Vi/flux-capacitor-power (3e674d1)
1 commit(s) ahead, 0 commit(s) behind

View File

@ -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)
}
}

View File

@ -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();

View File

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