gix_submodule/
config.rs

1use bstr::{BStr, BString, ByteSlice};
2
3/// Determine how the submodule participates in `git status` queries. This setting also affects `git diff`.
4#[derive(Default, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
5pub enum Ignore {
6    /// Submodule changes won't be considered at all, which is the fastest option.
7    All,
8    /// Ignore any changes to the submodule working tree, only show committed differences between the `HEAD` of the submodule
9    /// compared to the recorded commit in the superproject.
10    Dirty,
11    /// Only ignore untracked files in the submodule, but show modifications to the submodule working tree as well as differences
12    /// between the recorded commit in the superproject and the checked-out commit in the submodule.
13    Untracked,
14    /// No modifications to the submodule are ignored, which shows untracked files, modified files in the submodule worktree as well as
15    /// differences between the recorded commit in the superproject and the checked-out commit in the submodule.
16    #[default]
17    None,
18}
19
20impl TryFrom<&BStr> for Ignore {
21    type Error = ();
22
23    fn try_from(value: &BStr) -> Result<Self, Self::Error> {
24        Ok(match value.as_bytes() {
25            b"all" => Ignore::All,
26            b"dirty" => Ignore::Dirty,
27            b"untracked" => Ignore::Untracked,
28            b"none" => Ignore::None,
29            _ => return Err(()),
30        })
31    }
32}
33
34/// Determine how to recurse into this module from the superproject when fetching.
35///
36/// Generally, a fetch is only performed if the submodule commit referenced by the superproject isn't already
37/// present in the submodule repository.
38///
39/// Note that when unspecified, the `fetch.recurseSubmodules` configuration variable should be used instead.
40#[derive(Default, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
41pub enum FetchRecurse {
42    /// Fetch only changed submodules.
43    #[default]
44    OnDemand,
45    /// Fetch all populated submodules, changed or not.
46    ///
47    /// This skips the work needed to determine whether a submodule has changed in the first place, but may work
48    /// more as some fetches might not be necessary.
49    Always,
50    /// Submodules are never fetched.
51    Never,
52}
53
54impl FetchRecurse {
55    /// Check if `boolean` is set and translate it the respective variant, or check the underlying string
56    /// value for non-boolean options.
57    /// On error, it returns the obtained string value which would be the invalid value.
58    pub fn new(boolean: Result<bool, gix_config::value::Error>) -> Result<Self, BString> {
59        Ok(match boolean {
60            Ok(value) => {
61                if value {
62                    FetchRecurse::Always
63                } else {
64                    FetchRecurse::Never
65                }
66            }
67            Err(err) => {
68                if err.input != "on-demand" {
69                    return Err(err.input);
70                }
71                FetchRecurse::OnDemand
72            }
73        })
74    }
75}
76
77/// Describes the branch that should be tracked on the remote.
78#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
79pub enum Branch {
80    /// The name of the remote branch should be the same as the one currently checked out in the superproject.
81    CurrentInSuperproject,
82    /// The validated remote-only branch that could be used for fetching.
83    Name(BString),
84}
85
86impl Default for Branch {
87    fn default() -> Self {
88        Branch::Name("HEAD".into())
89    }
90}
91
92impl TryFrom<&BStr> for Branch {
93    type Error = gix_refspec::parse::Error;
94
95    fn try_from(value: &BStr) -> Result<Self, Self::Error> {
96        if value == "." {
97            return Ok(Branch::CurrentInSuperproject);
98        }
99
100        gix_refspec::parse(value, gix_refspec::parse::Operation::Fetch)
101            .map(|spec| Branch::Name(spec.source().expect("no object").to_owned()))
102    }
103}
104
105/// Determine how `git submodule update` should deal with this submodule to bring it up-to-date with the
106/// super-project's expectations.
107#[derive(Default, Debug, Clone, Hash, PartialOrd, PartialEq, Ord, Eq)]
108pub enum Update {
109    /// The commit recorded in the superproject should be checked out on a detached `HEAD`.
110    #[default]
111    Checkout,
112    /// The current branch in the submodule will be rebased onto the commit recorded in the superproject.
113    Rebase,
114    /// The commit recorded in the superproject will merged into the current branch of the submodule.
115    Merge,
116    /// A custom command to be called like `<command> hash-of-submodule-commit` that is to be executed to
117    /// perform the submodule update.
118    ///
119    /// Note that this variant is only allowed if the value is coming from an override. Thus it's not allowed to distribute
120    /// arbitrary commands via `.gitmodules` for security reasons.
121    Command(BString),
122    /// The submodule update is not performed at all.
123    None,
124}
125
126impl TryFrom<&BStr> for Update {
127    type Error = ();
128
129    fn try_from(value: &BStr) -> Result<Self, Self::Error> {
130        Ok(match value.as_bstr().as_bytes() {
131            b"checkout" => Update::Checkout,
132            b"rebase" => Update::Rebase,
133            b"merge" => Update::Merge,
134            b"none" => Update::None,
135            command if command.first() == Some(&b'!') => Update::Command(command[1..].to_owned().into()),
136            _ => return Err(()),
137        })
138    }
139}
140
141/// The error returned by [File::fetch_recurse()](crate::File::fetch_recurse) and [File::ignore()](crate::File::ignore).
142#[derive(Debug, thiserror::Error)]
143#[allow(missing_docs)]
144#[error("The '{field}' field of submodule '{submodule}' was invalid: '{actual}'")]
145pub struct Error {
146    pub field: &'static str,
147    pub submodule: BString,
148    pub actual: BString,
149}
150
151///
152pub mod branch {
153    use bstr::BString;
154
155    /// The error returned by [File::branch()](crate::File::branch).
156    #[derive(Debug, thiserror::Error)]
157    #[allow(missing_docs)]
158    #[error("The value '{actual}' of the 'branch' field of submodule '{submodule}' couldn't be turned into a valid fetch refspec")]
159    pub struct Error {
160        pub submodule: BString,
161        pub actual: BString,
162        pub source: gix_refspec::parse::Error,
163    }
164}
165
166///
167pub mod update {
168    use bstr::BString;
169
170    /// The error returned by [File::update()](crate::File::update).
171    #[derive(Debug, thiserror::Error)]
172    #[allow(missing_docs)]
173    pub enum Error {
174        #[error("The 'update' field of submodule '{submodule}' tried to set command '{actual}' to be shared")]
175        CommandForbiddenInModulesConfiguration { submodule: BString, actual: BString },
176        #[error("The 'update' field of submodule '{submodule}' was invalid: '{actual}'")]
177        Invalid { submodule: BString, actual: BString },
178    }
179}
180
181///
182pub mod url {
183    use bstr::BString;
184
185    /// The error returned by [File::url()](crate::File::url).
186    #[derive(Debug, thiserror::Error)]
187    #[allow(missing_docs)]
188    pub enum Error {
189        #[error("The url of submodule '{submodule}' could not be parsed")]
190        Parse {
191            submodule: BString,
192            source: gix_url::parse::Error,
193        },
194        #[error("The submodule '{submodule}' was missing its 'url' field or it was empty")]
195        Missing { submodule: BString },
196    }
197}
198
199///
200pub mod path {
201    use bstr::BString;
202
203    /// The error returned by [File::path()](crate::File::path).
204    #[derive(Debug, thiserror::Error)]
205    #[allow(missing_docs)]
206    pub enum Error {
207        #[error("The path '{actual}' of submodule '{submodule}' needs to be relative")]
208        Absolute { actual: BString, submodule: BString },
209        #[error("The submodule '{submodule}' was missing its 'path' field or it was empty")]
210        Missing { submodule: BString },
211        #[error("The path '{actual}' would lead outside of the repository worktree")]
212        OutsideOfWorktree { actual: BString, submodule: BString },
213    }
214}