remote-helper: Allow commit roll-backs on push
This makes the divergence check on push slightly more lenient, by allowing rolling back to a previous commit in the canonical branch history. Without this, it can become impossible to roll-back commits in certain situations.
This commit is contained in:
parent
193ed2f675
commit
823a7e7c52
|
|
@ -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)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
@ -67,6 +70,11 @@ pub enum Error {
|
|||
List(#[from] list::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Allow {
|
||||
rollback: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Options {
|
||||
/// Don't sync after push.
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
Loading…
Reference in New Issue