diff --git a/aria/contents/docs/libra/command/init/index.mdx b/aria/contents/docs/libra/command/init/index.mdx index 1e46b888f..a675d11e1 100644 --- a/aria/contents/docs/libra/command/init/index.mdx +++ b/aria/contents/docs/libra/command/init/index.mdx @@ -22,6 +22,8 @@ which sets up the repository without a working directory, containing only Libra **Note**:Unlike Git's `--bare`, the Libra init `--bare` initializes a bare repository that contains Libra's own SQLite database. +- `--template ` + Specify a directory from which templates will be used to initialize the repository. - `-b`, `--initial-branch ` Override the name of the initial branch - `-h`, `--help` Print help diff --git a/libra/src/command/clone.rs b/libra/src/command/clone.rs index d81147b7b..46d872ed0 100644 --- a/libra/src/command/clone.rs +++ b/libra/src/command/clone.rs @@ -88,6 +88,7 @@ pub async fn execute(args: CloneArgs) { initial_branch: args.branch.clone(), repo_directory: local_path.to_str().unwrap().to_string(), quiet: false, + template: None, }; command::init::execute(init_args).await; diff --git a/libra/src/command/init.rs b/libra/src/command/init.rs index 502a106eb..61e73769d 100644 --- a/libra/src/command/init.rs +++ b/libra/src/command/init.rs @@ -23,6 +23,10 @@ pub struct InitArgs { #[clap(long, required = false)] pub bare: bool, // Default is false + /// directory from which templates will be used + #[clap(long = "template", name = "template-directory", required = false)] + pub template: Option, + /// Set the initial branch name #[clap(short = 'b', long, required = false)] pub initial_branch: Option, @@ -82,6 +86,29 @@ fn is_writable(cur_dir: &Path) -> io::Result<()> { Ok(()) } +/// Recursively copy the contents of the template directory to the destination directory. +/// +/// # Behavior +/// - Directories are created as needed. +/// - Existing files in `dst` are NOT overwritten. +/// - Subdirectories are copied recursively. +fn copy_template(src: &Path, dst: &Path) -> io::Result<()> { + for entry in fs::read_dir(src)? { + let entry = entry?; + let file_type = entry.file_type()?; + let dest_path = dst.join(entry.file_name()); + + if file_type.is_dir() { + fs::create_dir_all(&dest_path)?; + copy_template(&entry.path(), &dest_path)?; + } else if !dest_path.exists() { + // Only copy if the file does not already exist + fs::copy(entry.path(), &dest_path)?; + } + } + Ok(()) +} + /// Initialize a new Libra repository /// This function creates the necessary directories and files for a new Libra repository. /// It also sets up the database and the initial configuration. @@ -127,41 +154,63 @@ pub async fn init(args: InitArgs) -> io::Result<()> { } } - // Create .libra & sub-dirs - let dirs = ["objects/pack", "objects/info", "info", "hooks"]; + // ensure root dir exists + fs::create_dir_all(&root_dir)?; + + // If a template path is provided, copy the template files to the root directory + if let Some(template_path) = &args.template { + let template_dir = Path::new(template_path); + if template_dir.exists() { + copy_template(template_dir, &root_dir)?; + } else { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("template directory '{}' does not exist", template_path), + )); + } + } else { + // Create info & hooks + let dirs = ["info", "hooks"]; + for dir in dirs { + fs::create_dir_all(root_dir.join(dir))?; + } + // Create info/exclude + // `include_str!` includes the file content while compiling + fs::write( + root_dir.join("info/exclude"), + include_str!("../../template/exclude"), + )?; + // Create .libra/description + fs::write( + root_dir.join("description"), + include_str!("../../template/description"), + )?; + // Create .libra/hooks/pre-commit.sh + fs::write( + root_dir.join("hooks").join("pre-commit.sh"), + include_str!("../../template/pre-commit.sh"), + )?; + + // Set Permission + #[cfg(not(target_os = "windows"))] + { + use std::os::unix::fs::PermissionsExt; + let perms = fs::Permissions::from_mode(0o755); + fs::set_permissions(root_dir.join("hooks").join("pre-commit.sh"), perms)?; + } + + // Create .libra/hooks/pre-commit.ps1 + fs::write( + root_dir.join("hooks").join("pre-commit.ps1"), + include_str!("../../template/pre-commit.ps1"), + )?; + } + + // Complete .libra and sub-directories + let dirs = ["objects/pack", "objects/info"]; for dir in dirs { fs::create_dir_all(root_dir.join(dir))?; } - // Create info/exclude - // `include_str!` includes the file content while compiling - fs::write( - root_dir.join("info/exclude"), - include_str!("../../template/exclude"), - )?; - // Create .libra/description - fs::write( - root_dir.join("description"), - include_str!("../../template/description"), - )?; - // Create .libra/hooks/pre-commit.sh - fs::write( - root_dir.join("hooks").join("pre-commit.sh"), - include_str!("../../template/pre-commit.sh"), - )?; - - // Set Permission - #[cfg(not(target_os = "windows"))] - { - use std::os::unix::fs::PermissionsExt; - let perms = fs::Permissions::from_mode(0o755); - fs::set_permissions(root_dir.join("hooks").join("pre-commit.sh"), perms)?; - } - - // Create .libra/hooks/pre-commit.ps1 - fs::write( - root_dir.join("hooks").join("pre-commit.ps1"), - include_str!("../../template/pre-commit.ps1"), - )?; // Create database: .libra/libra.db let conn; @@ -206,6 +255,7 @@ pub async fn init(args: InitArgs) -> io::Result<()> { Ok(()) } + /// Initialize the configuration for the Libra repository /// This function creates the necessary configuration entries in the database. async fn init_config(conn: &DbConn) -> Result<(), DbErr> { diff --git a/libra/src/utils/test.rs b/libra/src/utils/test.rs index d951d99a7..3146e3b83 100644 --- a/libra/src/utils/test.rs +++ b/libra/src/utils/test.rs @@ -106,6 +106,7 @@ pub async fn setup_with_new_libra_in(temp_path: impl AsRef) { initial_branch: None, repo_directory: temp_path.as_ref().to_str().unwrap().to_string(), quiet: false, + template: None, }; command::init::init(args).await.unwrap(); } diff --git a/libra/tests/command/checkout_test.rs b/libra/tests/command/checkout_test.rs index 99227f851..b6c9b42e1 100644 --- a/libra/tests/command/checkout_test.rs +++ b/libra/tests/command/checkout_test.rs @@ -64,6 +64,7 @@ async fn test_checkout_module_functions() { initial_branch: Some("main".to_string()), repo_directory: temp_path.path().to_str().unwrap().to_string(), quiet: false, + template: None, }; init::init(init_args) diff --git a/libra/tests/command/init_test.rs b/libra/tests/command/init_test.rs index 93c704414..11b3e1c00 100644 --- a/libra/tests/command/init_test.rs +++ b/libra/tests/command/init_test.rs @@ -32,6 +32,7 @@ async fn test_init() { initial_branch: None, repo_directory: target_dir.to_str().unwrap().to_string(), quiet: false, + template: None, }; // Run the init function init(args).await.unwrap(); @@ -44,6 +45,117 @@ async fn test_init() { verify_init(libra_dir.as_path()); } +#[tokio::test] +#[serial] +/// Test the init function with a template directory +async fn test_init_template() { + use std::fs; + use tempfile::tempdir; + + // Create a temporary target directory for the new repo + let target_dir = tempdir().unwrap().keep(); + + // Create a temporary template directory + let template_dir = tempdir().unwrap(); + + // Set up template structure similar to Git template + fs::create_dir_all(template_dir.path().join("objects/pack")).unwrap(); + fs::create_dir_all(template_dir.path().join("objects/info")).unwrap(); + fs::create_dir_all(template_dir.path().join("info")).unwrap(); + + // Add description file in the template + fs::write( + template_dir.path().join("description"), + "Template repository", + ) + .unwrap(); + + // Add info/exclude file in the template + fs::write(template_dir.path().join("info/exclude"), "").unwrap(); + + // Prepare init arguments with template path + let args = InitArgs { + bare: false, + initial_branch: None, + repo_directory: target_dir.to_str().unwrap().to_string(), + quiet: false, + template: Some(template_dir.path().to_str().unwrap().to_string()), + }; + + // Run the init function + init(args).await.unwrap(); + + // Verify that the `.libra` directory exists + let libra_dir = target_dir.join(".libra"); + assert!(libra_dir.exists(), ".libra directory does not exist"); + + // Verify the repository initialization structure + verify_init(libra_dir.as_path()); + + // --- Additional checks for template contents --- + + // Verify that description file is copied from template + let description_path = libra_dir.join("description"); + assert!( + description_path.exists(), + "Template description file not copied" + ); + + // Verify that info/exclude file is copied from template + let exclude_path = libra_dir.join("info/exclude"); + assert!( + exclude_path.exists(), + "Template info/exclude file not copied" + ); + + // Verify that objects subdirectories are copied from template + assert!( + libra_dir.join("objects/pack").exists(), + "Template objects/pack directory not copied" + ); + assert!( + libra_dir.join("objects/info").exists(), + "Template objects/info directory not copied" + ); +} + +#[tokio::test] +#[serial] +/// Test the init function with an invalid template path +async fn test_init_with_invalid_template_path() { + use tempfile::tempdir; + + // Create a temporary target directory for the new repo + let target_dir = tempdir().unwrap().keep(); + + // Provide a non-existent template path + let invalid_template_path = "/path/to/nonexistent/template"; + + let args = InitArgs { + bare: false, + initial_branch: None, + repo_directory: target_dir.to_str().unwrap().to_string(), + quiet: false, + template: Some(invalid_template_path.to_string()), + }; + + // Run the init function and expect it to return an error + let result = init(args).await; + + // Verify that the function returns an error due to invalid template path + assert!( + result.is_err(), + "Init should fail when template path does not exist" + ); + + // Optionally, verify the error kind/message if your init function provides it + if let Err(err) = result { + // Uncomment and adjust depending on your error type + // assert_eq!(err.kind(), Some(ExpectedErrorKind::NotFound)); + println!("Received expected error: {:?}", err); + } +} + #[tokio::test] #[serial] /// Test the init function with the --bare flag @@ -57,6 +169,7 @@ async fn test_init_bare() { initial_branch: None, repo_directory: target_dir.to_str().unwrap().to_string(), quiet: false, + template: None, }; // Run the init function init(args).await.unwrap(); @@ -64,6 +177,7 @@ async fn test_init_bare() { // Verify the contents of the other directory verify_init(target_dir.as_path()); } + #[tokio::test] #[serial] /// Test the init function with the --bare flag and an existing repository @@ -76,6 +190,7 @@ async fn test_init_bare_with_existing_repo() { initial_branch: None, repo_directory: target_dir.to_str().unwrap().to_string(), quiet: false, + template: None, }; init(init_args).await.unwrap(); // Execute init for bare repository @@ -86,6 +201,7 @@ async fn test_init_bare_with_existing_repo() { initial_branch: None, repo_directory: target_dir.to_str().unwrap().to_string(), quiet: false, + template: None, }; init(args).await }; @@ -110,6 +226,7 @@ async fn test_init_with_initial_branch() { initial_branch: Some("main".to_string()), repo_directory: temp_path.path().to_str().unwrap().to_string(), quiet: false, + template: None, }; // Run the init function init(args).await.unwrap(); @@ -157,6 +274,7 @@ async fn test_invalid_branch_name(branch_name: &str) { initial_branch: Some(branch_name.to_string()), repo_directory: target_dir.to_str().unwrap().to_string(), quiet: false, + template: None, }; // Run the init function let result = init(args).await; @@ -180,6 +298,7 @@ async fn test_init_with_directory() { initial_branch: None, repo_directory: test_dir.to_str().unwrap().to_owned(), quiet: false, + template: None, }; // Run the init function init(args).await.unwrap(); @@ -209,6 +328,7 @@ async fn test_init_with_invalid_directory() { initial_branch: None, repo_directory: test_dir.to_str().unwrap().to_owned(), quiet: false, + template: None, }; // Run the init function let result = init(args).await; @@ -249,6 +369,7 @@ async fn test_init_with_unauthorized_directory() { initial_branch: None, repo_directory: test_dir.to_str().unwrap().to_owned(), quiet: false, + template: None, }; // Run the init function let result = init(args).await; @@ -272,6 +393,7 @@ async fn test_init_quiet() { initial_branch: None, repo_directory: target_dir.to_str().unwrap().to_string(), quiet: true, + template: None, }; // Run the init function init(args).await.unwrap();