Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
remote-helper: Allow commit roll-backs on push
cloudhead committed 2 years ago
commit 823a7e7c52a21d4450a1326df8a5ed315163c811
parent 193ed2f675ac6b0d1ab79ed65057c8a56a4fab85
3 files changed +74 -28
modified radicle-cli/examples/git/git-push-diverge.md
@@ -65,3 +65,20 @@ $ git push rad
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
   f2de534..f6cff86  master -> master
```
+

+
One thing of note is that we can't push an older commit either, by default:
+

+
``` ~alice
+
$ git reset --hard HEAD^ -q
+
```
+
``` ~alice (fail)
+
$ git push -f
+
```
+

+
We have to use the `allow.rollback` option:
+

+
``` ~alice (stderr)
+
$ git push -f -o allow.rollback
+
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
 + f6cff86...319a7dc master -> master (forced update)
+
```
modified radicle-remote-helper/src/lib.rs
@@ -38,6 +38,9 @@ pub enum Error {
    /// Invalid arguments received.
    #[error("invalid arguments: {0:?}")]
    InvalidArguments(Vec<String>),
+
    /// Unknown push option received.
+
    #[error("unknown push option {0:?}")]
+
    UnsupportedPushOption(String),
    /// Error with the remote url.
    #[error("invalid remote url: {0}")]
    RemoteUrl(#[from] UrlError),
@@ -68,6 +71,11 @@ pub enum Error {
}

#[derive(Debug, Default, Clone)]
+
pub struct Allow {
+
    rollback: bool,
+
}
+

+
#[derive(Debug, Default, Clone)]
pub struct Options {
    /// Don't sync after push.
    no_sync: bool,
@@ -77,6 +85,8 @@ pub struct Options {
    base: Option<Rev>,
    /// Patch message.
    message: cli::patch::Message,
+
    /// Operations allowed.
+
    allow: Allow,
}

/// Run the radicle remote helper using the given profile.
@@ -129,35 +139,11 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
                println!("ok");
            }
            ["option", "push-option", args @ ..] => {
-
                match *args {
-
                    ["sync"] => opts.no_sync = false,
-
                    ["no-sync"] => opts.no_sync = true,
-
                    ["patch.draft"] => opts.draft = true,
-
                    _ => {
-
                        let args = args.join(" ");
-

-
                        if let Some((key, val)) = args.split_once('=') {
-
                            match key {
-
                                "patch.message" => {
-
                                    opts.message.append(val);
-
                                }
-
                                "patch.base" => {
-
                                    let base = cli::args::rev(&val.into())
-
                                        .map_err(|e| Error::Base(e.into()))?;
-
                                    opts.base = Some(base);
-
                                }
-
                                _ => {
-
                                    println!("unsupported");
-
                                    continue;
-
                                }
-
                            }
-
                        } else {
-
                            println!("unsupported");
-
                            continue;
-
                        }
-
                    }
+
                if push_option(args, &mut opts).is_ok() {
+
                    println!("ok");
+
                } else {
+
                    println!("unsupported");
                }
-
                println!("ok");
            }
            ["option", "progress", ..] => {
                println!("unsupported");
@@ -204,6 +190,40 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
    }
}

+
/// Parse a single push option. Returns `Ok` if it was successful.
+
/// Note that some push options can contain spaces, eg. `patch.message="Hello World!"`,
+
/// hence the arguments are passed as a slice.
+
fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> {
+
    match args {
+
        ["sync"] => opts.no_sync = false,
+
        ["no-sync"] => opts.no_sync = true,
+
        ["patch.draft"] => opts.draft = true,
+
        ["allow.rollback"] => opts.allow.rollback = true,
+
        _ => {
+
            let args = args.join(" ");
+

+
            if let Some((key, val)) = args.split_once('=') {
+
                match key {
+
                    "patch.message" => {
+
                        opts.message.append(val);
+
                    }
+
                    "patch.base" => {
+
                        let base =
+
                            cli::args::rev(&val.into()).map_err(|e| Error::Base(e.into()))?;
+
                        opts.base = Some(base);
+
                    }
+
                    other => {
+
                        return Err(Error::UnsupportedPushOption(other.to_owned()));
+
                    }
+
                }
+
            } else {
+
                return Err(Error::UnsupportedPushOption(args.to_owned()));
+
            }
+
        }
+
    }
+
    Ok(())
+
}
+

/// Read one line from stdin, and split it into tokens.
pub(crate) fn read_line<'a>(stdin: &io::Stdin, line: &'a mut String) -> io::Result<Vec<&'a str>> {
    line.clear();
modified radicle-remote-helper/src/push.rs
@@ -226,12 +226,21 @@ pub fn run(

                        // If we're trying to update the canonical head, make sure
                        // we don't diverge from the current head.
+
                        //
+
                        // Note that we *do* allow rolling back to a previous commit on the
+
                        // canonical branch.
                        if dst == *canonical_ref && delegates.contains(&Did::from(nid)) {
                            let head = working.find_reference(src.as_str())?;
                            let head = head.peel_to_commit()?.id();
+
                            // Rollback is allowed and head is an ancestor of the canonical head.
+
                            let rollback = opts.allow.rollback
+
                                && working.graph_descendant_of(**canonical_oid, head)?;

                            if head != **canonical_oid
+
                                // Canonical head is *not* an ancestor of head.
                                && !working.graph_descendant_of(head, **canonical_oid)?
+
                                // Not a rollback.
+
                                && !rollback
                            {
                                eprintln!(
                                    "hint: you are attempting to push a commit that would \