From 29f1de741384783e4f432d9d06ba644fa324d83b Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Tue, 22 Jul 2025 01:53:36 +0900 Subject: [PATCH 1/8] feat: implement version control and change log for problem files using git2 --- Cargo.toml | 3 +- src/file_manager/git.rs | 375 ++++++++++++++++++++++++++++++++++++++++ src/file_manager/mod.rs | 1 + 3 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 src/file_manager/git.rs diff --git a/Cargo.toml b/Cargo.toml index 46e41a9..a4c09fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,12 @@ name = "coduck-backend" anyhow = "1.0" axum = { version = "0.8.4", features = ["json", "multipart"] } chrono = { version = "0.4.38", features = ["serde"] } -reqwest = { version = "0.12.19", features = ["json", "rustls-tls"] } +git2 = "0.20.2" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.133" tokio = { version = "1.45.1", features = ["full"] } uuid = { version = "1.17.0", features = ["v4"] } [dev-dependencies] +reqwest = { version = "0.12.19", features = ["json", "rustls-tls"] } rstest = "0.25.0" diff --git a/src/file_manager/git.rs b/src/file_manager/git.rs new file mode 100644 index 0000000..3a092f2 --- /dev/null +++ b/src/file_manager/git.rs @@ -0,0 +1,375 @@ +#![allow(dead_code)] + +use anyhow::{Context, Result}; +use git2::{DiffOptions, IndexAddOption, Repository, StatusOptions, Time}; +use std::path::PathBuf; +use tokio::fs; + +const UPLOAD_DIR: &str = "uploads"; + +#[derive(Debug)] +struct GitManager { + problem_id: u32, +} + +impl GitManager { + fn new(problem_id: u32) -> Self { + Self { problem_id } + } + + fn git_init(&self) -> Result<()> { + let path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); + Repository::init(&path) + .map(|_| ()) + .with_context(|| format!("Failed to init git repo at {:?}", path)) + } + + async fn create_problem(&self) -> Result<()> { + self.git_init()?; + self.create_default_directories().await?; + self.git_add_all()?; + Ok(()) + } + + fn git_add_all(&self) -> Result<()> { + let repo = self.get_repository()?; + let mut idx = repo.index()?; + idx.add_all(["."].iter(), IndexAddOption::DEFAULT, None)?; + idx.write()?; + Ok(()) + } + + fn git_commit(&self, message: String) -> Result { + self.git_add_all()?; + let repo = self.get_repository()?; + let mut idx = repo.index()?; + let tree_id = idx.write_tree()?; + let tree = repo.find_tree(tree_id)?; + let sig = repo.signature()?; + let parent_commits = match repo.head() { + Ok(head_ref) => { + let head = head_ref + .target() + .ok_or_else(|| anyhow::anyhow!("HEAD refers to non-HEAD"))?; + vec![repo.find_commit(head)?] + } + Err(_) => Vec::new(), + }; + + let parents: Vec<&git2::Commit> = parent_commits.iter().collect(); + let commit_oid = repo.commit(Some("HEAD"), &sig, &sig, &message, &tree, &parents)?; + Ok(commit_oid.to_string()) + } + + fn git_status(&self) -> Result> { + let repo = self.get_repository()?; + let mut status_opts = StatusOptions::new(); + status_opts + .include_untracked(true) + .recurse_untracked_dirs(true); + let statuses = repo.statuses(Some(&mut status_opts))?; + let mut file_infos = Vec::new(); + for entry in statuses.iter() { + let status = Self::status_to_string(entry.status()); + let path = entry.path().unwrap_or("unknown").to_string(); + file_infos.push(FileInfo { status, path }); + } + Ok(file_infos) + } + + fn git_log(&self) -> Result> { + let repo = self.get_repository()?; + let mut revwalk = repo.revwalk()?; + revwalk.push_head()?; + let mut changed_logs = Vec::new(); + for commit_id in revwalk { + let commit = &repo.find_commit(commit_id?)?; + let user = commit.author().name().unwrap_or("unknown").to_string(); + let time = commit.time(); + let message = commit.message().unwrap_or("").to_string(); + let tree = commit.tree()?; + let parent = if commit.parent_count() > 0 { + Some(commit.parent(0)?.tree()?) + } else { + None + }; + let mut diff_opts = DiffOptions::new(); + diff_opts.include_untracked(false); + diff_opts.include_ignored(false); + let diff = + repo.diff_tree_to_tree(parent.as_ref(), Some(&tree), Some(&mut diff_opts))?; + let mut paths = Vec::new(); + for delta in diff.deltas() { + let status = Self::delta_status_to_string(delta.status()); + let path = delta + .new_file() + .path() + .and_then(|p| p.to_str()) + .unwrap_or("unknown") + .to_string(); + paths.push(FileInfo { status, path }); + } + changed_logs.push(ChangedLog { + user, + time, + message, + paths, + }); + } + Ok(changed_logs) + } + + async fn create_default_directories(&self) -> Result<()> { + let base_path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); + let directories = [ + "solutions/accepted", + "solutions/rejected", + "tests", + "statements", + ]; + for dir in directories { + let path = base_path.join(dir); + fs::create_dir_all(path) + .await + .with_context(|| format!("Failed to create directory: {}", dir))?; + } + Ok(()) + } + + fn delta_status_to_string(status: git2::Delta) -> String { + match status { + git2::Delta::Added => "ADDED".to_string(), + git2::Delta::Modified => "MODIFIED".to_string(), + git2::Delta::Deleted => "DELETED".to_string(), + git2::Delta::Renamed => "RENAMED".to_string(), + git2::Delta::Typechange => "TYPECHANGE".to_string(), + _ => "OTHER".to_string(), + } + } + + fn status_to_string(status: git2::Status) -> String { + match status { + // 아직 add 되지 않은 상태 + git2::Status::WT_NEW => "ADDED".to_string(), + git2::Status::WT_MODIFIED => "MODIFIED".to_string(), + git2::Status::WT_DELETED => "DELETED".to_string(), + git2::Status::WT_RENAMED => "RENAMED".to_string(), + git2::Status::WT_TYPECHANGE => "TYPECHANGE".to_string(), + + // stage 된 상태 + git2::Status::INDEX_NEW => "ADDED".to_string(), + git2::Status::INDEX_MODIFIED => "MODIFIED".to_string(), + git2::Status::INDEX_DELETED => "DELETED".to_string(), + git2::Status::INDEX_RENAMED => "RENAMED".to_string(), + git2::Status::INDEX_TYPECHANGE => "TYPECHANGE".to_string(), + + _ => "OTHER".to_string(), + } + } + + fn get_repository(&self) -> Result { + let path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); + Repository::open(path).context("Failed to open git repository") + } +} + +#[derive(Debug, PartialEq, Eq)] +struct ChangedLog { + user: String, + time: Time, + message: String, + paths: Vec, +} + +#[derive(Debug, PartialEq, Eq)] +struct FileInfo { + status: String, + path: String, +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + use std::path::Path; + use tokio::fs; + + #[rstest] + #[case(git2::Delta::Added, "ADDED")] + #[case(git2::Delta::Modified, "MODIFIED")] + #[case(git2::Delta::Deleted, "DELETED")] + #[case(git2::Delta::Renamed, "RENAMED")] + #[case(git2::Delta::Typechange, "TYPECHANGE")] + fn can_parse_delta_status_to_string(#[case] status: git2::Delta, #[case] expected: String) { + assert_eq!(GitManager::delta_status_to_string(status), expected); + } + + #[rstest] + #[case(git2::Status::WT_NEW, "ADDED")] + #[case(git2::Status::WT_MODIFIED, "MODIFIED")] + #[case(git2::Status::WT_DELETED, "DELETED")] + #[case(git2::Status::WT_RENAMED, "RENAMED")] + #[case(git2::Status::WT_TYPECHANGE, "TYPECHANGE")] + #[case(git2::Status::INDEX_NEW, "ADDED")] + #[case(git2::Status::INDEX_MODIFIED, "MODIFIED")] + #[case(git2::Status::INDEX_DELETED, "DELETED")] + #[case(git2::Status::INDEX_RENAMED, "RENAMED")] + #[case(git2::Status::INDEX_TYPECHANGE, "TYPECHANGE")] + fn can_parse_status_to_string(#[case] status: git2::Status, #[case] expected: String) { + assert_eq!(GitManager::status_to_string(status), expected); + } + + #[tokio::test] + async fn can_init_git_repository() -> Result<(), std::io::Error> { + let problem_id = 10; + let git_manager = GitManager::new(problem_id); + assert!(git_manager.git_init().is_ok()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}").as_str()).exists()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/.git").as_str()).exists()); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } + + #[tokio::test] + async fn can_create_default_file() -> Result<(), std::io::Error> { + let problem_id = 12; + let git_manager = GitManager::new(problem_id); + assert!(git_manager.create_default_directories().await.is_ok()); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } + + #[tokio::test] + async fn can_create_problem() -> Result<(), std::io::Error> { + let problem_id = 13; + let git_manager = GitManager::new(problem_id); + assert!(git_manager.create_problem().await.is_ok()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}").as_str()).exists()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/.git").as_str()).exists()); + assert!( + Path::new(format!("{UPLOAD_DIR}/{problem_id}/solutions/accepted").as_str()).exists() + ); + assert!( + Path::new(format!("{UPLOAD_DIR}/{problem_id}/solutions/rejected").as_str()).exists() + ); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/tests").as_str()).exists()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/statements").as_str()).exists()); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } + + #[tokio::test] + async fn can_get_git_status() -> Result<(), tokio::io::Error> { + let problem_id = 14; + let git_manager = GitManager::new(problem_id); + + git_manager.git_init().unwrap(); + + fs::create_dir_all(format!("{UPLOAD_DIR}/{problem_id}/tests")).await?; + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.in"), "1 2").await?; + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.out"), "3").await?; + + let file_infos = git_manager.git_status().unwrap(); + let expected = vec![ + FileInfo { + status: "ADDED".to_string(), + path: "tests/1.in".to_string(), + }, + FileInfo { + status: "ADDED".to_string(), + path: "tests/1.out".to_string(), + }, + ]; + assert_eq!(file_infos, expected); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } + + #[tokio::test] + async fn can_git_add() -> Result<(), tokio::io::Error> { + let problem_id = 15; + let git_manager = GitManager::new(problem_id); + + git_manager.git_init().unwrap(); + + let repo = git_manager.get_repository().unwrap(); + fs::create_dir_all(format!("{UPLOAD_DIR}/{problem_id}/tests")).await?; + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.in"), "1 2").await?; + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.out"), "3").await?; + + assert!(git_manager.git_add_all().is_ok()); + + let statuses = repo.statuses(None).unwrap(); + + // 워킹 디렉토리에 존재하지 않아야 한다. + assert!(!statuses.iter().any(|e| e.status().is_wt_new())); + assert!(!statuses.iter().any(|e| e.status().is_wt_modified())); + assert!(!statuses.iter().any(|e| e.status().is_wt_deleted())); + + // 스테이징 영역에 올라와야 한다. + assert!(statuses.iter().all(|e| e.status().is_index_new())); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } + + #[tokio::test] + async fn can_commit() -> Result<(), tokio::io::Error> { + let problem_id = 16; + let git_manager = GitManager::new(problem_id); + git_manager.git_init().unwrap(); + + fs::create_dir_all(format!("{UPLOAD_DIR}/{problem_id}/tests")).await?; + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.in"), "1 2").await?; + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.out"), "3").await?; + + let commit_message = "add test 1"; + + assert!(git_manager.git_commit(commit_message.to_string()).is_ok()); + + let repo = git_manager.get_repository().unwrap(); + let head = repo.head().unwrap(); + let commit = head.peel_to_commit().unwrap(); + + assert_eq!(commit.message(), Some(commit_message)); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } + + #[tokio::test] + async fn can_get_log() -> Result<(), tokio::io::Error> { + let problem_id = 17; + let git_manager = GitManager::new(problem_id); + git_manager.git_init().unwrap(); + git_manager.create_default_directories().await.unwrap(); + + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.in"), "1 2").await?; + fs::write(format!("{UPLOAD_DIR}/{problem_id}/tests/1.out"), "3").await?; + + git_manager + .git_commit("create default file".to_string()) + .unwrap(); + + let log = git_manager.git_log().unwrap(); + + let expected_path = vec![ + FileInfo { + status: "ADDED".to_string(), + path: "tests/1.in".to_string(), + }, + FileInfo { + status: "ADDED".to_string(), + path: "tests/1.out".to_string(), + }, + ]; + assert_eq!(log[0].paths, expected_path); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } +} diff --git a/src/file_manager/mod.rs b/src/file_manager/mod.rs index bd9731a..f19fd34 100644 --- a/src/file_manager/mod.rs +++ b/src/file_manager/mod.rs @@ -1,5 +1,6 @@ mod handlers; mod models; +mod git; pub(crate) use handlers::*; pub use models::*; From b4eaafcde5ba7dae748e0c2ec6ad76fc58d806ad Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Wed, 23 Jul 2025 00:35:36 +0900 Subject: [PATCH 2/8] refactor: Remove accepted, rejected subdirectory in solution --- src/file_manager/git.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/file_manager/git.rs b/src/file_manager/git.rs index 3a092f2..6ae8080 100644 --- a/src/file_manager/git.rs +++ b/src/file_manager/git.rs @@ -121,12 +121,7 @@ impl GitManager { async fn create_default_directories(&self) -> Result<()> { let base_path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); - let directories = [ - "solutions/accepted", - "solutions/rejected", - "tests", - "statements", - ]; + let directories = ["solutions", "tests", "statements"]; for dir in directories { let path = base_path.join(dir); fs::create_dir_all(path) @@ -236,6 +231,9 @@ mod tests { let problem_id = 12; let git_manager = GitManager::new(problem_id); assert!(git_manager.create_default_directories().await.is_ok()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/solutions").as_str()).exists()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/tests").as_str()).exists()); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/statements").as_str()).exists()); fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; Ok(()) From fa02586284e6fa60046d21662b9890c05dd9dc4f Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Fri, 25 Jul 2025 23:25:16 +0900 Subject: [PATCH 3/8] fix: cargo fmt --- src/file_manager/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file_manager/mod.rs b/src/file_manager/mod.rs index f19fd34..b4ad472 100644 --- a/src/file_manager/mod.rs +++ b/src/file_manager/mod.rs @@ -1,6 +1,6 @@ +mod git; mod handlers; mod models; -mod git; pub(crate) use handlers::*; pub use models::*; From b83b919e5153de7f6c88b246689d5b96cf58ce23 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Fri, 25 Jul 2025 23:37:41 +0900 Subject: [PATCH 4/8] fix: Set git repository config --- src/file_manager/git.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/file_manager/git.rs b/src/file_manager/git.rs index 6ae8080..16c1b9e 100644 --- a/src/file_manager/git.rs +++ b/src/file_manager/git.rs @@ -164,7 +164,17 @@ impl GitManager { fn get_repository(&self) -> Result { let path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); - Repository::open(path).context("Failed to open git repository") + if let Ok(repo) = Repository::open(&path) { + let mut config = repo.config()?; + config.set_str("user.name", "admin")?; + config.set_str("user.email", "admin@coduck.com")?; + Ok(repo) + } else { + Err(anyhow::anyhow!( + "Failed to open git repository at {:?}", + path + )) + } } } @@ -246,12 +256,7 @@ mod tests { assert!(git_manager.create_problem().await.is_ok()); assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}").as_str()).exists()); assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/.git").as_str()).exists()); - assert!( - Path::new(format!("{UPLOAD_DIR}/{problem_id}/solutions/accepted").as_str()).exists() - ); - assert!( - Path::new(format!("{UPLOAD_DIR}/{problem_id}/solutions/rejected").as_str()).exists() - ); + assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/solutions").as_str()).exists()); assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/tests").as_str()).exists()); assert!(Path::new(format!("{UPLOAD_DIR}/{problem_id}/statements").as_str()).exists()); From 5b0f3cbf734004c9426cf1690fb686aaad3fa392 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Fri, 25 Jul 2025 23:41:51 +0900 Subject: [PATCH 5/8] fix: Update error message and comments --- src/file_manager/git.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/file_manager/git.rs b/src/file_manager/git.rs index 16c1b9e..efc4cff 100644 --- a/src/file_manager/git.rs +++ b/src/file_manager/git.rs @@ -50,7 +50,7 @@ impl GitManager { Ok(head_ref) => { let head = head_ref .target() - .ok_or_else(|| anyhow::anyhow!("HEAD refers to non-HEAD"))?; + .ok_or_else(|| anyhow::anyhow!("HEAD does not point to a valid commit"))?; vec![repo.find_commit(head)?] } Err(_) => Vec::new(), @@ -144,14 +144,14 @@ impl GitManager { fn status_to_string(status: git2::Status) -> String { match status { - // 아직 add 되지 않은 상태 + // Not yet added to index git2::Status::WT_NEW => "ADDED".to_string(), git2::Status::WT_MODIFIED => "MODIFIED".to_string(), git2::Status::WT_DELETED => "DELETED".to_string(), git2::Status::WT_RENAMED => "RENAMED".to_string(), git2::Status::WT_TYPECHANGE => "TYPECHANGE".to_string(), - // stage 된 상태 + // Staged state git2::Status::INDEX_NEW => "ADDED".to_string(), git2::Status::INDEX_MODIFIED => "MODIFIED".to_string(), git2::Status::INDEX_DELETED => "DELETED".to_string(), From 9f616cb5968cedd1769b7eb15be91ce8543439e9 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Sat, 26 Jul 2025 00:13:49 +0900 Subject: [PATCH 6/8] refactor: extract default directory names to a constant array --- src/file_manager/git.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/file_manager/git.rs b/src/file_manager/git.rs index efc4cff..a390843 100644 --- a/src/file_manager/git.rs +++ b/src/file_manager/git.rs @@ -6,6 +6,7 @@ use std::path::PathBuf; use tokio::fs; const UPLOAD_DIR: &str = "uploads"; +const DEFAULT_DIRECTORIES: [&str; 3] = ["solutions", "tests", "statements"]; #[derive(Debug)] struct GitManager { @@ -121,8 +122,7 @@ impl GitManager { async fn create_default_directories(&self) -> Result<()> { let base_path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); - let directories = ["solutions", "tests", "statements"]; - for dir in directories { + for dir in DEFAULT_DIRECTORIES { let path = base_path.join(dir); fs::create_dir_all(path) .await From 487c95be22f6a4ea738f038ccfa567dd0e3b7d6e Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Sat, 26 Jul 2025 00:16:05 +0900 Subject: [PATCH 7/8] refactor: simplify get_repository error handling using and_then and with_context --- src/file_manager/git.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/file_manager/git.rs b/src/file_manager/git.rs index a390843..988455b 100644 --- a/src/file_manager/git.rs +++ b/src/file_manager/git.rs @@ -164,17 +164,14 @@ impl GitManager { fn get_repository(&self) -> Result { let path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); - if let Ok(repo) = Repository::open(&path) { - let mut config = repo.config()?; - config.set_str("user.name", "admin")?; - config.set_str("user.email", "admin@coduck.com")?; - Ok(repo) - } else { - Err(anyhow::anyhow!( - "Failed to open git repository at {:?}", - path - )) - } + Repository::open(&path) + .with_context(|| format!("Failed to open git repository at {:?}", path)) + .and_then(|repo| { + let mut config = repo.config()?; + config.set_str("user.name", "admin")?; + config.set_str("user.email", "admin@coduck.com")?; + Ok(repo) + }) } } From 2cc072c079369046c8273fbc25326fb56d248163 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Sat, 26 Jul 2025 00:50:43 +0900 Subject: [PATCH 8/8] refactor: Move git user config setup from get_repository to git_init --- src/file_manager/git.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/file_manager/git.rs b/src/file_manager/git.rs index 988455b..f679ba3 100644 --- a/src/file_manager/git.rs +++ b/src/file_manager/git.rs @@ -21,6 +21,12 @@ impl GitManager { fn git_init(&self) -> Result<()> { let path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); Repository::init(&path) + .and_then(|repo| { + let mut config = repo.config()?; + config.set_str("user.name", "admin")?; + config.set_str("user.email", "admin@coduck.com")?; + Ok(repo) + }) .map(|_| ()) .with_context(|| format!("Failed to init git repo at {:?}", path)) } @@ -166,12 +172,6 @@ impl GitManager { let path = PathBuf::from(UPLOAD_DIR).join(self.problem_id.to_string()); Repository::open(&path) .with_context(|| format!("Failed to open git repository at {:?}", path)) - .and_then(|repo| { - let mut config = repo.config()?; - config.set_str("user.name", "admin")?; - config.set_str("user.email", "admin@coduck.com")?; - Ok(repo) - }) } } @@ -233,6 +233,23 @@ mod tests { Ok(()) } + #[tokio::test] + async fn can_set_config() -> Result<(), std::io::Error> { + let problem_id = 11; + let git_manager = GitManager::new(problem_id); + assert!(git_manager.git_init().is_ok()); + let repo = git_manager.get_repository().unwrap(); + let config = repo.config().unwrap(); + assert_eq!(config.get_string("user.name"), Ok("admin".to_string())); + assert_eq!( + config.get_string("user.email"), + Ok("admin@coduck.com".to_string()) + ); + + fs::remove_dir_all(format!("{UPLOAD_DIR}/{problem_id}")).await?; + Ok(()) + } + #[tokio::test] async fn can_create_default_file() -> Result<(), std::io::Error> { let problem_id = 12;