Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libra] Add -b/--initial-branch option for the init command #812

Merged
merged 8 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions aria/contents/docs/libra/command/init/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ which sets up the repository without a working directory, containing only Libra
Initialize a bare repository.<br>
**Note**:Unlike Git's `--bare`, the Libra init `--bare` initializes a bare repository
that contains Libra's own SQLite database.
- `-b`, `--initial-branch <INITIAL_BRANCH>`
Override the name of the initial branch
- `-h`, `--help` Print help
12 changes: 7 additions & 5 deletions libra/src/command/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,17 @@ async fn list_branches(remotes: bool) {
}


fn is_valid_git_branch_name(name: &str) -> bool {
// 检查是否包含不允许的字符
pub fn is_valid_git_branch_name(name: &str) -> bool {
// Validate branch name
// Not contain spaces, control characters or special characters
if name.contains(&[' ', '\t', '\\', ':', '"', '?', '*', '['][..])
|| name.chars().any(|c| c.is_ascii_control())
{
return false;
}

// 检查其他Git规则
// Not start or end with a slash ('/'), or end with a dot ('.')
// Not contain consecutive slashes ('//') or dots ('..')
if name.starts_with('/')
|| name.ends_with('/')
|| name.ends_with('.')
Expand All @@ -204,12 +206,12 @@ fn is_valid_git_branch_name(name: &str) -> bool {
return false;
}

// 检查特殊的Git保留字
// Not be reserved names like 'HEAD' or contain '@{'
if name == "HEAD" || name.contains("@{") {
return false;
}

// 检查是否是空字符串或只包含点
// Not be empty or just a dot ('.')
if name.trim().is_empty() || name.trim() == "." {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion libra/src/command/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub async fn execute(args: CloneArgs) {

// CAUTION: change [current_dir] to the repo directory
env::set_current_dir(&local_path).unwrap();
let init_args = command::init::InitArgs { bare: false };
let init_args = command::init::InitArgs { bare: false, initial_branch: None };
command::init::execute(init_args).await;

/* fetch remote */
Expand Down
89 changes: 82 additions & 7 deletions libra/src/command/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ use crate::internal::db;
use crate::internal::model::{config, reference};
use crate::utils::util::{DATABASE, ROOT_DIR};
use std::path::Path;
use crate::command::branch;

#[derive(Parser, Debug)]
pub struct InitArgs {
/// Create a bare repository
#[clap(short, long, required = false)]
#[clap(long, required = false)]
pub bare: bool, // Default is false

/// Set the initial branch name
#[clap(short = 'b', long, required = false)]
pub initial_branch: Option<String>,
}

/// Execute the init function
Expand Down Expand Up @@ -62,7 +67,16 @@ pub async fn init(args: InitArgs) -> io::Result<()> {
"Initialization failed: The repository is already initialized at the specified location.
If you wish to reinitialize, please remove the existing directory or file.",
));

}

// Check if the branch name is valid
if let Some(ref branch_name) = args.initial_branch {
if !branch::is_valid_git_branch_name(branch_name) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid branch name: '{}'.\n\nBranch names must:\n- Not contain spaces, control characters, or any of these characters: \\ : \" ? * [\n- Not start or end with a slash ('/'), or end with a dot ('.')\n- Not contain consecutive slashes ('//') or dots ('..')\n- Not be reserved names like 'HEAD' or contain '@{{'\n- Not be empty or just a dot ('.')\n\nPlease choose a valid branch name.", branch_name),
));
}
}

