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