Skip to content

Commit 5115835

Browse files
committed
Move loading of Git config into config crate
1 parent f655684 commit 5115835

File tree

17 files changed

+331
-295
lines changed

17 files changed

+331
-295
lines changed

src/application.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ pub(crate) use crate::application::app_data::AppData;
99
use crate::{
1010
Args,
1111
Exit,
12-
config::Config,
12+
config::{Config, ConfigLoader},
1313
display::Display,
14-
git::Repository,
14+
git::{Repository, open_repository_from_env},
1515
help::build_help,
1616
input::{Event, EventHandler, EventReaderFn, KeyBindings, StandardEvent},
1717
module::{self, ExitStatus, ModuleHandler, State},
@@ -40,7 +40,8 @@ where ModuleProvider: module::ModuleProvider + Send + 'static
4040
{
4141
let filepath = Self::filepath_from_args(args)?;
4242
let repository = Self::open_repository()?;
43-
let config = Arc::new(Self::load_config(&repository)?);
43+
let config_loader = ConfigLoader::from(repository);
44+
let config = Self::load_config(&config_loader)?;
4445
let todo_file = Arc::new(Mutex::new(Self::load_todo_file(filepath.as_str(), &config)?));
4546

4647
let display = Display::new(tui, &config.theme);
@@ -72,8 +73,9 @@ where ModuleProvider: module::ModuleProvider + Send + 'static
7273
let search_state = search_threads.state();
7374
threads.push(Box::new(search_threads));
7475

76+
let keybindings = KeyBindings::new(&config.key_bindings);
7577
let app_data = AppData::new(
76-
Arc::clone(&config),
78+
config,
7779
State::WindowSizeError,
7880
Arc::clone(&todo_file),
7981
view_state.clone(),
@@ -82,8 +84,8 @@ where ModuleProvider: module::ModuleProvider + Send + 'static
8284
);
8385

8486
let module_handler = ModuleHandler::new(
85-
EventHandler::new(KeyBindings::new(&config.key_bindings)),
86-
ModuleProvider::new(repository, &app_data),
87+
EventHandler::new(keybindings),
88+
ModuleProvider::new(Repository::from(config_loader.eject_repository()), &app_data),
8789
);
8890
let process = Process::new(&app_data, initial_display_size, module_handler, thread_statuses.clone());
8991
let process_threads = process::Thread::new(process.clone());
@@ -135,17 +137,17 @@ where ModuleProvider: module::ModuleProvider + Send + 'static
135137
})
136138
}
137139

138-
fn open_repository() -> Result<Repository, Exit> {
139-
Repository::open_from_env().map_err(|err| {
140+
fn open_repository() -> Result<git2::Repository, Exit> {
141+
open_repository_from_env().map_err(|err| {
140142
Exit::new(
141143
ExitStatus::StateError,
142144
format!("Unable to load Git repository: {err}").as_str(),
143145
)
144146
})
145147
}
146148

147-
fn load_config(repo: &Repository) -> Result<Config, Exit> {
148-
Config::try_from(repo).map_err(|err| Exit::new(ExitStatus::ConfigError, format!("{err:#}").as_str()))
149+
fn load_config(config_loader: &ConfigLoader) -> Result<Config, Exit> {
150+
Config::try_from(config_loader).map_err(|err| Exit::new(ExitStatus::ConfigError, format!("{err:#}").as_str()))
149151
}
150152

151153
fn todo_file_options(config: &Config) -> TodoFileOptions {

src/application/app_data.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ pub(crate) struct AppData {
1616

1717
impl AppData {
1818
pub(crate) fn new(
19-
config: Arc<Config>,
19+
config: Config,
2020
active_module: module::State,
2121
todo_file: Arc<Mutex<TodoFile>>,
2222
view_state: view::State,
2323
input_state: input::State,
2424
search_state: search::State,
2525
) -> Self {
2626
Self {
27-
config,
27+
config: Arc::new(config),
2828
active_module: Arc::new(Mutex::new(active_module)),
2929
todo_file,
3030
view_state,

src/config.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! these utilities are not tested, and often are optimized for developer experience than
99
//! performance should only be used in test code.
1010
mod color;
11+
mod config_loader;
1112
mod diff_ignore_whitespace_setting;
1213
mod diff_show_whitespace_setting;
1314
mod errors;
@@ -19,18 +20,16 @@ mod utils;
1920
use self::utils::{get_bool, get_diff_ignore_whitespace, get_diff_show_whitespace, get_string, get_unsigned_integer};
2021
pub(crate) use self::{
2122
color::Color,
23+
config_loader::ConfigLoader,
2224
diff_ignore_whitespace_setting::DiffIgnoreWhitespaceSetting,
2325
diff_show_whitespace_setting::DiffShowWhitespaceSetting,
2426
git_config::GitConfig,
2527
key_bindings::KeyBindings,
2628
theme::Theme,
2729
};
28-
use crate::{
29-
config::{
30-
errors::{ConfigError, ConfigErrorCause, InvalidColorError},
31-
utils::get_optional_string,
32-
},
33-
git::Repository,
30+
use crate::config::{
31+
errors::{ConfigError, ConfigErrorCause, InvalidColorError},
32+
utils::get_optional_string,
3433
};
3534

3635
const DEFAULT_SPACE_SYMBOL: &str = "\u{b7}"; // ·
@@ -95,16 +94,16 @@ impl Config {
9594
}
9695
}
9796

98-
impl TryFrom<&Repository> for Config {
97+
impl TryFrom<&ConfigLoader> for Config {
9998
type Error = ConfigError;
10099

101100
/// Creates a new Config instance loading the Git Config using [`crate::git::Repository`].
102101
///
103102
/// # Errors
104103
///
105104
/// Will return an `Err` if there is a problem loading the configuration.
106-
fn try_from(repo: &Repository) -> Result<Self, Self::Error> {
107-
let config = repo
105+
fn try_from(config_loader: &ConfigLoader) -> Result<Self, Self::Error> {
106+
let config = config_loader
108107
.load_config()
109108
.map_err(|e| ConfigError::new_read_error("", ConfigErrorCause::GitError(e)))?;
110109
Self::new_with_config(Some(&config))
@@ -130,9 +129,10 @@ mod tests {
130129
use crate::test_helpers::{invalid_utf, with_git_config, with_temp_bare_repository};
131130

132131
#[test]
133-
fn try_from_repository() {
132+
fn try_from_config_loader() {
134133
with_temp_bare_repository(|repository| {
135-
assert_ok!(Config::try_from(&repository));
134+
let loader = ConfigLoader::from(repository);
135+
assert_ok!(Config::try_from(&loader));
136136
});
137137
}
138138

src/config/config_loader.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use std::fmt::{Debug, Formatter};
2+
3+
use git2::Repository;
4+
5+
use crate::git::{Config, GitError};
6+
7+
pub(crate) struct ConfigLoader {
8+
repository: Repository,
9+
}
10+
11+
impl ConfigLoader {
12+
/// Load the git configuration for the repository.
13+
///
14+
/// # Errors
15+
/// Will result in an error if the configuration is invalid.
16+
pub(crate) fn load_config(&self) -> Result<Config, GitError> {
17+
self.repository.config().map_err(|e| GitError::ConfigLoad { cause: e })
18+
}
19+
20+
pub(crate) fn eject_repository(self) -> Repository {
21+
self.repository
22+
}
23+
}
24+
25+
impl From<Repository> for ConfigLoader {
26+
fn from(repository: Repository) -> Self {
27+
Self { repository }
28+
}
29+
}
30+
31+
impl Debug for ConfigLoader {
32+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
33+
f.debug_struct("ConfigLoader")
34+
.field("[path]", &self.repository.path())
35+
.finish()
36+
}
37+
}
38+
39+
// Paths in Windows make these tests difficult, so disable
40+
#[cfg(all(unix, test))]
41+
mod unix_tests {
42+
use claims::assert_ok;
43+
44+
use crate::{
45+
config::ConfigLoader,
46+
test_helpers::{with_temp_bare_repository, with_temp_repository},
47+
};
48+
49+
#[test]
50+
fn load_config() {
51+
with_temp_bare_repository(|repository| {
52+
let config = ConfigLoader::from(repository);
53+
assert_ok!(config.load_config());
54+
});
55+
}
56+
57+
#[test]
58+
fn fmt() {
59+
with_temp_repository(|repository| {
60+
let path = repository.path().canonicalize().unwrap();
61+
let loader = ConfigLoader::from(repository);
62+
let formatted = format!("{loader:?}");
63+
assert_eq!(
64+
formatted,
65+
format!("ConfigLoader {{ [path]: \"{}/\" }}", path.to_str().unwrap())
66+
);
67+
});
68+
}
69+
}

src/git.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,56 @@ pub(crate) use self::{
4545
status::Status,
4646
user::User,
4747
};
48+
49+
/// Find and open an existing repository, respecting git environment variables. This will check
50+
/// for and use `$GIT_DIR`, and if unset will search for a repository starting in the current
51+
/// directory, walking to the root.
52+
///
53+
/// # Errors
54+
/// Will result in an error if the repository cannot be opened.
55+
pub(crate) fn open_repository_from_env() -> Result<git2::Repository, GitError> {
56+
git2::Repository::open_from_env().map_err(|e| {
57+
GitError::RepositoryLoad {
58+
kind: RepositoryLoadKind::Environment,
59+
cause: e,
60+
}
61+
})
62+
}
63+
64+
// Paths in Windows make these tests difficult, so disable
65+
#[cfg(all(unix, test))]
66+
mod tests {
67+
use claims::assert_ok;
68+
use git2::ErrorClass;
69+
70+
use super::*;
71+
use crate::test_helpers::with_git_directory;
72+
73+
#[test]
74+
fn open_repository_from_env_success() {
75+
with_git_directory("fixtures/simple", |_| {
76+
assert_ok!(open_repository_from_env());
77+
});
78+
}
79+
80+
#[test]
81+
fn open_repository_from_env_error() {
82+
with_git_directory("fixtures/does-not-exist", |path| {
83+
match open_repository_from_env() {
84+
Ok(_) => {
85+
panic!("open_repository_from_env should return error")
86+
},
87+
Err(err) => {
88+
assert_eq!(err, GitError::RepositoryLoad {
89+
kind: RepositoryLoadKind::Environment,
90+
cause: git2::Error::new(
91+
ErrorCode::NotFound,
92+
ErrorClass::Os,
93+
format!("failed to resolve path '{path}': No such file or directory")
94+
),
95+
});
96+
},
97+
}
98+
});
99+
}
100+
}

src/git/commit.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -177,27 +177,26 @@ mod tests {
177177
#[test]
178178
fn new_authored_date_same_committed_date() {
179179
with_temp_repository(|repository| {
180-
create_commit(
181-
&repository,
182-
Some(CreateCommitOptions::new().author_time(JAN_2021_EPOCH)),
183-
);
184-
let commit = repository.find_commit("refs/heads/main").unwrap();
180+
let repo = crate::git::Repository::from(repository);
181+
create_commit(&repo, Some(CreateCommitOptions::new().author_time(JAN_2021_EPOCH)));
182+
let commit = repo.find_commit("refs/heads/main").unwrap();
185183
assert_none!(commit.authored_date());
186184
});
187185
}
188186

189187
#[test]
190188
fn new_authored_date_different_than_committed() {
191189
with_temp_repository(|repository| {
190+
let repo = crate::git::Repository::from(repository);
192191
create_commit(
193-
&repository,
192+
&repo,
194193
Some(
195194
CreateCommitOptions::new()
196195
.commit_time(JAN_2021_EPOCH)
197196
.author_time(JAN_2021_EPOCH + 1),
198197
),
199198
);
200-
let commit = repository.find_commit("refs/heads/main").unwrap();
199+
let commit = repo.find_commit("refs/heads/main").unwrap();
201200
assert_some_eq!(
202201
commit.authored_date(),
203202
&DateTime::parse_from_rfc3339("2021-01-01T00:00:01Z").unwrap()
@@ -208,8 +207,9 @@ mod tests {
208207
#[test]
209208
fn new_committer_different_than_author() {
210209
with_temp_repository(|repository| {
211-
create_commit(&repository, Some(CreateCommitOptions::new().committer("Committer")));
212-
let commit = repository.find_commit("refs/heads/main").unwrap();
210+
let repo = crate::git::Repository::from(repository);
211+
create_commit(&repo, Some(CreateCommitOptions::new().committer("Committer")));
212+
let commit = repo.find_commit("refs/heads/main").unwrap();
213213
assert_some_eq!(
214214
commit.committer(),
215215
&User::new(Some("Committer"), Some("[email protected]"))
@@ -220,15 +220,16 @@ mod tests {
220220
#[test]
221221
fn new_committer_same_as_author() {
222222
with_temp_repository(|repository| {
223-
let commit = repository.find_commit("refs/heads/main").unwrap();
223+
let repo = crate::git::Repository::from(repository);
224+
let commit = repo.find_commit("refs/heads/main").unwrap();
224225
assert_none!(commit.committer());
225226
});
226227
}
227228

228229
#[test]
229230
fn try_from_success() {
230231
with_temp_repository(|repository| {
231-
let reference = repository.repository().find_reference("refs/heads/main").unwrap();
232+
let reference = repository.find_reference("refs/heads/main").unwrap();
232233
let commit = Commit::try_from(&reference).unwrap();
233234

234235
assert_eq!(commit.reference.unwrap().shortname(), "main");
@@ -238,11 +239,10 @@ mod tests {
238239
#[test]
239240
fn try_from_error() {
240241
with_temp_repository(|repository| {
241-
let repo = repository.repository();
242-
let blob = repo.blob(b"foo").unwrap();
243-
_ = repo.reference("refs/blob", blob, false, "blob").unwrap();
242+
let blob = repository.blob(b"foo").unwrap();
243+
_ = repository.reference("refs/blob", blob, false, "blob").unwrap();
244244

245-
let reference = repo.find_reference("refs/blob").unwrap();
245+
let reference = repository.find_reference("refs/blob").unwrap();
246246
assert_err_eq!(Commit::try_from(&reference), GitError::CommitLoad {
247247
cause: git2::Error::new(
248248
git2::ErrorCode::InvalidSpec,

0 commit comments

Comments
 (0)