Sebastian Thielthanks clippye1003d54 years agoHistory
374 lines · 14.3 KB · Rust
RawDownload
use std::{convert::TryInto, str::FromStr}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use crate::{    spec,    spec::parse::{delegate, delegate::SiblingBranch, Delegate, Error},}; /// Parse a git [`revspec`](https://git-scm.com/docs/git-rev-parse#_specifying_revisions) and call `delegate` for each token/// successfully parsed.////// Note that the `delegate` is expected to maintain enough state to lookup revisions properly./// Returns `Ok(())` if all of `input` was consumed, or the error if either the `revspec` syntax was incorrect or/// the `delegate` failed to perform the request.pub fn parse(mut input: &BStr, delegate: &mut impl Delegate) -> Result<(), Error> {    let mut prev_kind = None;    if let Some(b'^') = input.get(0) {        input = next(input).1;        delegate.kind(spec::Kind::Range).ok_or(Error::Delegate)?;        prev_kind = spec::Kind::Range.into();    }     input = revision(input, delegate)?;    if let Some((rest, kind)) = try_range(input) {        if let Some(prev_kind) = prev_kind {            return Err(Error::KindSetTwice { prev_kind, kind });        }        delegate.kind(kind).ok_or(Error::Delegate)?;        input = revision(rest.as_bstr(), delegate)?;    }     if input.is_empty() {        delegate.done();        Ok(())    } else {        Err(Error::UnconsumedInput { input: input.into() })    }} fn try_set_prefix(delegate: &mut impl Delegate, hex_name: &BStr) -> Option<()> {    git_hash::Prefix::from_hex(hex_name.to_str().expect("hexadecimal only"))        .ok()        .and_then(|prefix| delegate.disambiguate_prefix(prefix))} fn long_describe_prefix(name: &BStr) -> Option<&BStr> {    let mut iter = name.rsplit(|b| *b == b'-');    let candidate = iter.by_ref().find_map(|substr| {        if substr.get(0)? != &b'g' {            return None;        };        let rest = substr.get(1..)?;        rest.iter().all(|b| b.is_ascii_hexdigit()).then(|| rest.as_bstr())    });    iter.any(|token| !token.is_empty()).then(|| candidate).flatten()} fn short_describe_prefix(name: &BStr) -> Option<&BStr> {    let mut iter = name.split(|b| *b == b'-');    let candidate = iter        .next()        .and_then(|prefix| prefix.iter().all(|b| b.is_ascii_hexdigit()).then(|| prefix.as_bstr()));    (iter.count() == 1).then(|| candidate).flatten()} type InsideParensRestConsumed<'a> = (std::borrow::Cow<'a, BStr>, &'a BStr, usize);fn parens(input: &[u8]) -> Result<Option<InsideParensRestConsumed<'_>>, Error> {    if input.get(0) != Some(&b'{') {        return Ok(None);    }    let mut open_braces = 0;    let mut ignore_next = false;    let mut skip_list = Vec::new();    for (idx, b) in input.iter().enumerate() {        match *b {            b'{' => {                if ignore_next {                    ignore_next = false;                } else {                    open_braces += 1                }            }            b'}' => {                if ignore_next {                    ignore_next = false;                } else {                    open_braces -= 1                }            }            b'\\' => {                skip_list.push(idx);                if ignore_next {                    skip_list.pop();                    ignore_next = false;                } else {                    ignore_next = true;                }            }            _ => {                if ignore_next {                    skip_list.pop();                };                ignore_next = false            }        }        if open_braces == 0 {            let inner: std::borrow::Cow<'_, _> = if skip_list.is_empty() {                input[1..idx].as_bstr().into()            } else {                let mut from = 1;                let mut buf = BString::default();                for next in skip_list.into_iter() {                    buf.push_str(&input[from..next]);                    from = next + 1;                }                if let Some(rest) = input.get(from..idx) {                    buf.push_str(rest);                }                buf.into()            };            return Ok(Some((inner, input[idx + 1..].as_bstr(), idx + 1)));        }    }    Err(Error::UnclosedBracePair { input: input.into() })} fn try_parse<T: FromStr + PartialEq + Default>(input: &BStr) -> Result<Option<T>, Error> {    input        .to_str()        .ok()        .and_then(|n| {            n.parse().ok().map(|n| {                if n == T::default() && input[0] == b'-' {                    return Err(Error::NegativeZero { input: input.into() });                };                Ok(n)            })        })        .transpose()} fn revision<'a>(mut input: &'a BStr, delegate: &mut impl Delegate) -> Result<&'a BStr, Error> {    fn consume_all(res: Option<()>) -> Result<&'static BStr, Error> {        res.ok_or(Error::Delegate).map(|_| "".into())    }    match input.as_ref() {        [b':'] => return Err(Error::MissingColonSuffix),        [b':', b'/'] => return Err(Error::EmptyTopLevelRegex),        [b':', b'/', regex @ ..] => {            let (regex, negated) = parse_regex_prefix(regex.as_bstr())?;            assert!(!regex.is_empty(), "This is handled earlier");            return consume_all(delegate.find(regex, negated));        }        [b':', b'0', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 0)),        [b':', b'1', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 1)),        [b':', b'2', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 2)),        [b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 0)),        _ => {}    };     let mut sep_pos = None;    let mut consecutive_hex_chars = Some(0);    {        let mut cursor = input;        let mut ofs = 0;        while let Some((pos, b)) = cursor.iter().enumerate().find(|(_, b)| {            if b"@~^:.".contains(b) {                true            } else {                if let Some(num) = consecutive_hex_chars.as_mut() {                    if b.is_ascii_hexdigit() {                        *num += 1;                    } else {                        consecutive_hex_chars = None;                    }                }                false            }        }) {            if *b != b'.' || cursor.get(pos + 1) == Some(&b'.') {                sep_pos = Some(ofs + pos);                break;            }            ofs += pos + 1;            cursor = &cursor[pos + 1..];        }    }     let name = &input[..sep_pos.unwrap_or(input.len())].as_bstr();    let mut sep = sep_pos.map(|pos| input[pos]);    let mut has_ref_or_implied_name = name.is_empty();    if name.is_empty() && sep == Some(b'@') && sep_pos.and_then(|pos| input.get(pos + 1)) != Some(&b'{') {        delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?;        sep_pos = sep_pos.map(|pos| pos + 1);        sep = match sep_pos.and_then(|pos| input.get(pos).copied()) {            None => return Ok("".into()),            Some(pos) => Some(pos),        };    } else {        (consecutive_hex_chars.unwrap_or(0) >= git_hash::Prefix::MIN_HEX_LEN)            .then(|| try_set_prefix(delegate, name))            .flatten()            .or_else(|| {                let prefix = long_describe_prefix(name).or_else(|| short_describe_prefix(name))?;                try_set_prefix(delegate, prefix)            })            .or_else(|| {                name.is_empty().then(|| ()).or_else(|| {                    #[allow(clippy::let_unit_value)]                    {                        let res = delegate.find_ref(name)?;                        has_ref_or_implied_name = true;                        res.into()                    }                })            })            .ok_or(Error::Delegate)?;    }     input = {        if let Some(b'@') = sep {            let past_sep = input[sep_pos.map(|pos| pos + 1).unwrap_or(input.len())..].as_bstr();            let (nav, rest, _consumed) = parens(past_sep)?.ok_or_else(|| Error::AtNeedsCurlyBrackets {                input: input[sep_pos.unwrap_or(input.len())..].into(),            })?;            let nav = nav.as_ref();            if let Some(n) = try_parse::<isize>(nav)? {                if n < 0 {                    if name.is_empty() {                        delegate                            .nth_checked_out_branch(n.abs().try_into().expect("non-negative isize fits usize"))                            .ok_or(Error::Delegate)?;                    } else {                        return Err(Error::RefnameNeedsPositiveReflogEntries { nav: nav.into() });                    }                } else if has_ref_or_implied_name {                    delegate                        .reflog(delegate::ReflogLookup::Entry(                            n.try_into().expect("non-negative isize fits usize"),                        ))                        .ok_or(Error::Delegate)?;                } else {                    return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() });                }            } else if let Some(kind) = SiblingBranch::parse(nav) {                if has_ref_or_implied_name {                    delegate.sibling_branch(kind).ok_or(Error::Delegate)                } else {                    Err(Error::SiblingBranchNeedsBranchName { name: (*name).into() })                }?            } else if has_ref_or_implied_name {                let time = git_date::parse(nav).ok_or_else(|| Error::Time { input: nav.into() })?;                delegate                    .reflog(delegate::ReflogLookup::Date(time))                    .ok_or(Error::Delegate)?;            } else {                return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() });            }            rest        } else {            if sep_pos == Some(0) && sep == Some(b'~') {                return Err(Error::MissingTildeAnchor);            }            input[sep_pos.unwrap_or(input.len())..].as_bstr()        }    };     navigate(input, delegate)} fn navigate<'a>(input: &'a BStr, delegate: &mut impl Delegate) -> Result<&'a BStr, Error> {    let mut cursor = 0;    while let Some(b) = input.get(cursor) {        cursor += 1;        match *b {            b'~' => {                let (number, consumed) = input                    .get(cursor..)                    .and_then(|past_sep| try_parse_usize(past_sep.as_bstr()).transpose())                    .transpose()?                    .unwrap_or((1, 0));                if number != 0 {                    delegate                        .traverse(delegate::Traversal::NthAncestor(number))                        .ok_or(Error::Delegate)?;                }                cursor += consumed;            }            b'^' => {                let past_sep = input.get(cursor..);                if let Some((number, consumed)) = past_sep                    .and_then(|past_sep| try_parse_usize(past_sep.as_bstr()).transpose())                    .transpose()?                {                    if number == 0 {                        delegate.peel_until(delegate::PeelTo::ObjectKind(git_object::Kind::Commit))                    } else {                        delegate.traverse(delegate::Traversal::NthParent(number))                    }                    .ok_or(Error::Delegate)?;                    cursor += consumed;                } else if let Some((kind, _rest, consumed)) =                    past_sep.and_then(|past_sep| parens(past_sep).transpose()).transpose()?                {                    cursor += consumed;                    let target = match kind.as_ref().as_ref() {                        b"commit" => delegate::PeelTo::ObjectKind(git_object::Kind::Commit),                        b"tag" => delegate::PeelTo::ObjectKind(git_object::Kind::Tag),                        b"tree" => delegate::PeelTo::ObjectKind(git_object::Kind::Tree),                        b"blob" => delegate::PeelTo::ObjectKind(git_object::Kind::Blob),                        b"object" => delegate::PeelTo::ExistingObject,                        b"" => delegate::PeelTo::RecursiveTagObject,                        regex if regex.starts_with(b"/") => {                            let (regex, negated) = parse_regex_prefix(regex[1..].as_bstr())?;                            if !regex.is_empty() {                                delegate.find(regex, negated).ok_or(Error::Delegate)?;                            }                            continue;                        }                        invalid => return Err(Error::InvalidObject { input: invalid.into() }),                    };                    delegate.peel_until(target).ok_or(Error::Delegate)?;                } else {                    delegate                        .traverse(delegate::Traversal::NthParent(1))                        .ok_or(Error::Delegate)?;                }            }            b':' => {                delegate                    .peel_until(delegate::PeelTo::Path(input[cursor..].as_bstr()))                    .ok_or(Error::Delegate)?;                return Ok("".into());            }            _ => return Ok(input[cursor - 1..].as_bstr()),        }    }    Ok("".into())} fn parse_regex_prefix(regex: &BStr) -> Result<(&BStr, bool), Error> {    Ok(match regex.strip_prefix(b"!") {        Some(regex) if regex.get(0) == Some(&b'!') => (regex.as_bstr(), false),        Some(regex) if regex.get(0) == Some(&b'-') => (regex[1..].as_bstr(), true),        Some(_regex) => return Err(Error::UnspecifiedRegexModifier { regex: regex.into() }),        None => (regex, false),    })} fn try_parse_usize(input: &BStr) -> Result<Option<(usize, usize)>, Error> {    let mut bytes = input.iter().peekable();    if bytes.peek().filter(|&&&b| b == b'-' || b == b'+').is_some() {        return Err(Error::SignedNumber { input: input.into() });    }    let num_digits = bytes.take_while(|b| b.is_ascii_digit()).count();    if num_digits == 0 {        return Ok(None);    }    let number = try_parse(&input[..num_digits])?.expect("parse number if only digits");    Ok(Some((number, num_digits)))} fn try_range(input: &BStr) -> Option<(&[u8], spec::Kind)> {    input        .strip_prefix(b"...")        .map(|rest| (rest, spec::Kind::MergeBase))        .or_else(|| input.strip_prefix(b"..").map(|rest| (rest, spec::Kind::Range)))} fn next(i: &BStr) -> (u8, &BStr) {    let b = i[0];    (b, i[1..].as_bstr())}