1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
use std::collections::BTreeSet;
use crate::{
file::{init, init::Options, Metadata},
File,
};
/// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_no_includes()`].
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("The configuration file at \"{}\" could not be read", path.display())]
Io {
source: std::io::Error,
path: std::path::PathBuf,
},
#[error(transparent)]
Init(#[from] init::Error),
}
/// Instantiation from one or more paths
impl File<'static> {
/// Load the single file at `path` with `source` without following include directives.
///
/// Note that the path will be checked for ownership to derive trust.
pub fn from_path_no_includes(path: std::path::PathBuf, source: crate::Source) -> Result<Self, Error> {
let trust = match gix_sec::Trust::from_path_ownership(&path) {
Ok(t) => t,
Err(err) => return Err(Error::Io { source: err, path }),
};
let mut buf = Vec::new();
match std::io::copy(
&mut match std::fs::File::open(&path) {
Ok(f) => f,
Err(err) => return Err(Error::Io { source: err, path }),
},
&mut buf,
) {
Ok(_) => {}
Err(err) => return Err(Error::Io { source: err, path }),
}
Ok(File::from_bytes_owned(
&mut buf,
Metadata::from(source).at(path).with(trust),
Default::default(),
)?)
}
/// Constructs a `git-config` file from the provided metadata, which must include a path to read from or be ignored.
/// Returns `Ok(None)` if there was not a single input path provided, which is a possibility due to
/// [`Metadata::path`] being an `Option`.
/// If an input path doesn't exist, the entire operation will abort. See [`from_paths_metadata_buf()`][Self::from_paths_metadata_buf()]
/// for a more powerful version of this method.
pub fn from_paths_metadata(
path_meta: impl IntoIterator<Item = impl Into<Metadata>>,
options: Options<'_>,
) -> Result<Option<Self>, Error> {
let mut buf = Vec::with_capacity(512);
let err_on_nonexisting_paths = true;
Self::from_paths_metadata_buf(
&mut path_meta.into_iter().map(Into::into),
&mut buf,
err_on_nonexisting_paths,
options,
)
}
/// Like [`from_paths_metadata()`][Self::from_paths_metadata()], but will use `buf` to temporarily store the config file
/// contents for parsing instead of allocating an own buffer.
///
/// If `err_on_nonexisting_paths` is false, instead of aborting with error, we will continue to the next path instead.
pub fn from_paths_metadata_buf(
path_meta: &mut dyn Iterator<Item = Metadata>,
buf: &mut Vec<u8>,
err_on_non_existing_paths: bool,
options: Options<'_>,
) -> Result<Option<Self>, Error> {
let mut target = None;
let mut seen = BTreeSet::default();
for (path, mut meta) in path_meta.filter_map(|mut meta| meta.path.take().map(|p| (p, meta))) {
if !seen.insert(path.clone()) {
continue;
}
buf.clear();
match std::io::copy(
&mut match std::fs::File::open(&path) {
Ok(f) => f,
Err(err) if !err_on_non_existing_paths && err.kind() == std::io::ErrorKind::NotFound => continue,
Err(err) => {
let err = Error::Io { source: err, path };
if options.ignore_io_errors {
gix_features::trace::warn!("ignoring: {err:#?}");
continue;
} else {
return Err(err);
}
}
},
buf,
) {
Ok(_) => {}
Err(err) => {
if options.ignore_io_errors {
gix_features::trace::warn!(
"ignoring: {:#?}",
Error::Io {
source: err,
path: path.clone()
}
);
buf.clear();
} else {
return Err(Error::Io { source: err, path });
}
}
};
meta.path = Some(path);
let config = Self::from_bytes_owned(buf, meta, options)?;
match &mut target {
None => {
target = Some(config);
}
Some(target) => {
target.append(config);
}
}
}
Ok(target)
}
}