use crate::command_checkout_table::{
CHECKOUT_TABLE, DISCARD_CHANGES, FORCE, PATCH, PATHSPEC_FROM_FILE, STAGE, SWITCH_TABLE,
};
use crate::command_options::{Boundary, DEFAULT_MODE, OptionError, ParsedOptions, parse_options};
use crate::command_options_query::{is_enabled, is_stated, positional_tokens};
use std::ffi::OsString;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Discard {
Harmless,
MovesHead,
Overwrites,
}
pub fn checkout_discard(region: &[OsString]) -> Result<Discard, OptionError> {
let parsed: ParsedOptions = parse_options(region, CHECKOUT_TABLE, DEFAULT_MODE, &[])?;
let names: usize = positional_tokens(&parsed, region.len()).len();
if is_enabled(&parsed, FORCE)
|| is_enabled(&parsed, PATCH)
|| is_stated(&parsed, STAGE)
|| is_stated(&parsed, PATHSPEC_FROM_FILE)
|| matches!(parsed.boundary, Boundary::DashDash(_))
|| names > 1
{
return Ok(Discard::Overwrites);
}
if names == 1 {
return Ok(Discard::MovesHead);
}
return Ok(Discard::Harmless);
}
pub fn switch_discard(region: &[OsString]) -> Result<Discard, OptionError> {
let parsed: ParsedOptions = parse_options(region, SWITCH_TABLE, DEFAULT_MODE, &[])?;
if is_enabled(&parsed, FORCE) || is_enabled(&parsed, DISCARD_CHANGES) {
return Ok(Discard::Overwrites);
}
if !positional_tokens(&parsed, region.len()).is_empty() {
return Ok(Discard::MovesHead);
}
return Ok(Discard::Harmless);
}
fn options_of(region: &[OsString]) -> Vec<&[u8]> {
let mut tokens: Vec<&[u8]> = Vec::<&[u8]>::new();
for token in region {
let bytes: &[u8] = token.as_encoded_bytes();
if bytes == b"--" {
break;
}
tokens.push(bytes);
}
return tokens;
}
fn is_equals(byte: &u8) -> bool {
return *byte == b'=';
}
fn spells_long(token: &[u8], full: &str, minimum: usize) -> bool {
if !token.starts_with(b"--") {
return false;
}
let name: &[u8] = token.split(is_equals).next().unwrap_or_default();
return name.len() >= minimum && full.as_bytes().starts_with(name);
}
fn has_short(token: &[u8], letter: u8) -> bool {
return token.len() > 1 && token[0] == b'-' && token[1] != b'-' && token[1..].contains(&letter);
}
pub fn restore_discard(region: &[OsString]) -> Discard {
for token in options_of(region) {
if token == b"-h" || spells_long(token, "--help", 4) {
return Discard::Harmless;
}
}
return Discard::Overwrites;
}
pub fn restores_index_only(region: &[OsString]) -> bool {
let tokens: Vec<&[u8]> = options_of(region);
let mut staged: bool = false;
let mut worktree: bool = false;
let mut at: usize = 0;
while at < tokens.len() {
let token: &[u8] = tokens[at];
at += 1;
if token.starts_with(b"--") {
if spells_long(token, "--staged", 4) {
staged = true;
}
if spells_long(token, "--worktree", 3) {
worktree = true;
}
if !token.contains(&b'=') && (spells_long(token, "--source", 4) || spells_long(token, "--conflict", 3)) {
at += 1;
}
continue;
}
if token.len() > 1 && token[0] == b'-' {
for (position, letter) in token[1..].iter().enumerate() {
match *letter {
b'S' => staged = true,
b'W' => worktree = true,
b's' => {
if position + 2 == token.len() {
at += 1;
}
break;
}
_ => {}
}
}
}
}
return staged && !worktree;
}
pub fn force_discard(region: &[OsString]) -> Discard {
for token in options_of(region) {
if has_short(token, b'f') || spells_long(token, "--force", 4) {
return Discard::Overwrites;
}
}
return Discard::Harmless;
}
pub fn read_tree_discard(region: &[OsString]) -> Discard {
for token in options_of(region) {
if spells_long(token, "--reset", 5) {
return Discard::Overwrites;
}
}
return Discard::Harmless;
}
pub fn abort_discard(region: &[OsString]) -> Discard {
for token in options_of(region) {
if spells_long(token, "--abort", 4) {
return Discard::Overwrites;
}
}
return Discard::Harmless;
}
#[cfg(test)]
#[path = "command_discard_tests.rs"]
mod tests;