Skip to main content

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<Option<bool>, gix_config::value::Error>) -> Result<Option<Self>, BString> {
59        Ok(match boolean {
60            Ok(Some(value)) => Some(if value {
61                FetchRecurse::Always
62            } else {
63                FetchRecurse::Never
64            }),
65            Ok(None) => None,
66            Err(err) => {
67                if err.input != "on-demand" {
68                    return Err(err.input);
69                }
70                Some(FetchRecurse::OnDemand)
71            }
72        })
73    }
74}
75
76/// Describes the branch that should be tracked on the remote.
77#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
78pub enum Branch {
79    /// The name of the remote branch should be the same as the one currently checked out in the superproject.
80    CurrentInSuperproject,
81    /// The validated remote-only branch that could be used for fetching.
82    Name(BString),
83}
84
85impl Default for Branch {
86    fn default() -> Self {
87        Branch::Name("HEAD".into())
88    }
89}
90
91impl TryFrom<&BStr> for Branch {
92    type Error = gix_refspec::parse::Error;
93
94    fn try_from(value: &BStr) -> Result<Self, Self::Error> {
95        if value == "." {
96            return Ok(Branch::CurrentInSuperproject);
97        }
98
99        gix_refspec::parse(value, gix_refspec::parse::Operation::Fetch)
100            .map(|spec| Branch::Name(spec.source().expect("no object").to_owned()))
101    }
102}
103
104/// Determine how `git submodule update` should deal with this submodule to bring it up-to-date with the
105/// super-project's expectations.
106#[derive(Default, Debug, Clone, Hash, PartialOrd, PartialEq, Ord, Eq)]
107pub enum Update {
108    /// The commit recorded in the superproject should be checked out on a detached `HEAD`.
109    #[default]
110    Checkout,
111    /// The current branch in the submodule will be rebased onto the commit recorded in the superproject.
112    Rebase,
113    /// The commit recorded in the superproject will merged into the current branch of the submodule.
114    Merge,
115    /// A custom command to be called like `<command> hash-of-submodule-commit` that is to be executed to
116    /// perform the submodule update.
117    ///
118    /// Note that this variant is only allowed if the value is coming from an override. Thus it's not allowed to distribute
119    /// arbitrary commands via `.gitmodules` for security reasons.
120    Command(BString),
121    /// The submodule update is not performed at all.
122    None,
123}
124
125impl TryFrom<&BStr> for Update {
126    type Error = ();
127
128    fn try_from(value: &BStr) -> Result<Self, Self::Error> {
129        Ok(match value.as_bstr().as_bytes() {
130            b"checkout" => Update::Checkout,
131            b"rebase" => Update::Rebase,
132            b"merge" => Update::Merge,
133            b"none" => Update::None,
134            command if command.first() == Some(&b'!') => Update::Command(command[1..].to_owned().into()),
135            _ => return Err(()),
136        })
137    }
138}
139
140/// The error returned by [File::fetch_recurse()](crate::File::fetch_recurse) and [File::ignore()](crate::File::ignore).
141#[derive(Debug, thiserror::Error)]
142#[expect(missing_docs)]
143#[error("The '{field}' field of submodule '{submodule}' was invalid: '{actual}'")]
144pub struct Error {
145    pub field: &'static str,
146    pub submodule: BString,
147    pub actual: BString,
148}
149
150///
151pub mod branch {
152    use bstr::BString;
153
154    /// The error returned by [File::branch()](crate::File::branch).
155    #[derive(Debug, thiserror::Error)]
156    #[expect(missing_docs)]
157    #[error(
158        "The value '{actual}' of the 'branch' field of submodule '{submodule}' couldn't be turned into a valid fetch refspec"
159    )]
160    pub struct Error {
161        pub submodule: BString,
162        pub actual: BString,
163        pub source: gix_refspec::parse::Error,
164    }
165}
166
167///
168pub mod update {
169    use bstr::BString;
170
171    /// The error returned by [File::update()](crate::File::update).
172    #[derive(Debug, thiserror::Error)]
173    #[expect(missing_docs)]
174    pub enum Error {
175        #[error("The 'update' field of submodule '{submodule}' tried to set command '{actual}' to be shared")]
176        CommandForbiddenInModulesConfiguration { submodule: BString, actual: BString },
177        #[error("The 'update' field of submodule '{submodule}' was invalid: '{actual}'")]
178        Invalid { submodule: BString, actual: BString },
179    }
180}
181
182///
183pub mod url {
184    use bstr::BString;
185
186    /// The error returned by [File::url()](crate::File::url).
187    #[derive(Debug, thiserror::Error)]
188    #[expect(missing_docs)]
189    pub enum Error {
190        #[error("The url of submodule '{submodule}' could not be parsed")]
191        Parse {
192            submodule: BString,
193            source: gix_url::parse::Error,
194        },
195        #[error("The submodule '{submodule}' was missing its 'url' field or it was empty")]
196        Missing { submodule: BString },
197    }
198}
199
200///
201pub mod path {
202    use bstr::BString;
203
204    /// The error returned by [File::path()](crate::File::path).
205    #[derive(Debug, thiserror::Error)]
206    #[expect(missing_docs)]
207    pub enum Error {
208        #[error("The path '{actual}' of submodule '{submodule}' needs to be relative")]
209        Absolute { actual: BString, submodule: BString },
210        #[error("The submodule '{submodule}' was missing its 'path' field or it was empty")]
211        Missing { submodule: BString },
212        #[error("The path '{actual}' would lead outside of the repository worktree")]
213        OutsideOfWorktree { actual: BString, submodule: BString },
214    }
215}