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
|
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||||
f2de534..f6cff86 master -> master
|
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.
|
/// Invalid arguments received.
|
||||||
#[error("invalid arguments: {0:?}")]
|
#[error("invalid arguments: {0:?}")]
|
||||||
InvalidArguments(Vec<String>),
|
InvalidArguments(Vec<String>),
|
||||||
|
/// Unknown push option received.
|
||||||
|
#[error("unknown push option {0:?}")]
|
||||||
|
UnsupportedPushOption(String),
|
||||||
/// Error with the remote url.
|
/// Error with the remote url.
|
||||||
#[error("invalid remote url: {0}")]
|
#[error("invalid remote url: {0}")]
|
||||||
RemoteUrl(#[from] UrlError),
|
RemoteUrl(#[from] UrlError),
|
||||||
|
|
@ -67,6 +70,11 @@ pub enum Error {
|
||||||
List(#[from] list::Error),
|
List(#[from] list::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct Allow {
|
||||||
|
rollback: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
/// Don't sync after push.
|
/// Don't sync after push.
|
||||||
|
|
@ -77,6 +85,8 @@ pub struct Options {
|
||||||
base: Option<Rev>,
|
base: Option<Rev>,
|
||||||
/// Patch message.
|
/// Patch message.
|
||||||
message: cli::patch::Message,
|
message: cli::patch::Message,
|
||||||
|
/// Operations allowed.
|
||||||
|
allow: Allow,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the radicle remote helper using the given profile.
|
/// Run the radicle remote helper using the given profile.
|
||||||
|
|
@ -129,35 +139,11 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
|
||||||
println!("ok");
|
println!("ok");
|
||||||
}
|
}
|
||||||
["option", "push-option", args @ ..] => {
|
["option", "push-option", args @ ..] => {
|
||||||
match *args {
|
if push_option(args, &mut opts).is_ok() {
|
||||||
["sync"] => opts.no_sync = false,
|
println!("ok");
|
||||||
["no-sync"] => opts.no_sync = true,
|
} else {
|
||||||
["patch.draft"] => opts.draft = true,
|
println!("unsupported");
|
||||||
_ => {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
println!("ok");
|
|
||||||
}
|
}
|
||||||
["option", "progress", ..] => {
|
["option", "progress", ..] => {
|
||||||
println!("unsupported");
|
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.
|
/// 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>> {
|
pub(crate) fn read_line<'a>(stdin: &io::Stdin, line: &'a mut String) -> io::Result<Vec<&'a str>> {
|
||||||
line.clear();
|
line.clear();
|
||||||
|
|
|
||||||
|
|
@ -226,12 +226,21 @@ pub fn run(
|
||||||
|
|
||||||
// If we're trying to update the canonical head, make sure
|
// If we're trying to update the canonical head, make sure
|
||||||
// we don't diverge from the current head.
|
// 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)) {
|
if dst == *canonical_ref && delegates.contains(&Did::from(nid)) {
|
||||||
let head = working.find_reference(src.as_str())?;
|
let head = working.find_reference(src.as_str())?;
|
||||||
let head = head.peel_to_commit()?.id();
|
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
|
if head != **canonical_oid
|
||||||
|
// Canonical head is *not* an ancestor of head.
|
||||||
&& !working.graph_descendant_of(head, **canonical_oid)?
|
&& !working.graph_descendant_of(head, **canonical_oid)?
|
||||||
|
// Not a rollback.
|
||||||
|
&& !rollback
|
||||||
{
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"hint: you are attempting to push a commit that would \
|
"hint: you are attempting to push a commit that would \
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue