Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
remote-helper: Update Patch by ID Prefix
Lorenz Leutgeb committed 7 months ago
commit 2fbdaac76c8a7fc3fbe3fb186b0344b6bec01050
parent ee49e28766ce7c703b95e22d177cce046072f03d
4 files changed +137 -35
modified crates/radicle-cli/examples/rad-patch-update.md
@@ -72,3 +72,30 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
│ ↑ Revision ea7def3 @ 4d27214 by alice (you) now    │
╰────────────────────────────────────────────────────╯
```
+

+
Of course it is possible to update the patch via `git push` directly:
+

+
```
+
$ git mv README.md README.markdown
+
$ git commit -q -m "Longer file extension"
+
$ git push rad HEAD:patches/b6a23eb08656d
+
$ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
+
╭────────────────────────────────────────────────────╮
+
│ Title     Not a real change                        │
+
│ Patch     b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │
+
│ Author    alice (you)                              │
+
│ Head      344e9ca07a4bf4b68cae3676d42727e17135f889 │
+
│ Base      [..                                    ] │
+
│ Branches  feature/1                                │
+
│ Commits   ahead 3, behind 0                        │
+
│ Status    open                                     │
+
├────────────────────────────────────────────────────┤
+
│ 344e9ca Longer file extension                      │
+
│ 4d27214 Rename readme file                         │
+
│ 51b2f0f Not a real change                          │
+
├────────────────────────────────────────────────────┤
+
│ ● Revision b6a23eb @ 51b2f0f by alice (you) now    │
+
│ ↑ Revision ea7def3 @ 4d27214 by alice (you) now    │
+
│ ↑ Revision 226fa43 @ 344e9ca by alice (you) now    │
+
╰────────────────────────────────────────────────────╯
+
```

\ No newline at end of file
modified crates/radicle-remote-helper/src/push.rs
@@ -6,7 +6,6 @@ mod error;
use std::collections::HashMap;
use std::io::IsTerminal;
use std::process::ExitStatus;
-
use std::str::FromStr;
use std::{assert_eq, io};

use radicle::identity::crefs::GetCanonicalRefs as _;
@@ -211,8 +210,8 @@ impl Command {
enum PushAction {
    OpenPatch,
    UpdatePatch {
-
        dst: git::Qualified<'static>,
-
        patch: patch::PatchId,
+
        patch_id: patch::PatchId,
+
        patch: Box<patch::Patch>,
    },
    PushRef {
        dst: git::Qualified<'static>,
@@ -220,28 +219,50 @@ enum PushAction {
}

impl PushAction {
-
    fn new(dst: &git::RefString) -> Result<Self, error::PushAction> {
+
    fn new(
+
        dst: &git::RefString,
+
        patches: &cob::patch::Cache<
+
            cob::patch::Patches<'_, storage::git::Repository>,
+
            cob::cache::StoreWriter,
+
        >,
+
    ) -> Result<Self, error::PushAction> {
        if dst == &*rad::PATCHES_REFNAME {
-
            Ok(Self::OpenPatch)
-
        } else {
-
            let dst = git::Qualified::from_refstr(dst)
-
                .ok_or_else(|| error::PushAction::InvalidRef {
-
                    refname: dst.clone(),
-
                })?
-
                .to_owned();
-

-
            if let Some(oid) = dst.strip_prefix(git::refname!("refs/heads/patches")) {
-
                let patch = git::Oid::from_str(oid)
-
                    .map_err(|err| error::PushAction::InvalidPatchId {
-
                        suffix: oid.to_string(),
-
                        source: err,
-
                    })
-
                    .map(patch::PatchId::from)?;
-
                Ok(Self::UpdatePatch { dst, patch })
-
            } else {
-
                Ok(Self::PushRef { dst })
-
            }
+
            return Ok(Self::OpenPatch);
+
        }
+

+
        let dst = git::Qualified::from_refstr(dst)
+
            .ok_or_else(|| error::PushAction::InvalidRef {
+
                refname: dst.clone(),
+
            })?
+
            .to_owned();
+

+
        let Some(oid) = dst.strip_prefix(git::refname!("refs/heads/patches")) else {
+
            return Ok(Self::PushRef { dst });
+
        };
+

+
        let candidates = {
+
            let result: Result<Vec<_>, _> = patches.list_by_prefix(oid.to_string())?.collect();
+
            result?
+
        };
+

+
        if candidates.is_empty() {
+
            return Err(error::PushAction::InvalidPatchId {
+
                suffix: oid.to_string(),
+
            });
+
        } else if candidates.len() > 1 {
+
            return Err(error::PushAction::AmbiguousPatchId {
+
                suffix: oid.to_string(),
+
                candidates: candidates.iter().map(|(id, _)| id.to_string()).collect(),
+
            });
        }
+

+
        let mut candidates = candidates;
+
        let (patch_id, patch) = candidates.remove(0);
+

+
        Ok(Self::UpdatePatch {
+
            patch_id,
+
            patch: Box::new(patch),
+
        })
    }
}

@@ -319,7 +340,7 @@ pub fn run(
            }
            Command::Push(git::Refspec { src, dst, force }) => {
                let patches = crate::patches_mut(profile, stored)?;
-
                let action = PushAction::new(dst)?;
+
                let action = PushAction::new(dst, &patches)?;

                match action {
                    PushAction::OpenPatch => patch_open(
@@ -333,11 +354,14 @@ pub fn run(
                        profile,
                        opts.clone(),
                    ),
-
                    PushAction::UpdatePatch { dst, patch } => patch_update(
+
                    PushAction::UpdatePatch {
+
                        patch_id,
+
                        patch,
+
                    } => patch_update(
                        src,
-
                        &dst,
                        *force,
-
                        patch,
+
                        patch_id,
+
                        *patch,
                        &nid,
                        &working,
                        stored,
@@ -637,9 +661,9 @@ where
#[allow(clippy::too_many_arguments)]
fn patch_update<G>(
    src: &git::Oid,
-
    dst: &git::Qualified,
    force: bool,
    patch_id: patch::PatchId,
+
    patch: patch::Patch,
    nid: &NodeId,
    working: &git::raw::Repository,
    stored: &storage::git::Repository,
@@ -654,14 +678,10 @@ where
    G: crypto::signature::Signer<crypto::Signature>,
{
    let commit = *src;
-
    let dst = dst.with_namespace(nid.into());
+
    let dst = git::refs::patch(&patch_id).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));
-
    };
-

    // Don't update patch if it already has a revision matching this commit.
    if patch.revisions().any(|(_, r)| r.head() == commit) {
        return Ok(None);
modified crates/radicle-remote-helper/src/push/error.rs
@@ -1,5 +1,6 @@
use radicle::git;
use radicle::git::canonical;
+
use radicle::patch;
use thiserror::Error;

#[derive(Debug, Error)]
@@ -36,11 +37,17 @@ pub struct HeadsDiverge {

#[derive(Debug, Error)]
pub enum PushAction {
+
    #[error(transparent)]
+
    Cache(#[from] patch::cache::Error),
    #[error("invalid reference {refname}, expected qualified reference starting with `refs/`")]
    InvalidRef { refname: git::RefString },
    #[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID")]
-
    InvalidPatchId {
+
    InvalidPatchId { suffix: String },
+
    #[error(
+
        "found refs/heads/patches/{suffix} where {suffix} was an ambiguous among {candidates:?}"
+
    )]
+
    AmbiguousPatchId {
        suffix: String,
-
        source: git::raw::Error,
+
        candidates: Vec<String>,
    },
}
modified crates/radicle/src/cob/patch/cache.rs
@@ -46,6 +46,10 @@ pub trait Patches {
    /// [`Patches::drafted`], [`Patches::merged`].
    fn list_by_status(&self, status: &Status) -> Result<Self::Iter<'_>, Self::Error>;

+
    /// List all patches in the store with an id that starts with the
+
    /// given `prefix`.
+
    fn list_by_prefix(&self, prefix: String) -> Result<Self::Iter<'_>, Self::Error>;
+

    /// Get the [`PatchCounts`] of all the patches in the store.
    fn counts(&self) -> Result<PatchCounts, Self::Error>;

@@ -469,6 +473,10 @@ where
        query::list_by_status(&self.cache.db, &self.rid(), status)
    }

+
    fn list_by_prefix(&self, prefix: String) -> Result<Self::Iter<'_>, Self::Error> {
+
        query::list_by_prefix(&self.cache.db, &self.rid(), prefix)
+
    }
+

    fn counts(&self) -> Result<PatchCounts, Self::Error> {
        query::counts(&self.cache.db, &self.rid())
    }
@@ -530,6 +538,22 @@ where
            .map_err(super::Error::from)
    }

+
    fn list_by_prefix(&self, prefix: String) -> Result<Self::Iter<'_>, Self::Error> {
+
        self.store
+
            .all()
+
            .map(move |inner| NoCacheIter {
+
                inner: Box::new(inner.into_iter().filter_map(move |res| {
+
                    match res {
+
                        Ok((id, patch)) => (id.to_string().starts_with(&prefix))
+
                            .then_some((id, patch))
+
                            .map(Ok),
+
                        Err(e) => Some(Err(e.into())),
+
                    }
+
                })),
+
            })
+
            .map_err(super::Error::from)
+
    }
+

    fn counts(&self) -> Result<PatchCounts, Self::Error> {
        self.store.counts().map_err(super::Error::from)
    }
@@ -561,6 +585,10 @@ where
        query::list_by_status(&self.cache.db, &self.rid(), status)
    }

+
    fn list_by_prefix(&self, prefix: String) -> Result<Self::Iter<'_>, Self::Error> {
+
        query::list_by_prefix(&self.cache.db, &self.rid(), prefix)
+
    }
+

    fn counts(&self) -> Result<PatchCounts, Self::Error> {
        query::counts(&self.cache.db, &self.rid())
    }
@@ -650,6 +678,26 @@ mod query {
        })
    }

+
    pub(super) fn list_by_prefix<'a>(
+
        db: &'a sql::ConnectionThreadSafe,
+
        rid: &RepoId,
+
        prefix: String,
+
    ) -> Result<PatchesIter<'a>, Error> {
+
        let mut stmt = db.prepare(
+
            "SELECT patches.id, patch
+
             FROM patches
+
             WHERE repo = ?1
+
             AND id LIKE ?2 || '%'
+
             ORDER BY id
+
            ",
+
        )?;
+
        stmt.bind((1, rid))?;
+
        stmt.bind((2, sql::Value::String(prefix)))?;
+
        Ok(PatchesIter {
+
            inner: stmt.into_iter(),
+
        })
+
    }
+

    pub(super) fn list_by_status<'a>(
        db: &'a sql::ConnectionThreadSafe,
        rid: &RepoId,