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)
    }
}