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();