| use std::path::Path; |
| |
| use bstr::BStr; |
| use gix_attributes::StateRef; |
| use smallvec::SmallVec; |
| |
| use crate::{ |
| driver, eol, |
| eol::AttributesDigest, |
| pipeline::{convert::configuration, Context, CrlfRoundTripCheck}, |
| Driver, |
| }; |
| |
| /// A utility to do buffer-swapping with. |
| #[derive(Default, Clone)] |
| pub(crate) struct Buffers { |
| pub src: Vec<u8>, |
| pub dest: Vec<u8>, |
| } |
| |
| /// A utility to do buffer-swapping with. |
| pub(crate) struct BuffersWithSource<'src, 'bufs> { |
| pub ro_src: Option<&'src [u8]>, |
| pub src: &'bufs mut Vec<u8>, |
| pub dest: &'bufs mut Vec<u8>, |
| } |
| |
| impl Buffers { |
| pub fn with_src<'a, 'src>(&'a mut self, src: &'src [u8]) -> BuffersWithSource<'src, 'a> { |
| self.clear(); |
| BuffersWithSource { |
| ro_src: Some(src), |
| src: &mut self.src, |
| dest: &mut self.dest, |
| } |
| } |
| pub fn clear(&mut self) { |
| self.src.clear(); |
| self.dest.clear(); |
| } |
| |
| pub fn swap(&mut self) { |
| std::mem::swap(&mut self.src, &mut self.dest); |
| } |
| } |
| |
| impl BuffersWithSource<'_, '_> { |
| /// Must be called after every change (i.e. when it's known that `dest` was written. |
| pub fn swap(&mut self) { |
| self.ro_src.take(); |
| std::mem::swap(&mut self.src, &mut self.dest); |
| self.dest.clear(); |
| } |
| pub fn src_and_dest(&mut self) -> (&[u8], &mut Vec<u8>) { |
| match self.ro_src { |
| Some(src) => (src, &mut self.dest), |
| None => (self.src, &mut self.dest), |
| } |
| } |
| } |
| |
| pub(crate) struct Configuration<'a> { |
| pub(crate) driver: Option<&'a Driver>, |
| /// What attributes say about CRLF handling. |
| pub(crate) _attr_digest: Option<eol::AttributesDigest>, |
| /// The final digest that includes configuration values |
| pub(crate) digest: eol::AttributesDigest, |
| pub(crate) encoding: Option<&'static encoding_rs::Encoding>, |
| /// Whether or not to apply the `ident` filter |
| pub(crate) apply_ident_filter: bool, |
| } |
| |
| impl<'driver> Configuration<'driver> { |
| pub(crate) fn at_path( |
| rela_path: &BStr, |
| drivers: &'driver [Driver], |
| attrs: &mut gix_attributes::search::Outcome, |
| attributes: &mut dyn FnMut(&BStr, &mut gix_attributes::search::Outcome), |
| config: eol::Configuration, |
| ) -> Result<Configuration<'driver>, configuration::Error> { |
| fn extract_driver<'a>(drivers: &'a [Driver], attr: &gix_attributes::search::Match<'_>) -> Option<&'a Driver> { |
| if let StateRef::Value(name) = attr.assignment.state { |
| drivers.iter().find(|d| d.name == name.as_bstr()) |
| } else { |
| None |
| } |
| } |
| |
| fn extract_encoding( |
| attr: &gix_attributes::search::Match<'_>, |
| ) -> Result<Option<&'static encoding_rs::Encoding>, configuration::Error> { |
| match attr.assignment.state { |
| StateRef::Set | StateRef::Unset => Err(configuration::Error::InvalidEncoding), |
| StateRef::Value(name) => encoding_rs::Encoding::for_label(name.as_bstr()) |
| .ok_or(configuration::Error::UnknownEncoding { |
| name: name.as_bstr().to_owned(), |
| }) |
| .map(|encoding| { |
| // The working-tree-encoding is the encoding we have to expect in the working tree. |
| // If the specified one is the default encoding, there is nothing to do. |
| if encoding == encoding_rs::UTF_8 { |
| None |
| } else { |
| Some(encoding) |
| } |
| }), |
| StateRef::Unspecified => Ok(None), |
| } |
| } |
| |
| /// This is based on `git_path_check_crlf` in the git codebase. |
| fn extract_crlf(attr: &gix_attributes::search::Match<'_>) -> Option<eol::AttributesDigest> { |
| match attr.assignment.state { |
| StateRef::Unspecified => None, |
| StateRef::Set => Some(eol::AttributesDigest::Text), |
| StateRef::Unset => Some(eol::AttributesDigest::Binary), |
| StateRef::Value(v) => { |
| if v.as_bstr() == "input" { |
| Some(eol::AttributesDigest::TextInput) |
| } else if v.as_bstr() == "auto" { |
| Some(eol::AttributesDigest::TextAuto) |
| } else { |
| None |
| } |
| } |
| } |
| } |
| |
| fn extract_eol(attr: &gix_attributes::search::Match<'_>) -> Option<eol::Mode> { |
| match attr.assignment.state { |
| StateRef::Unspecified | StateRef::Unset | StateRef::Set => None, |
| StateRef::Value(v) => { |
| if v.as_bstr() == "lf" { |
| Some(eol::Mode::Lf) |
| } else if v.as_bstr() == "crlf" { |
| Some(eol::Mode::CrLf) |
| } else { |
| None |
| } |
| } |
| } |
| } |
| |
| attributes(rela_path, attrs); |
| let attrs: SmallVec<[_; crate::pipeline::ATTRS.len()]> = attrs.iter_selected().collect(); |
| let apply_ident_filter = attrs[1].assignment.state.is_set(); |
| let driver = extract_driver(drivers, &attrs[2]); |
| let encoding = extract_encoding(&attrs[5])?; |
| |
| let mut digest = extract_crlf(&attrs[4]); |
| if digest.is_none() { |
| digest = extract_crlf(&attrs[0]); |
| } |
| |
| if digest != Some(AttributesDigest::Binary) { |
| let eol = extract_eol(&attrs[3]); |
| digest = match digest { |
| Some(AttributesDigest::TextAuto) if eol == Some(eol::Mode::Lf) => Some(AttributesDigest::TextAutoInput), |
| Some(AttributesDigest::TextAuto) if eol == Some(eol::Mode::CrLf) => { |
| Some(AttributesDigest::TextAutoCrlf) |
| } |
| _ => match eol { |
| Some(eol::Mode::CrLf) => Some(AttributesDigest::TextCrlf), |
| Some(eol::Mode::Lf) => Some(AttributesDigest::TextInput), |
| _ => digest, |
| }, |
| }; |
| } |
| |
| let attr_digest = digest; |
| digest = match digest { |
| None => Some(config.auto_crlf.into()), |
| Some(AttributesDigest::Text) => Some(config.to_eol().into()), |
| _ => digest, |
| }; |
| |
| Ok(Configuration { |
| driver, |
| _attr_digest: attr_digest, |
| digest: digest.expect("always set by now"), |
| encoding, |
| apply_ident_filter, |
| }) |
| } |
| } |
| |
| impl Context { |
| pub(crate) fn with_path<'a>(&self, rela_path: &'a BStr) -> driver::apply::Context<'a, '_> { |
| driver::apply::Context { |
| rela_path, |
| ref_name: self.ref_name.as_ref().map(AsRef::as_ref), |
| treeish: self.treeish, |
| blob: self.blob, |
| } |
| } |
| } |
| |
| impl CrlfRoundTripCheck { |
| pub(crate) fn to_eol_roundtrip_check(self, rela_path: &Path) -> Option<eol::convert_to_git::RoundTripCheck<'_>> { |
| match self { |
| CrlfRoundTripCheck::Fail => Some(eol::convert_to_git::RoundTripCheck::Fail { rela_path }), |
| CrlfRoundTripCheck::Warn => Some(eol::convert_to_git::RoundTripCheck::Warn { rela_path }), |
| CrlfRoundTripCheck::Skip => None, |
| } |
| } |
| } |