Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
tools: Add `rad-merge` tool
cloudhead committed 2 years ago
commit bfc3c7164f2e77e797249357910f3f6311442a3b
parent 6c5cba3ca600d0577f3d99ea127225071459ba98
4 files changed +105 -20
modified radicle-remote-helper/src/push.rs
@@ -403,7 +403,7 @@ fn patch_update<G: Signer>(
    // and pushed, but the patch hasn't yet been updated. On push to the patch branch,
    // it'll seem like the patch is "empty", because the changes are already in the base branch.
    if base == head && patch.is_open() {
-
        patch_merge(patch, revision, head, working, stored, signer)?;
+
        patch_merge(patch, revision, head, working, signer)?;
    }

    Ok(())
@@ -470,7 +470,7 @@ fn patch_merge_all<G: Signer>(
            let revision_id = *revision_id;
            let patch = patch::PatchMut::new(id, patch, &mut patches);

-
            patch_merge(patch, revision_id, new, working, stored, signer)?;
+
            patch_merge(patch, revision_id, new, working, signer)?;
        }
    }
    Ok(())
@@ -481,33 +481,20 @@ fn patch_merge<G: Signer>(
    revision: patch::RevisionId,
    commit: git::Oid,
    working: &git::raw::Repository,
-
    stored: &storage::git::Repository,
    signer: &G,
) -> Result<(), Error> {
-
    patch.merge(revision, commit, signer)?;
+
    let merged = patch.merge(revision, commit, signer)?;

    eprintln!(
        "{} Patch {} merged",
        cli::format::positive("✓"),
-
        cli::format::tertiary(patch.id)
+
        cli::format::tertiary(merged.patch)
    );

    // Delete patch references that were created when the patch was opened.
    // Note that we don't return an error if we can't delete the refs, since it's
    // not critical.
-
    let nid = signer.public_key();
-
    let stored_ref = git::refs::storage::patch(&patch.id).with_namespace(nid.into());
-
    let working_ref = git::refs::workdir::patch_upstream(&patch.id);
-

-
    stored
-
        .raw()
-
        .find_reference(&stored_ref)
-
        .and_then(|mut r| r.delete())
-
        .ok();
-
    working
-
        .find_reference(&working_ref)
-
        .and_then(|mut r| r.delete())
-
        .ok();
+
    merged.cleanup(working, signer).ok();

    Ok(())
}
modified radicle-tools/Cargo.toml
@@ -28,6 +28,10 @@ name = "rad-self"
path = "src/rad-self.rs"

[[bin]]
+
name = "rad-merge"
+
path = "src/rad-merge.rs"
+

+
[[bin]]
name = "rad-set-canonical-refs"
path = "src/rad-set-canonical-refs.rs"

added radicle-tools/src/rad-merge.rs
@@ -0,0 +1,47 @@
+
use std::collections::HashSet;
+
use std::env;
+

+
use anyhow::anyhow;
+
use radicle::cob::patch::{PatchId, Patches};
+
use radicle::git::Oid;
+
use radicle::storage::ReadStorage;
+
use radicle_cli::terminal as term;
+

+
fn main() -> anyhow::Result<()> {
+
    let pid: PatchId = env::args()
+
        .nth(1)
+
        .ok_or_else(|| anyhow!("usage: rad-merge <patch-id>"))?
+
        .parse()?;
+
    let profile = radicle::Profile::load()?;
+
    let (working, rid) = radicle::rad::cwd()?;
+
    let stored = profile.storage.repository(rid)?;
+
    let mut patches = Patches::open(&stored)?;
+
    let mut patch = patches.get_mut(&pid)?;
+

+
    if patch.is_merged() {
+
        anyhow::bail!("fatal: patch {pid} is already merged");
+
    }
+
    let (revision, r) = patch.latest();
+
    let head = r.head();
+

+
    let mut revwalk = stored.backend.revwalk()?;
+
    revwalk.push_head()?;
+

+
    let commits = revwalk
+
        .map(|r| r.map(Oid::from))
+
        .collect::<Result<HashSet<Oid>, _>>()?;
+

+
    if !commits.contains(&head) {
+
        anyhow::bail!("fatal: patch head {head} is not in default branch");
+
    }
+
    let signer = term::signer(&profile)?;
+

+
    patch
+
        .merge(*revision, head, &signer)?
+
        .cleanup(&working, &signer)?;
+

+
    println!("✓ Patch {pid} merged at commit {head}");
+
    println!("You may now run `rad sync --announce`.");
+

+
    Ok(())
+
}
modified radicle/src/cob/patch.rs
@@ -25,6 +25,7 @@ use crate::identity;
use crate::identity::doc::DocError;
use crate::identity::PayloadError;
use crate::prelude::*;
+
use crate::storage;

/// Type name of a patch.
pub static TYPENAME: Lazy<TypeName> =
@@ -235,6 +236,46 @@ impl HistoryAction for Action {
    }
}

+
/// Output of a merge.
+
#[derive(Debug)]
+
#[must_use]
+
pub struct Merged<'a, R> {
+
    pub patch: PatchId,
+
    pub entry: EntryId,
+

+
    stored: &'a R,
+
}
+

+
impl<'a, R: WriteRepository> Merged<'a, R> {
+
    /// Cleanup after merging a patch.
+
    ///
+
    /// This removes Git refs relating to the patch, both in the working copy,
+
    /// and the stored copy; and updates `rad/sigrefs`.
+
    pub fn cleanup<G: Signer>(
+
        self,
+
        working: &git::raw::Repository,
+
        signer: &G,
+
    ) -> Result<(), storage::Error> {
+
        let nid = signer.public_key();
+
        let stored_ref = git::refs::storage::patch(&self.patch).with_namespace(nid.into());
+
        let working_ref = git::refs::workdir::patch_upstream(&self.patch);
+

+
        working
+
            .find_reference(&working_ref)
+
            .and_then(|mut r| r.delete())
+
            .ok();
+

+
        self.stored
+
            .raw()
+
            .find_reference(&stored_ref)
+
            .and_then(|mut r| r.delete())
+
            .ok();
+
        self.stored.sign_refs(signer)?;
+

+
        Ok(())
+
    }
+
}
+

/// Where a patch is intended to be merged.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -1501,9 +1542,15 @@ where
        revision: RevisionId,
        commit: git::Oid,
        signer: &G,
-
    ) -> Result<EntryId, Error> {
+
    ) -> Result<Merged<R>, Error> {
        // TODO: Don't allow merging the same revision twice?
-
        self.transaction("Merge revision", signer, |tx| tx.merge(revision, commit))
+
        let entry = self.transaction("Merge revision", signer, |tx| tx.merge(revision, commit))?;
+

+
        Ok(Merged {
+
            entry,
+
            patch: self.id,
+
            stored: self.store.as_ref(),
+
        })
    }

    /// Update a patch with a new revision.