• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MitMaro / git-interactive-rebase-tool / 15656777377

14 Jun 2025 10:36PM UTC coverage: 97.566% (-0.02%) from 97.589%
15656777377

Pull #978

github

web-flow
Merge c1ecfa05d into 4d16b296a
Pull Request #978: refactor: remove uses of `Option` in `new_with_config()`

148 of 152 new or added lines in 14 files covered. (97.37%)

1 existing line in 1 file now uncovered.

4930 of 5053 relevant lines covered (97.57%)

2.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.1
/src/config.rs
1
//! Git Interactive Rebase Tool - Configuration Module
2
//!
3
//! # Description
4
//! This module is used to handle the loading of configuration from the Git config system.
5
//!
6
//! ## Test Utilities
7
//! To facilitate testing the usages of this crate, a set of testing utilities are provided. Since
8
//! these utilities are not tested, and often are optimized for developer experience than
9
//! performance should only be used in test code.
10
mod color;
11
mod config_loader;
12
mod diff_ignore_whitespace_setting;
13
mod diff_show_whitespace_setting;
14
mod errors;
15
mod git_config;
16
mod key_bindings;
17
mod theme;
18
mod utils;
19

20
use self::utils::{get_bool, get_diff_ignore_whitespace, get_diff_show_whitespace, get_string, get_unsigned_integer};
21
pub(crate) use self::{
22
        color::Color,
23
        config_loader::ConfigLoader,
24
        diff_ignore_whitespace_setting::DiffIgnoreWhitespaceSetting,
25
        diff_show_whitespace_setting::DiffShowWhitespaceSetting,
26
        git_config::GitConfig,
27
        key_bindings::KeyBindings,
28
        theme::Theme,
29
};
30
use crate::config::{
31
        errors::{ConfigError, ConfigErrorCause, InvalidColorError},
32
        utils::get_optional_string,
33
};
34

35
const DEFAULT_SPACE_SYMBOL: &str = "\u{b7}"; // ·
36
const DEFAULT_TAB_SYMBOL: &str = "\u{2192}"; // →
37

38
/// Represents the configuration options.
39
#[derive(Clone, Debug)]
40
#[non_exhaustive]
41
pub(crate) struct Config {
42
        /// If to select the next line in the list after performing an action.
43
        pub(crate) auto_select_next: bool,
44
        /// How to handle whitespace when calculating diffs.
45
        pub(crate) diff_ignore_whitespace: DiffIgnoreWhitespaceSetting,
46
        /// If to ignore blank lines when calculating diffs.
47
        pub(crate) diff_ignore_blank_lines: bool,
48
        /// How to show whitespace in diffs.
49
        pub(crate) diff_show_whitespace: DiffShowWhitespaceSetting,
50
        /// The symbol used to replace space characters.
51
        pub(crate) diff_space_symbol: String,
52
        /// The symbol used to replace tab characters.
53
        pub(crate) diff_tab_symbol: String,
54
        /// The display width of the tab character.
55
        pub(crate) diff_tab_width: u32,
56
        /// If set, automatically add an exec line with the command after every modified line
57
        pub(crate) post_modified_line_exec_command: Option<String>,
58
        /// The maximum number of undo steps.
59
        pub(crate) undo_limit: u32,
60
        /// Configuration options loaded directly from Git.
61
        pub(crate) git: GitConfig,
62
        /// Key binding configuration.
63
        pub(crate) key_bindings: KeyBindings,
64
        /// Theme configuration.
65
        pub(crate) theme: Theme,
66
}
67

68
impl Config {
×
69
        pub(crate) fn new_with_config(git_config: &crate::git::Config) -> Result<Self, ConfigError> {
1✔
70
                Ok(Self {
1✔
71
                        auto_select_next: get_bool(git_config, "interactive-rebase-tool.autoSelectNext", false)?,
1✔
72
                        diff_ignore_whitespace: get_diff_ignore_whitespace(
2✔
73
                                git_config,
×
74
                                "interactive-rebase-tool.diffIgnoreWhitespace",
75
                        )?,
76
                        diff_ignore_blank_lines: get_bool(git_config, "interactive-rebase-tool.diffIgnoreBlankLines", false)?,
3✔
77
                        diff_show_whitespace: get_diff_show_whitespace(git_config, "interactive-rebase-tool.diffShowWhitespace")?,
2✔
78
                        diff_space_symbol: get_string(
4✔
79
                                git_config,
80
                                "interactive-rebase-tool.diffSpaceSymbol",
1✔
81
                                DEFAULT_SPACE_SYMBOL,
82
                        )?,
83
                        diff_tab_symbol: get_string(git_config, "interactive-rebase-tool.diffTabSymbol", DEFAULT_TAB_SYMBOL)?,
4✔
84
                        diff_tab_width: get_unsigned_integer(git_config, "interactive-rebase-tool.diffTabWidth", 4)?,
2✔
85
                        undo_limit: get_unsigned_integer(git_config, "interactive-rebase-tool.undoLimit", 5000)?,
5✔
86
                        post_modified_line_exec_command: get_optional_string(
3✔
87
                                git_config,
88
                                "interactive-rebase-tool.postModifiedLineExecCommand",
89
                        )?,
90
                        git: GitConfig::new_with_config(git_config)?,
2✔
91
                        key_bindings: KeyBindings::new_with_config(git_config)?,
2✔
92
                        theme: Theme::new_with_config(git_config)?,
2✔
93
                })
94
        }
95
}
96