// Create .libra & sub-dirs
Expand Down Expand Up @@ -91,7 +105,7 @@ pub async fn init(args: InitArgs) -> io::Result<()> {

// Create HEAD
reference::ActiveModel {
name: Set(Some("master".to_owned())),
name: Set(Some(args.initial_branch.unwrap_or_else(|| "master".to_owned()))),
kind: Set(reference::ConfigKind::Head),
..Default::default() // all others are `NotSet`
}
Expand Down Expand Up @@ -173,6 +187,7 @@ fn set_dir_hidden(_dir: &str) -> io::Result<()> {
mod tests {
use super::*;
use crate::utils::test;
use crate::internal::head::Head;

pub fn verify_init(base_dir: &Path){

Expand Down Expand Up @@ -202,7 +217,7 @@ mod tests {
async fn test_init() {
// Set up the test environment without a Libra repository
test::setup_clean_testing_env();
let args = InitArgs {bare: false};
let args = InitArgs { bare: false, initial_branch: None };
// Run the init function
init(args).await.unwrap();

Expand All @@ -220,7 +235,7 @@ mod tests {
// Set up the test environment without a Libra repository
test::setup_clean_testing_env();
// Run the init function with --bare flag
let args = InitArgs {bare: true};
let args = InitArgs { bare: true, initial_branch: None };
// Run the init function
init(args).await.unwrap();

Expand All @@ -235,12 +250,12 @@ mod tests {
test::setup_clean_testing_env();

// Initialize a bare repository
let init_args = InitArgs { bare: false };
let init_args = InitArgs { bare: false, initial_branch: None };
init(init_args).await.unwrap(); // Execute init for bare repository

// Simulate trying to reinitialize the bare repo
let result = async {
let args = InitArgs { bare: true };
let args = InitArgs { bare: true, initial_branch: None };
init(args).await
};

Expand All @@ -250,4 +265,64 @@ mod tests {
assert!(err.to_string().contains("Initialization failed")); // Check error message contains "Already initialized"
}

/// Test the init function with an initial branch name
#[tokio::test]
async fn test_init_with_initial_branch() {
// Set up the test environment without a Libra repository
test::setup_clean_testing_env();
let args = InitArgs { bare: false, initial_branch: Some("main".to_string()) };
// Run the init function
init(args).await.unwrap();

// Verify that the `.libra` directory exists
let libra_dir = Path::new(".libra");
assert!(libra_dir.exists(), ".libra directory does not exist");

// Verify the contents of the other directory
verify_init(libra_dir);

// Verify the HEAD reference
match Head::current().await {
Head::Branch(current_branch) => {
assert_eq!(current_branch, "main");
}
_ => panic!("should be branch"),
};
}

/// Test the init function with an invalid branch name
#[tokio::test]
async fn test_init_with_invalid_branch() {
genedna marked this conversation as resolved.
Show resolved Hide resolved
// Cover all invalid branch name cases
test_invalid_branch_name("master ").await;
test_invalid_branch_name("master\t").await;
test_invalid_branch_name("master\\").await;
test_invalid_branch_name("master:").await;
test_invalid_branch_name("master\"").await;
test_invalid_branch_name("master?").await;
test_invalid_branch_name("master*").await;
test_invalid_branch_name("master[").await;
test_invalid_branch_name("/master").await;
test_invalid_branch_name("master/").await;
test_invalid_branch_name("master.").await;
test_invalid_branch_name("mast//er").await;
test_invalid_branch_name("mast..er").await;
test_invalid_branch_name("HEAD").await;
test_invalid_branch_name("mast@{er").await;
test_invalid_branch_name("").await;
test_invalid_branch_name(".").await;
}

async fn test_invalid_branch_name(branch_name: &str) {
// Set up the test environment without a Libra repository
test::setup_clean_testing_env();
let args = InitArgs { bare: false, initial_branch: Some(branch_name.to_string()) };
// Run the init function
let result = init(args).await;
// Check for the error
let err = result.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); // Check error type
assert!(err.to_string().contains("invalid branch name")); // Check error message contains "invalid branch name"
}

}
2 changes: 1 addition & 1 deletion libra/src/utils/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn setup_clean_testing_env() {
/// switch to test dir and create a new .libra
pub async fn setup_with_new_libra() {
setup_clean_testing_env();
let args = command::init::InitArgs { bare: false };
let args = command::init::InitArgs { bare: false, initial_branch: None };
command::init::init(args).await.unwrap();
}

Expand Down