Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
remote-helper: Only update patch after evaluating base
Lorenz Leutgeb committed 7 months ago
commit b98df1f3bcfe33dc229dff884ecf422a7c42866b
parent ee49e28766ce7c703b95e22d177cce046072f03d
1 file changed +36 -25
modified crates/radicle-remote-helper/src/push.rs
@@ -476,6 +476,30 @@ pub fn run(
    Ok(())
}

+
fn patch_base(
+
    head: &git::Oid,
+
    opts: &Options,
+
    stored: &storage::git::Repository,
+
) -> Result<git::Oid, Error> {
+
    let base = if let Some(base) = opts.base {
+
        base
+
    } else {
+
        // Computation of the canonical head is required only if the user
+
        // did not specify a base explicitly. This allows the user to
+
        // continue updating patches even while the canonical head cannot
+
        // be computed, e.g. while they wait for their fellow delegates
+
        // to converge and sync.
+
        let (_, target) = stored.canonical_head()?;
+
        stored.merge_base(&target, head)?
+
    };
+

+
    if base == *head {
+
        Err(Error::EmptyPatch)
+
    } else {
+
        Ok(base)
+
    }
+
}
+

/// Open a new patch.
fn patch_open<G>(
    src: &git::Oid,
@@ -497,6 +521,11 @@ where
    let head = *src;
    let dst = git::refs::storage::staging::patch(nid, head);

+
    let base = patch_base(&head, &opts, stored)?;
+

+
    let (title, description) =
+
        term::patch::get_create_message(opts.message, &stored.backend, &base, &head)?;
+

    // Before creating the patch, we must push the associated git objects to storage.
    // Unfortunately, we don't have an easy way to transfer the missing objects without
    // creating a temporary reference on the remote. The temporary reference is deleted
@@ -506,18 +535,6 @@ where
    // not fail, since the reference will already exist with the correct OID.
    push_ref(src, &dst, false, stored.raw(), opts.verbosity)?;

-
    let (_, target) = stored.canonical_head()?;
-
    let base = if let Some(base) = opts.base {
-
        base
-
    } else {
-
        stored.merge_base(&target, &head)?
-
    };
-
    if base == head {
-
        return Err(Error::EmptyPatch);
-
    }
-
    let (title, description) =
-
        term::patch::get_create_message(opts.message, &stored.backend, &base, &head)?;
-

    let patch = if opts.draft {
        patches.draft(
            title,
@@ -636,7 +653,7 @@ where
/// Update an existing patch.
#[allow(clippy::too_many_arguments)]
fn patch_update<G>(
-
    src: &git::Oid,
+
    head: &git::Oid,
    dst: &git::Qualified,
    force: bool,
    patch_id: patch::PatchId,
@@ -653,32 +670,26 @@ fn patch_update<G>(
where
    G: crypto::signature::Signer<crypto::Signature>,
{
-
    let commit = *src;
+
    let head = *head;
    let dst = dst.with_namespace(nid.into());

-
    push_ref(src, &dst, force, stored.raw(), opts.verbosity)?;
-

    let Ok(Some(patch)) = patches.get(&patch_id) else {
        return Err(Error::NotFound(patch_id));
    };

+
    let base = patch_base(&head, &opts, stored)?;
+

    // Don't update patch if it already has a revision matching this commit.
-
    if patch.revisions().any(|(_, r)| r.head() == commit) {
+
    if patch.revisions().any(|(_, r)| r.head() == head) {
        return Ok(None);
    }

    let (latest_id, latest) = patch.latest();
    let latest = latest.clone();

-
    let message = term::patch::get_update_message(opts.message, &stored.backend, &latest, &commit)?;
+
    let message = term::patch::get_update_message(opts.message, &stored.backend, &latest, &head)?;

-
    let (_, target) = stored.canonical_head()?;
-
    let head: git::Oid = commit;
-
    let base = if let Some(base) = opts.base {
-
        base
-
    } else {
-
        stored.merge_base(&target, &head)?
-
    };
+
    push_ref(&head, &dst, force, stored.raw(), opts.verbosity)?;

    let mut patch_mut = patch::PatchMut::new(patch_id, patch, &mut patches);
    let revision = patch_mut.update(message, base, head, signer)?;