NEW
97
impl Default for Config {
×
98
        fn default() -> Self {
1✔
99
                Self {
100
                        auto_select_next: false,
101
                        diff_ignore_whitespace: DiffIgnoreWhitespaceSetting::None,
102
                        diff_ignore_blank_lines: false,
103
                        diff_show_whitespace: DiffShowWhitespaceSetting::Both,
104
                        diff_space_symbol: DEFAULT_SPACE_SYMBOL.to_owned(),
1✔
105
                        diff_tab_symbol: DEFAULT_TAB_SYMBOL.to_owned(),
1✔
106
                        diff_tab_width: 4,
107
                        undo_limit: 5000,
108
                        post_modified_line_exec_command: None,
109
                        git: GitConfig::default(),
1✔
110
                        key_bindings: KeyBindings::default(),
1✔
111
                        theme: Theme::default(),
1✔
112
                }
113
        }
114
}
115

UNCOV
116
impl TryFrom<&ConfigLoader> for Config {
×
117
        type Error = ConfigError;
118

119
        /// Creates a new Config instance loading the Git Config.
120
        ///
121
        /// # Errors
122
        ///
123
        /// Will return an `Err` if there is a problem loading the configuration.
124
        fn try_from(config_loader: &ConfigLoader) -> Result<Self, Self::Error> {
1✔
125
                let config = config_loader
1✔
126
                        .load_config()
127
                        .map_err(|e| ConfigError::new_read_error("", ConfigErrorCause::GitError(e)))?;
1✔
128
                Self::new_with_config(&config)
1✔
129
        }
130
}
131

132
impl TryFrom<&crate::git::Config> for Config {
133
        type Error = ConfigError;
134

135
        fn try_from(config: &crate::git::Config) -> Result<Self, Self::Error> {
1✔
136
                Self::new_with_config(config)
2✔
137
        }
138
}
139

140
#[cfg(test)]
141
mod tests {
142
        use std::fmt::Debug;
143

144
        use claims::{assert_err_eq, assert_ok};
145
        use rstest::rstest;
146

147
        use super::*;
148
        use crate::test_helpers::{invalid_utf, with_git_config, with_temp_bare_repository};
149

150
        #[test]
151
        fn try_from_config_loader() {
152
                with_temp_bare_repository(|repository| {
153
                        let loader = ConfigLoader::from(repository);
154
                        assert_ok!(Config::try_from(&loader));
155
                });
156
        }
157

158
        #[test]
159
        fn try_from_git_config() {
160
                with_git_config(&[], |git_config| {
161
                        assert_ok!(Config::try_from(&git_config));
162
                });
163
        }
164

165
        #[test]
166
        fn try_from_git_config_error() {
167
                with_git_config(
168
                        &["[interactive-rebase-tool]", "autoSelectNext = invalid"],
169
                        |git_config| {
170
                                _ = Config::try_from(&git_config).unwrap_err();
171
                        },
172
                );
173
        }
174

175
        #[rstest]
176
        #[case::auto_select_next_default("autoSelectNext", "", false, |config: Config| config.auto_select_next)]
177
        #[case::auto_select_next_false("autoSelectNext", "false", false, |config: Config| config.auto_select_next)]
178
        #[case::auto_select_next_true("autoSelectNext", "true", true, |config: Config| config.auto_select_next)]
179
        #[case::diff_ignore_whitespace_default(
180
                "diffIgnoreWhitespace",
181
                "",
182
                DiffIgnoreWhitespaceSetting::None,
183
                |config: Config| config.diff_ignore_whitespace)
184
        ]
185
        #[case::diff_ignore_whitespace_true(
186
                "diffIgnoreWhitespace",
187
                "true",
188
                DiffIgnoreWhitespaceSetting::All,
189
                |config: Config| config.diff_ignore_whitespace)
190
        ]
191
        #[case::diff_ignore_whitespace_on(
192
                "diffIgnoreWhitespace",
193
                "on",
194
                DiffIgnoreWhitespaceSetting::All,
195
                |config: Config| config.diff_ignore_whitespace)
196
        ]
197
        #[case::diff_ignore_whitespace_all(
