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...
|
✓ Pushing HEAD to storage...
|
||||||
✓ Analyzing remotes...
|
✓ 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
|
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;
|
||||||
use radicle::git::raw::Oid;
|
use radicle::git::raw::Oid;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage;
|
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
use radicle::storage::Remote;
|
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
use crate::terminal::args::Error;
|
use crate::terminal::args::Error;
|
||||||
|
|
@ -33,68 +31,32 @@ pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
|
||||||
Ok(oid.into())
|
Ok(oid.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of merge targets.
|
#[inline]
|
||||||
#[derive(Debug, Default)]
|
fn get_branch(git_ref: git::Qualified) -> git::RefString {
|
||||||
pub struct MergeTargets {
|
let (_, _, head, tail) = git_ref.non_empty_components();
|
||||||
/// Merge targets that have already merged the patch.
|
std::iter::once(head).chain(tail).collect()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the merge target for this patch. This can ben any tracked remote's "default"
|
/// Determine the merge target for this patch. This can ben any tracked remote's "default"
|
||||||
/// branch, as well as your own (eg. `rad/master`).
|
/// branch, as well as your own (eg. `rad/master`).
|
||||||
pub fn get_merge_target(
|
pub fn get_merge_target(
|
||||||
project: &Project,
|
|
||||||
storage: &Repository,
|
storage: &Repository,
|
||||||
head_branch: &git::raw::Branch,
|
head_branch: &git::raw::Branch,
|
||||||
) -> anyhow::Result<(storage::Remote, git::Oid)> {
|
) -> anyhow::Result<(git::RefString, git::Oid)> {
|
||||||
let head_oid = head_branch
|
let spinner = term::spinner("Analyzing remotes...");
|
||||||
.get()
|
let (qualified_ref, target_oid) = storage.canonical_head()?;
|
||||||
.target()
|
let head_oid = branch_oid(head_branch)?;
|
||||||
.ok_or(anyhow!("invalid HEAD ref; aborting"))?;
|
let merge_base = storage.raw().merge_base(*head_oid, *target_oid)?;
|
||||||
let mut spinner = term::spinner("Analyzing remotes...");
|
|
||||||
let targets = find_merge_targets(&head_oid, project.default_branch().as_refstr(), storage)?;
|
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.
|
// TODO: Tell user how many peers don't have this change.
|
||||||
spinner.finish();
|
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.
|
/// Return the [`Oid`] of the merge target.
|
||||||
|
|
@ -265,12 +227,3 @@ pub fn patch_commits<'a>(
|
||||||
}
|
}
|
||||||
Ok(commits)
|
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::cob::patch;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage;
|
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
|
@ -46,11 +45,10 @@ pub fn handle_patch_message(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_patch_commit_info(
|
fn show_patch_commit_info(
|
||||||
project: &Project,
|
|
||||||
workdir: &git::raw::Repository,
|
workdir: &git::raw::Repository,
|
||||||
node_id: &NodeId,
|
node_id: &NodeId,
|
||||||
head_branch: &git::raw::Branch,
|
head_branch: &git::raw::Branch,
|
||||||
target_peer: &storage::Remote,
|
target_ref: &git::RefStr,
|
||||||
target_oid: git::Oid,
|
target_oid: git::Oid,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let head_oid = branch_oid(head_branch)?;
|
let head_oid = branch_oid(head_branch)?;
|
||||||
|
|
@ -60,9 +58,8 @@ fn show_patch_commit_info(
|
||||||
|
|
||||||
term::blank();
|
term::blank();
|
||||||
term::info!(
|
term::info!(
|
||||||
"{}/{} ({}) <- {}/{} ({})",
|
"{} ({}) <- {}/{} ({})",
|
||||||
term::format::dim(term::format::node(&target_peer.id)),
|
term::format::highlight(target_ref),
|
||||||
term::format::highlight(project.default_branch().to_string()),
|
|
||||||
term::format::secondary(term::format::oid(*target_oid)),
|
term::format::secondary(term::format::oid(*target_oid)),
|
||||||
term::format::dim(term::format::node(node_id)),
|
term::format::dim(term::format::node(node_id)),
|
||||||
term::format::highlight(branch_name(head_branch)?),
|
term::format::highlight(branch_name(head_branch)?),
|
||||||
|
|
@ -91,27 +88,16 @@ pub fn run(
|
||||||
options: Options,
|
options: Options,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut patches = patch::Patches::open(storage)?;
|
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()?)?;
|
let head_branch = try_branch(workdir.head()?)?;
|
||||||
push_to_storage(storage, &head_branch, &options)?;
|
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.
|
// 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
|
// 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
|
// to date, and error out, unless the user specifies manually the merge
|
||||||
// base.
|
// base.
|
||||||
|
|
||||||
show_patch_commit_info(
|
show_patch_commit_info(workdir, profile.id(), &head_branch, &target_ref, target_oid)?;
|
||||||
&project,
|
|
||||||
workdir,
|
|
||||||
profile.id(),
|
|
||||||
&head_branch,
|
|
||||||
&target_peer,
|
|
||||||
target_oid,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
term::blank();
|
term::blank();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ pub fn run(
|
||||||
|
|
||||||
push_to_storage(storage, &head_branch, options)?;
|
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 mut patches = patch::Patches::open(storage)?;
|
||||||
|
|
||||||
let patch_id = match patch_id {
|
let patch_id = match patch_id {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue