use crate::command_options::{
Arity, DEFAULT_MODE, OptionError, OptionSpec, ParsedOptions, UNREAD, WrapperOccurrence,
parse_options, row,
};
use crate::command_options_query::{is_enabled, is_stated, last_occurrence, positional_tokens, value_bytes};
use std::ffi::OsString;
pub const ATOMIC: u16 = 1;
pub const DRY_RUN: u16 = 2;
pub const FORCE: u16 = 4;
pub const FORCE_WITH_LEASE: u16 = 8;
pub const FORCE_IF_INCLUDES: u16 = 16;
pub const MIRROR: u16 = 32;
pub const DELETE: u16 = 64;
pub const ALL: u16 = 128;
pub const TAGS: u16 = 256;
pub const PRUNE: u16 = 512;
pub const FOLLOW_TAGS: u16 = 1024;
pub const REPO: u16 = 2048;
pub const PORCELAIN: u16 = 4096;
pub const SET_UPSTREAM: u16 = 8192;
pub const NO_VERIFY: u16 = 16384;
pub const RECURSE_SUBMODULES: u16 = 32768;
pub const PUSH_TABLE: &[OptionSpec] = &[
row(UNREAD, Some(b'v'), Some("verbose"), Arity::None, true),
row(UNREAD, Some(b'q'), Some("quiet"), Arity::None, true),
row(REPO, None, Some("repo"), Arity::Required, true),
row(ALL, None, Some("all"), Arity::None, true),
row(ALL, None, Some("branches"), Arity::None, true),
row(MIRROR, None, Some("mirror"), Arity::None, true),
row(DELETE, Some(b'd'), Some("delete"), Arity::None, true),
row(TAGS, None, Some("tags"), Arity::None, true),
row(DRY_RUN, Some(b'n'), Some("dry-run"), Arity::None, true),
row(PORCELAIN, None, Some("porcelain"), Arity::None, true),
row(FORCE, Some(b'f'), Some("force"), Arity::None, true),
row(
FORCE_WITH_LEASE,
None,
Some("force-with-lease"),
Arity::Optional,
true,
),
row(FORCE_IF_INCLUDES, None, Some("force-if-includes"), Arity::None, true),
row(
RECURSE_SUBMODULES,
None,
Some("recurse-submodules"),
Arity::Required,
true,
),
row(UNREAD, None, Some("thin"), Arity::None, true),
row(UNREAD, None, Some("receive-pack"), Arity::Required, true),
row(UNREAD, None, Some("exec"), Arity::Required, true),
row(SET_UPSTREAM, Some(b'u'), Some("set-upstream"), Arity::None, true),
row(UNREAD, None, Some("progress"), Arity::None, true),
row(PRUNE, None, Some("prune"), Arity::None, true),
row(NO_VERIFY, None, Some("no-verify"), Arity::None, true),
row(FOLLOW_TAGS, None, Some("follow-tags"), Arity::None, true),
row(UNREAD, None, Some("signed"), Arity::Optional, true),
row(ATOMIC, None, Some("atomic"), Arity::None, true),
row(
UNREAD,
Some(b'o'),
Some("push-option"),
Arity::Required,
true,
),
row(UNREAD, Some(b'4'), Some("ipv4"), Arity::None, false),
row(UNREAD, Some(b'6'), Some("ipv6"), Arity::None, false),
];
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PushRegion {
pub atomic_stated: bool,
pub dry_run: bool,
pub no_verify: bool,
pub wrapper: Vec<WrapperOccurrence>,
}
pub fn parse_push_region(
region: &[OsString],
wrapper_flags: &[&[u8]],
) -> Result<PushRegion, OptionError> {
let parsed: ParsedOptions = parse_options(region, PUSH_TABLE, DEFAULT_MODE, wrapper_flags)?;
return Ok(PushRegion {
atomic_stated: is_stated(&parsed, ATOMIC),
dry_run: is_enabled(&parsed, DRY_RUN),
no_verify: is_enabled(&parsed, NO_VERIFY),
wrapper: parsed.wrapper,
});
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PushShape {
pub repository: Option<OsString>,
pub refspecs: Vec<OsString>,
pub branches: bool,
pub tags: bool,
pub mirror: bool,
pub deletes: bool,
pub recurse_submodules: Option<OsString>,
}
pub fn push_shape(region: &[OsString]) -> Option<PushShape> {
let parsed: ParsedOptions = parse_options(region, PUSH_TABLE, DEFAULT_MODE, &[]).ok()?;
let mut positionals: Vec<OsString> = positional_tokens(&parsed, region.len())
.into_iter()
.map(|index| {
return region[index].clone();
})
.collect();
let value_of = |id: u16| -> Option<OsString> {
let value = last_occurrence(&parsed, id).and_then(|occurrence| {
return occurrence.value;
})?;
return Some(<OsString as std::os::unix::ffi::OsStringExt>::from_vec(value_bytes(region, value).to_vec()));
};
let repository: Option<OsString> =
if positionals.is_empty() { value_of(REPO) } else { Some(positionals.remove(0)) };
let mut refspecs: Vec<OsString> = Vec::new();
let mut words = positionals.into_iter();
while let Some(word) = words.next() {
if word.as_encoded_bytes() == b"tag"
&& let Some(name) = words.next()
{
let mut joined: OsString = OsString::from("refs/tags/");
joined.push(name);
refspecs.push(joined);
} else {
refspecs.push(word);
}
}
return Some(PushShape {
repository,
refspecs,
branches: is_enabled(&parsed, ALL),
tags: is_enabled(&parsed, TAGS),
mirror: is_enabled(&parsed, MIRROR),
deletes: is_enabled(&parsed, DELETE),
recurse_submodules: value_of(RECURSE_SUBMODULES),
});
}
const NOT_STACKABLE: &[u16] = &[
DRY_RUN,
FORCE,
FORCE_WITH_LEASE,
FORCE_IF_INCLUDES,
MIRROR,
DELETE,
ALL,
TAGS,
PRUNE,
FOLLOW_TAGS,
REPO,
PORCELAIN,
SET_UPSTREAM,
];
pub fn stackable_option_tokens(region: &[OsString]) -> Option<Vec<OsString>> {
let parsed: ParsedOptions = parse_options(region, PUSH_TABLE, DEFAULT_MODE, &[]).ok()?;
if NOT_STACKABLE.iter().any(|id| {
return is_enabled(&parsed, *id);
}) {
return None;
}
let positionals: Vec<usize> = positional_tokens(&parsed, region.len());
if positionals.iter().any(|index| {
return region[*index].as_encoded_bytes().first() == Some(&b'+');
}) {
return None;
}
return Some(
region
.iter()
.enumerate()
.filter(|(index, _)| {
return !positionals.contains(index);
})
.map(|(_, token)| {
return token.clone();
})
.collect(),
);
}
#[cfg(test)]
#[path = "command_push_tests.rs"]
mod tests;