198
                "diffIgnoreWhitespace",
199
                "all",
200
                DiffIgnoreWhitespaceSetting::All,
201
                |config: Config| config.diff_ignore_whitespace)
202
        ]
203
        #[case::diff_ignore_whitespace_change(
204
                "diffIgnoreWhitespace",
205
                "change",
206
                DiffIgnoreWhitespaceSetting::Change,
207
                |config: Config| config.diff_ignore_whitespace)
208
        ]
209
        #[case::diff_ignore_whitespace_false(
210
                "diffIgnoreWhitespace",
211
                "false",
212
                DiffIgnoreWhitespaceSetting::None,
213
                |config: Config| config.diff_ignore_whitespace)
214
        ]
215
        #[case::diff_ignore_whitespace_off(
216
                "diffIgnoreWhitespace",
217
                "off",
218
                DiffIgnoreWhitespaceSetting::None,
219
                |config: Config| config.diff_ignore_whitespace)
220
        ]
221
        #[case::diff_ignore_whitespace_none(
222
                "diffIgnoreWhitespace",
223
                "none",
224
                DiffIgnoreWhitespaceSetting::None,
225
                |config: Config| config.diff_ignore_whitespace)
226
        ]
227
        #[case::diff_ignore_whitespace_mixed_case(
228
                "diffIgnoreWhitespace",
229
                "ChAnGe",
230
                DiffIgnoreWhitespaceSetting::Change,
231
                |config: Config| config.diff_ignore_whitespace)
232
        ]
233
        #[case::diff_ignore_blank_lines_default(
234
                "diffIgnoreBlankLines",
235
                "",
236
                false,
237
                |config: Config| config.diff_ignore_blank_lines
238
        )]
239
        #[case::diff_ignore_blank_lines_false(
240
                "diffIgnoreBlankLines",
241
                "false",
242
                false,
243
                |config: Config| config.diff_ignore_blank_lines
244
        )]
245
        #[case::diff_ignore_blank_lines_true(
246
                "diffIgnoreBlankLines",
247
                "true",
248
                true,
249
                |config: Config| config.diff_ignore_blank_lines
250
        )]
251
        #[case::diff_show_whitespace_default(
252
                "diffShowWhitespace",
253
                "",
254
                DiffShowWhitespaceSetting::Both,
255
                |config: Config| config.diff_show_whitespace)
256
        ]
257
        #[case::diff_show_whitespace_true(
258
                "diffShowWhitespace",
259
                "true",
260
                DiffShowWhitespaceSetting::Both,
261
                |config: Config| config.diff_show_whitespace)
262
        ]
263
        #[case::diff_show_whitespace_on(
264
                "diffShowWhitespace",
265
                "on",
266
                DiffShowWhitespaceSetting::Both,
267
                |config: Config| config.diff_show_whitespace)
268
        ]
269
        #[case::diff_show_whitespace_both(
270
                "diffShowWhitespace",
271
                "both",
272
                DiffShowWhitespaceSetting::Both,
273
                |config: Config| config.diff_show_whitespace)
274
        ]
275
        #[case::diff_show_whitespace_trailing(
276
                "diffShowWhitespace",
277
                "trailing",
278
                DiffShowWhitespaceSetting::Trailing,
279
                |config: Config| config.diff_show_whitespace)
280
        ]
281
        #[case::diff_show_whitespace_leading(
282
                "diffShowWhitespace",
283
                "leading",
284
                DiffShowWhitespaceSetting::Leading,
285
                |config: Config| config.diff_show_whitespace)
286
        ]
287
        #[case::diff_show_whitespace_false(
288
                "diffShowWhitespace",
289
                "false",
290
                DiffShowWhitespaceSetting::None,
291
                |config: Config| config.diff_show_whitespace)
292
        ]
293
        #[case::diff_show_whitespace_off(
294
                "diffShowWhitespace",
295
                "off",
296
                DiffShowWhitespaceSetting::None,
297
                |config: Config| config.diff_show_whitespace)
298
        ]
299
        #[case::diff_show_whitespace_none(
300
                "diffShowWhitespace",
301
                "none",
302
                DiffShowWhitespaceSetting::None,
303
                |config: Config| config.diff_show_whitespace)
304
        ]
305
        #[case::diff_show_whitespace_mixed_case(
306
                "diffShowWhitespace",
307
                "tRaIlInG",
308
                DiffShowWhitespaceSetting::Trailing,
309
                |config: Config| config.diff_show_whitespace)
310
        ]
311
        #[case::diff_tab_width_default("diffTabWidth", "", 4, |config: Config| config.diff_tab_width)]
312
        #[case::diff_tab_width("diffTabWidth", "42", 42, |config: Config| config.diff_tab_width)]
313
        #[case::diff_tab_symbol_default("diffTabSymbol", "", String::from("→"), |config: Config| config.diff_tab_symbol)]
314
        #[case::diff_tab_symbol("diffTabSymbol", "|", String::from("|"), |config: Config| config.diff_tab_symbol)]
315
        #[case::diff_space_symbol_default(
316
                "diffSpaceSymbol",
317
                "",
318
                String::from("·"),
319
                |config: Config| config.diff_space_symbol)
320
        ]
321
        #[case::diff_space_symbol("diffSpaceSymbol", "-", String::from("-"), |config: Config| config.diff_space_symbol)]
322
        #[case::undo_limit_default("undoLimit", "", 5000, |config: Config| config.undo_limit)]
323
        #[case::undo_limit("undoLimit", "42", 42, |config: Config| config.undo_limit)]
324
        #[case::post_modified_line_exec_command(
325
                "postModifiedLineExecCommand",
326
                "command",
327
                Some(String::from("command")),
328
                |config: Config| config.post_modified_line_exec_command
329
        )]
330
        #[case::post_modified_line_exec_command_default(
331
                "postModifiedLineExecCommand",
332
                "",
333
                None,
334
                |config: Config| config.post_modified_line_exec_command
335
        )]
336
        pub(crate) fn config_test<F, T>(
337
                #[case] config_name: &str,
338
                #[case] config_value: &str,
339
                #[case] expected: T,
340
                #[case] access: F,
341
        ) where
342
                F: Fn(Config) -> T + 'static,
343
                T: Debug + PartialEq,
344
        {
345
                let value = format!("{config_name} = \"{config_value}\"");
346
                let lines = if config_value.is_empty() {
347
                        vec![]
348
                }
349
                else {
350
                        vec!["[interactive-rebase-tool]", value.as_str()]
351
                };
352
                with_git_config(&lines, |config| {
353
                        let config = Config::new_with_config(&config).unwrap();
354
                        assert_eq!(access(config), expected);
355
                });
356
        }
357

358
        #[rstest]
359
        #[case::auto_select_next("autoSelectNext", "invalid", ConfigErrorCause::InvalidBoolean)]
360
        #[case::diff_ignore_whitespace("diffIgnoreWhitespace", "invalid", ConfigErrorCause::InvalidDiffIgnoreWhitespace)]
361
        #[case::diff_ignore_blank_lines("diffIgnoreBlankLines", "invalid", ConfigErrorCause::InvalidBoolean)]
362
        #[case::diff_show_whitespace("diffShowWhitespace", "invalid", ConfigErrorCause::InvalidShowWhitespace)]
363
        #[case::diff_tab_width_non_integer("diffTabWidth", "invalid", ConfigErrorCause::InvalidUnsignedInteger)]
364
        #[case::diff_tab_width_non_poitive_integer("diffTabWidth", "-100", ConfigErrorCause::InvalidUnsignedInteger)]
365
        #[case::undo_limit_non_integer("undoLimit", "invalid", ConfigErrorCause::InvalidUnsignedInteger)]
366
        #[case::undo_limit_non_positive_integer("undoLimit", "-100", ConfigErrorCause::InvalidUnsignedInteger)]
367
        fn value_parsing_invalid(#[case] config_name: &str, #[case] config_value: &str, #[case] cause: ConfigErrorCause) {
368
                with_git_config(
369
                        &[
370
                                "[interactive-rebase-tool]",
371
                                format!("{config_name} = {config_value}").as_str(),
372
                        ],
373
                        |git_config| {
374
                                assert_err_eq!(
375
                                        Config::new_with_config(&git_config),
376
                                        ConfigError::new(
377
                                                format!("interactive-rebase-tool.{config_name}").as_str(),
378
                                                config_value,
379
                                                cause
380
                                        )
381
                                );
382
                        },
383
                );
384
        }
385

386
        #[rstest]
387
        #[case::diff_tab_symbol("diffIgnoreWhitespace")]
388
        #[case::diff_show_whitespace("diffShowWhitespace")]
389
        #[case::diff_tab_symbol("diffTabSymbol")]
390
        #[case::diff_space_symbol("diffSpaceSymbol")]
391
        #[case::post_modified_line_exec_command("postModifiedLineExecCommand")]
392
        fn value_parsing_invalid_utf(#[case] config_name: &str) {
393
                with_git_config(
394
                        &[
395
                                "[interactive-rebase-tool]",
396
                                format!("{config_name} = {}", invalid_utf()).as_str(),
397
                        ],
398
                        |git_config| {
399
                                assert_err_eq!(
400
                                        Config::new_with_config(&git_config),
401
                                        ConfigError::new_read_error(
402
                                                format!("interactive-rebase-tool.{config_name}").as_str(),
403
                                                ConfigErrorCause::InvalidUtf
404
                                        )
405
                                );
406
                        },
407
                );
408
        }
409
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc