From 6cd9fe4adbca396358cb9177fa687422a4a41d58 Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Wed, 30 Jul 2025 11:02:35 +0800 Subject: [PATCH 01/23] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0add=E3=80=81diff?= =?UTF-8?q?=E3=80=81remove=E5=91=BD=E4=BB=A4=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/add_test.rs | 241 ++++++++++++++++++ libra/tests/command/diff_test.rs | 390 +++++++++++++++++++++++++++++ libra/tests/command/remove_test.rs | 319 +++++++++++++++++++++++ 3 files changed, 950 insertions(+) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 8b1378917..3eb54f481 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -1 +1,242 @@ +use super::*; +use std::fs; +use std::io::Write; +#[tokio::test] +#[serial] +/// Tests the basic functionality of add command by adding a single file +async fn test_add_single_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a new file + let file_content = "Hello, World!"; + let file_path = "test_file.txt"; + let mut file = fs::File::create(file_path).unwrap(); + file.write_all(file_content.as_bytes()).unwrap(); + + // Execute add command + add::execute(AddArgs { + pathspec: vec![String::from(file_path)], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify the file was added to index + let changes = changes_to_be_staged(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == file_path)); +} + +#[tokio::test] +#[serial] +/// Tests adding multiple files at once +async fn test_add_multiple_files() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files + for i in 1..=3 { + let file_content = format!("File content {}", i); + let file_path = format!("test_file_{}.txt", i); + let mut file = fs::File::create(&file_path).unwrap(); + file.write_all(file_content.as_bytes()).unwrap(); + } + + // Execute add command + add::execute(AddArgs { + pathspec: vec![ + String::from("test_file_1.txt"), + String::from("test_file_2.txt"), + String::from("test_file_3.txt"), + ], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify all files were added to index + let changes = changes_to_be_staged(); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); +} + +#[tokio::test] +#[serial] +/// Tests the --all flag which adds all files in the working tree +async fn test_add_all_flag() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files + for i in 1..=3 { + let file_content = format!("File content {}", i); + let file_path = format!("test_file_{}.txt", i); + let mut file = fs::File::create(&file_path).unwrap(); + file.write_all(file_content.as_bytes()).unwrap(); + } + + // Execute add command with --all flag + add::execute(AddArgs { + pathspec: vec![], + all: true, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify all files were added to index + let changes = changes_to_be_staged(); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); +} + +#[tokio::test] +#[serial] +/// Tests the --update flag which only updates files already in the index +async fn test_add_update_flag() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create files and add one to the index + let tracked_file = "tracked_file.txt"; + let untracked_file = "untracked_file.txt"; + + // Create and write initial content + let mut file1 = fs::File::create(tracked_file).unwrap(); + file1.write_all(b"Initial content").unwrap(); + + let mut file2 = fs::File::create(untracked_file).unwrap(); + file2.write_all(b"Initial content").unwrap(); + + // Add only one file to the index + add::execute(AddArgs { + pathspec: vec![String::from(tracked_file)], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Modify both files + let mut file1 = fs::OpenOptions::new().write(true).open(tracked_file).unwrap(); + file1.write_all(b" - Modified").unwrap(); + + let mut file2 = fs::OpenOptions::new().write(true).open(untracked_file).unwrap(); + file2.write_all(b" - Modified").unwrap(); + + // Execute add command with --update flag + add::execute(AddArgs { + pathspec: vec![String::from(".")], + all: false, + update: true, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify only tracked file was updated + let changes = changes_to_be_staged(); + // Tracked file should appear in changes as modified (because it was updated) + assert!(changes.modified.iter().any(|x| x.to_str().unwrap() == tracked_file)); + // Untracked file should still be untracked and show as new + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == untracked_file)); +} + +#[tokio::test] +#[serial] +/// Tests adding files with respect to ignore patterns in .libraignore +async fn test_add_with_ignore_patterns() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create .libraignore file + let mut ignore_file = fs::File::create(".libraignore").unwrap(); + ignore_file.write_all(b"ignored_*.txt\nignore_dir/").unwrap(); + + // Create files that should be ignored and not ignored + let ignored_file = "ignored_file.txt"; + let tracked_file = "tracked_file.txt"; + + // Create directory that should be ignored + fs::create_dir("ignore_dir").unwrap(); + let ignored_dir_file = "ignore_dir/file.txt"; + + // Create and write content + let mut file1 = fs::File::create(ignored_file).unwrap(); + file1.write_all(b"Should be ignored").unwrap(); + + let mut file2 = fs::File::create(tracked_file).unwrap(); + file2.write_all(b"Should be tracked").unwrap(); + + let mut file3 = fs::File::create(ignored_dir_file).unwrap(); + file3.write_all(b"Should be ignored").unwrap(); + + // Execute add command with all files + add::execute(AddArgs { + pathspec: vec![String::from(".")], + all: true, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify only non-ignored files were added + let changes = changes_to_be_staged(); + // Ignored files should not appear in changes.new + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_file)); + // Directory files should not appear in changes.new + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_dir_file)); + // Non-ignored file should not show as new (was added) + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == tracked_file)); +} + +#[tokio::test] +#[serial] +/// Tests the dry-run flag which should not actually add files +async fn test_add_dry_run() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file + let file_path = "test_file.txt"; + let mut file = fs::File::create(file_path).unwrap(); + file.write_all(b"Test content").unwrap(); + + // Execute add command with dry-run + add::execute(AddArgs { + pathspec: vec![String::from(file_path)], + all: false, + update: false, + verbose: false, + dry_run: true, + ignore_errors: false, + }) + .await; + + // Verify the file was not actually added to index + let changes = changes_to_be_staged(); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); +} diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 8b1378917..019abde83 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -1 +1,391 @@ +use super::*; +use std::fs; +use std::io::Write; +use std::path::PathBuf; +use libra::command::diff::{self, DiffArgs}; + +/// Helper function to create a file with content +fn create_file(path: &str, content: &str) { + let mut file = fs::File::create(path).unwrap(); + file.write_all(content.as_bytes()).unwrap(); +} + +/// Helper function to modify a file with new content +fn modify_file(path: &str, content: &str) { + let mut file = fs::OpenOptions::new().write(true).truncate(true).open(path).unwrap(); + file.write_all(content.as_bytes()).unwrap(); +} + +#[tokio::test] +#[serial] +/// Tests the basic diff functionality between working directory and HEAD +async fn test_basic_diff() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Create initial commit + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify the file + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + // Run diff command + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // We can't easily capture stdout, so we'll check that the command didn't panic +} + +#[tokio::test] +#[serial] +/// Tests diff with staged changes +async fn test_diff_staged() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Create initial commit + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify the file and stage it + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Modify the file again (so working dir differs from staged) + modify_file("file1.txt", "Modified content again\nLine 2\nLine 3 changed again\n"); + + // Run diff command with --staged flag + diff::execute(DiffArgs { + old: None, + new: None, + staged: true, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // The command should complete without panicking +} + +#[tokio::test] +#[serial] +/// Tests diff between two specific commits +async fn test_diff_between_commits() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and make initial commit + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Get the first commit hash + let first_commit = Head::current_commit().await.unwrap(); + + // Modify file and create a second commit + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Second commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Get the second commit hash + let second_commit = Head::current_commit().await.unwrap(); + + // Run diff command comparing the two commits + diff::execute(DiffArgs { + old: Some(first_commit.to_string()), + new: Some(second_commit.to_string()), + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // The command should complete without panicking +} + +#[tokio::test] +#[serial] +/// Tests diff with specific file path +async fn test_diff_with_pathspec() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files and commit them + create_file("file1.txt", "File 1 content\nLine 2\nLine 3\n"); + create_file("file2.txt", "File 2 content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from(".")] + .iter() + .map(|s| s.to_string()) + .collect(), + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify both files + modify_file("file1.txt", "File 1 modified\nLine 2\nLine 3 changed\n"); + modify_file("file2.txt", "File 2 modified\nLine 2\nLine 3 changed\n"); + + // Run diff command with specific file path + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![String::from("file1.txt")], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // The command should complete without panicking +} + +#[tokio::test] +#[serial] +/// Tests diff with output to a file +async fn test_diff_output_to_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and commit it + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify the file + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + // Output file path + let output_file = "diff_output.txt"; + + // Run diff command with output to file + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: Some(output_file.to_string()), + }) + .await; + + // Verify the output file exists + assert!(fs::metadata(output_file).is_ok(), "Output file should exist"); + + // Read the file content to make sure it contains diff output + let content = fs::read_to_string(output_file).unwrap(); + assert!(content.contains("diff --git"), "Output should contain diff header"); +} + +#[tokio::test] +#[serial] +/// Tests diff with different algorithms +async fn test_diff_algorithms() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file with some content to make a non-trivial diff + create_file( + "file1.txt", + "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\n", + ); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Make complex changes to test different algorithms + modify_file( + "file1.txt", + "Line 1\nModified Line\nLine 3\nNew Line\nLine 5\nLine 6\nDeleted Line 7\n", + ); + + // Test histogram algorithm + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: Some("histogram_diff.txt".to_string()), + }) + .await; + + // Test myers algorithm + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("myers".to_string()), + output: Some("myers_diff.txt".to_string()), + }) + .await; + + // Test myersMinimal algorithm + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("myersMinimal".to_string()), + output: Some("myersMinimal_diff.txt".to_string()), + }) + .await; + + // Verify all output files exist + assert!(fs::metadata("histogram_diff.txt").is_ok(), "Histogram output file should exist"); + assert!(fs::metadata("myers_diff.txt").is_ok(), "Myers output file should exist"); + assert!(fs::metadata("myersMinimal_diff.txt").is_ok(), "MyersMinimal output file should exist"); +} diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 8b1378917..5bfe9c017 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -1 +1,320 @@ +use super::*; +use std::fs; +use std::io::Write; +use std::path::PathBuf; +use libra::command::remove::{self, RemoveArgs}; + +/// Helper function to create a file with content +fn create_file(path: &str, content: &str) -> PathBuf { + let path = PathBuf::from(path); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).unwrap(); + } + let mut file = fs::File::create(&path).unwrap(); + file.write_all(content.as_bytes()).unwrap(); + path +} + +#[tokio::test] +#[serial] +/// Tests the basic remove functionality by removing a single file +async fn test_remove_single_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + let file_path = create_file("test_file.txt", "Test content"); + + add::execute(AddArgs { + pathspec: vec![String::from("test_file.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the file exists + assert!(file_path.exists(), "File should exist before removal"); + + // Remove the file + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_file.txt")], + cached: false, + recursive: false, + }) + .unwrap(); + + // Verify the file was removed from the filesystem + assert!(!file_path.exists(), "File should be removed from filesystem"); + + // Verify file is no longer in the index + let changes = changes_to_be_staged(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as new"); + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as modified"); + assert!(!changes.deleted.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as deleted"); +} + +#[tokio::test] +#[serial] +/// Tests removing a file with --cached flag, which only removes from the index but keeps the file +async fn test_remove_cached() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + let file_path = create_file("test_file.txt", "Test content"); + + add::execute(AddArgs { + pathspec: vec![String::from("test_file.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the file exists + assert!(file_path.exists(), "File should exist before removal"); + + // Remove the file with --cached flag + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_file.txt")], + cached: true, + recursive: false, + }) + .unwrap(); + + // Verify the file still exists in the filesystem + assert!(file_path.exists(), "File should still exist in filesystem"); + + // Verify file appears as new (untracked) in the index + let changes = changes_to_be_staged(); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should appear in changes as new/untracked"); +} + +#[tokio::test] +#[serial] +/// Tests recursive removal of a directory +async fn test_remove_directory_recursive() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a directory with files + let file1 = create_file("test_dir/file1.txt", "File 1 content"); + let file2 = create_file("test_dir/file2.txt", "File 2 content"); + let file3 = create_file("test_dir/subdir/file3.txt", "File 3 content"); + + // Add all files to the index + add::execute(AddArgs { + pathspec: vec![String::from("test_dir")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the directory and files exist + assert!(fs::metadata("test_dir").is_ok(), "Directory should exist"); + assert!(file1.exists(), "File 1 should exist"); + assert!(file2.exists(), "File 2 should exist"); + assert!(file3.exists(), "File 3 should exist"); + + // Remove the directory recursively + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_dir")], + cached: false, + recursive: true, + }) + .unwrap(); + + // Verify the directory and files were removed + assert!(!fs::metadata("test_dir").is_ok(), "Directory should be removed"); + assert!(!file1.exists(), "File 1 should be removed"); + assert!(!file2.exists(), "File 2 should be removed"); + assert!(!file3.exists(), "File 3 should be removed"); + + // Verify files are no longer in the index + let changes = changes_to_be_staged(); + for file in &[file1, file2, file3] { + let file_str = file.to_str().unwrap(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_str), + "File should not appear in changes as new"); + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == file_str), + "File should not appear in changes as modified"); + assert!(!changes.deleted.iter().any(|x| x.to_str().unwrap() == file_str), + "File should not appear in changes as deleted"); + } +} + +#[tokio::test] +#[serial] +/// Tests attempting to remove a directory without -r flag should fail +async fn test_remove_directory_without_recursive() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a directory with files + let file1 = create_file("test_dir/file1.txt", "File 1 content"); + let file2 = create_file("test_dir/file2.txt", "File 2 content"); + + // Add all files to the index + add::execute(AddArgs { + pathspec: vec![String::from("test_dir")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the directory and files exist + assert!(fs::metadata("test_dir").is_ok(), "Directory should exist"); + assert!(file1.exists(), "File 1 should exist"); + assert!(file2.exists(), "File 2 should exist"); + + // Attempt to remove the directory without recursive flag + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_dir")], + cached: false, + recursive: false, + }) + .unwrap(); // This should not error, but it should not remove anything either + + // Verify the directory and files still exist + assert!(fs::metadata("test_dir").is_ok(), "Directory should still exist"); + assert!(file1.exists(), "File 1 should still exist"); + assert!(file2.exists(), "File 2 should still exist"); +} + +#[tokio::test] +#[serial] +/// Tests removing a file that does not exist in the index +async fn test_remove_untracked_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file but don't add it to the index + let file_path = create_file("untracked_file.txt", "Untracked content"); + + // Make sure the file exists + assert!(file_path.exists(), "File should exist"); + + // Attempt to remove the untracked file (should fail/do nothing) + remove::execute(RemoveArgs { + pathspec: vec![String::from("untracked_file.txt")], + cached: false, + recursive: false, + }) + .unwrap(); // Should not panic but should print error + + // Verify the file still exists + assert!(file_path.exists(), "File should still exist"); +} + +#[tokio::test] +#[serial] +/// Tests removing a file that has been modified after being added to the index +async fn test_remove_modified_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + let file_path = create_file("test_file.txt", "Initial content"); + + add::execute(AddArgs { + pathspec: vec![String::from("test_file.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Modify the file + let mut file = fs::OpenOptions::new().write(true).open(&file_path).unwrap(); + file.write_all(b" - Modified").unwrap(); + + // Remove the file + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_file.txt")], + cached: false, + recursive: false, + }) + .unwrap(); + + // Verify the file was removed + assert!(!file_path.exists(), "File should be removed"); + + // Verify file is not in the index + let changes = changes_to_be_staged(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as new"); + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as modified"); + assert!(!changes.deleted.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as deleted"); +} + +#[tokio::test] +#[serial] +/// Tests removing multiple files at once +async fn test_remove_multiple_files() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files + let file1 = create_file("file1.txt", "File 1 content"); + let file2 = create_file("file2.txt", "File 2 content"); + let file3 = create_file("file3.txt", "File 3 content"); + + // Add all files to the index + add::execute(AddArgs { + pathspec: vec![String::from(".")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure all files exist + assert!(file1.exists(), "File 1 should exist"); + assert!(file2.exists(), "File 2 should exist"); + assert!(file3.exists(), "File 3 should exist"); + + // Remove multiple files at once + remove::execute(RemoveArgs { + pathspec: vec![ + String::from("file1.txt"), + String::from("file3.txt"), + ], + cached: false, + recursive: false, + }) + .unwrap(); + + // Verify the specified files were removed + assert!(!file1.exists(), "File 1 should be removed"); + assert!(file2.exists(), "File 2 should still exist"); + assert!(!file3.exists(), "File 3 should be removed"); +} \ No newline at end of file From 8c5bf1135274e5acbfdd1a21bf89acb8ef319b31 Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:17:49 +0800 Subject: [PATCH 02/23] Update libra/tests/command/remove_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/remove_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 5bfe9c017..67302a3ab 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -249,7 +249,7 @@ async fn test_remove_modified_file() { .await; // Modify the file - let mut file = fs::OpenOptions::new().write(true).open(&file_path).unwrap(); + let mut file = fs::OpenOptions::new().write(true).truncate(true).open(&file_path).unwrap(); file.write_all(b" - Modified").unwrap(); // Remove the file From 9b95536026026daff6dc8609755197d210dba85c Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:18:11 +0800 Subject: [PATCH 03/23] Update libra/tests/command/add_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/add_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 3eb54f481..ec12b9e6f 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -136,7 +136,7 @@ async fn test_add_update_flag() { .await; // Modify both files - let mut file1 = fs::OpenOptions::new().write(true).open(tracked_file).unwrap(); + let mut file1 = fs::OpenOptions::new().write(true).truncate(true).open(tracked_file).unwrap(); file1.write_all(b" - Modified").unwrap(); let mut file2 = fs::OpenOptions::new().write(true).open(untracked_file).unwrap(); From 976bb4dc8204b95d35673a0a16575fe13af6e24b Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:18:19 +0800 Subject: [PATCH 04/23] Update libra/tests/command/add_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/add_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index ec12b9e6f..31f490a47 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -139,7 +139,7 @@ async fn test_add_update_flag() { let mut file1 = fs::OpenOptions::new().write(true).truncate(true).open(tracked_file).unwrap(); file1.write_all(b" - Modified").unwrap(); - let mut file2 = fs::OpenOptions::new().write(true).open(untracked_file).unwrap(); + let mut file2 = fs::OpenOptions::new().write(true).truncate(true).open(untracked_file).unwrap(); file2.write_all(b" - Modified").unwrap(); // Execute add command with --update flag From b1b71ad39220aa2de4684e2de4c80c99dac859ee Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:18:42 +0800 Subject: [PATCH 05/23] Update libra/tests/command/diff_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/diff_test.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 019abde83..175988616 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -215,10 +215,7 @@ async fn test_diff_with_pathspec() { create_file("file2.txt", "File 2 content\nLine 2\nLine 3\n"); add::execute(AddArgs { - pathspec: vec![String::from(".")] - .iter() - .map(|s| s.to_string()) - .collect(), + pathspec: vec![String::from(".")], all: false, update: false, verbose: false, From 9db7856ecdbf53f748faf21874ab25589995f276 Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Wed, 30 Jul 2025 17:35:48 +0800 Subject: [PATCH 06/23] =?UTF-8?q?bugFix:=E4=BF=AE=E5=A4=8Dadd=5Ftest.rs?= =?UTF-8?q?=E3=80=81diff=5Ftest.rs=E3=80=81remove=5Ftest.rs=E4=B8=89?= =?UTF-8?q?=E4=B8=AA=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=E7=9A=84Clippy?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E7=9A=84=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/add_test.rs | 32 +++++++++------ libra/tests/command/diff_test.rs | 65 +++++++++++++++++------------- libra/tests/command/remove_test.rs | 6 +++ 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 31f490a47..b88c5fbad 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -21,6 +21,7 @@ async fn test_add_single_file() { pathspec: vec![String::from(file_path)], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -30,7 +31,6 @@ async fn test_add_single_file() { // Verify the file was added to index let changes = changes_to_be_staged(); assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == file_path)); } #[tokio::test] @@ -58,6 +58,7 @@ async fn test_add_multiple_files() { ], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -66,9 +67,9 @@ async fn test_add_multiple_files() { // Verify all files were added to index let changes = changes_to_be_staged(); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); } #[tokio::test] @@ -92,6 +93,7 @@ async fn test_add_all_flag() { pathspec: vec![], all: true, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -100,9 +102,9 @@ async fn test_add_all_flag() { // Verify all files were added to index let changes = changes_to_be_staged(); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); } #[tokio::test] @@ -129,6 +131,7 @@ async fn test_add_update_flag() { pathspec: vec![String::from(tracked_file)], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -147,6 +150,7 @@ async fn test_add_update_flag() { pathspec: vec![String::from(".")], all: false, update: true, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -155,8 +159,8 @@ async fn test_add_update_flag() { // Verify only tracked file was updated let changes = changes_to_be_staged(); - // Tracked file should appear in changes as modified (because it was updated) - assert!(changes.modified.iter().any(|x| x.to_str().unwrap() == tracked_file)); + // Tracked file should not appear in changes (because it was updated in index) + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == tracked_file)); // Untracked file should still be untracked and show as new assert!(changes.new.iter().any(|x| x.to_str().unwrap() == untracked_file)); } @@ -196,6 +200,7 @@ async fn test_add_with_ignore_patterns() { pathspec: vec![String::from(".")], all: true, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -204,10 +209,10 @@ async fn test_add_with_ignore_patterns() { // Verify only non-ignored files were added let changes = changes_to_be_staged(); - // Ignored files should not appear in changes.new - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_file)); - // Directory files should not appear in changes.new - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_dir_file)); + // Ignored files should still show as new (not added due to ignore) + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == ignored_file)); + // Directory files should still show as new (not added due to ignore) + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == ignored_dir_file)); // Non-ignored file should not show as new (was added) assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == tracked_file)); } @@ -230,6 +235,7 @@ async fn test_add_dry_run() { pathspec: vec![String::from(file_path)], all: false, update: false, + refresh: false, verbose: false, dry_run: true, ignore_errors: false, diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 175988616..6d4f7cef6 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -1,7 +1,6 @@ use super::*; use std::fs; use std::io::Write; -use std::path::PathBuf; use libra::command::diff::{self, DiffArgs}; @@ -32,6 +31,7 @@ async fn test_basic_diff() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -40,14 +40,14 @@ async fn test_basic_diff() { // Create initial commit commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify the file modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); @@ -81,6 +81,7 @@ async fn test_diff_staged() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -89,14 +90,14 @@ async fn test_diff_staged() { // Create initial commit commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify the file and stage it modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); @@ -105,6 +106,7 @@ async fn test_diff_staged() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -143,6 +145,7 @@ async fn test_diff_between_commits() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -150,14 +153,14 @@ async fn test_diff_between_commits() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Get the first commit hash let first_commit = Head::current_commit().await.unwrap(); @@ -169,6 +172,7 @@ async fn test_diff_between_commits() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -176,14 +180,14 @@ async fn test_diff_between_commits() { .await; commit::execute(CommitArgs { - message: Some("Second commit".to_string()), + message: "Second commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Get the second commit hash let second_commit = Head::current_commit().await.unwrap(); @@ -218,6 +222,7 @@ async fn test_diff_with_pathspec() { pathspec: vec![String::from(".")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -225,14 +230,14 @@ async fn test_diff_with_pathspec() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify both files modify_file("file1.txt", "File 1 modified\nLine 2\nLine 3 changed\n"); @@ -267,6 +272,7 @@ async fn test_diff_output_to_file() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -274,14 +280,14 @@ async fn test_diff_output_to_file() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify the file modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); @@ -326,6 +332,7 @@ async fn test_diff_algorithms() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -333,14 +340,14 @@ async fn test_diff_algorithms() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Make complex changes to test different algorithms modify_file( diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 67302a3ab..2bca0e6fd 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -31,6 +31,7 @@ async fn test_remove_single_file() { pathspec: vec![String::from("test_file.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -76,6 +77,7 @@ async fn test_remove_cached() { pathspec: vec![String::from("test_file.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -120,6 +122,7 @@ async fn test_remove_directory_recursive() { pathspec: vec![String::from("test_dir")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -176,6 +179,7 @@ async fn test_remove_directory_without_recursive() { pathspec: vec![String::from("test_dir")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -242,6 +246,7 @@ async fn test_remove_modified_file() { pathspec: vec![String::from("test_file.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -291,6 +296,7 @@ async fn test_remove_multiple_files() { pathspec: vec![String::from(".")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, From 654704b9b91db484dfad8eb5e283d8945020d347 Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Thu, 31 Jul 2025 09:28:20 +0800 Subject: [PATCH 07/23] =?UTF-8?q?libra:=E4=B8=BAadd=E3=80=81diff=E3=80=81r?= =?UTF-8?q?emove=E5=91=BD=E4=BB=A4=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/add_test.rs | 8 +-- libra/tests/command/diff_test.rs | 104 +++++++++-------------------- libra/tests/command/remove_test.rs | 75 ++++++++------------- 3 files changed, 65 insertions(+), 122 deletions(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index b88c5fbad..cf193f6f5 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -43,8 +43,8 @@ async fn test_add_multiple_files() { // Create multiple files for i in 1..=3 { - let file_content = format!("File content {}", i); - let file_path = format!("test_file_{}.txt", i); + let file_content = format!("File content {i}"); + let file_path = format!("test_file_{i}.txt"); let mut file = fs::File::create(&file_path).unwrap(); file.write_all(file_content.as_bytes()).unwrap(); } @@ -82,8 +82,8 @@ async fn test_add_all_flag() { // Create multiple files for i in 1..=3 { - let file_content = format!("File content {}", i); - let file_path = format!("test_file_{}.txt", i); + let file_content = format!("File content {i}"); + let file_path = format!("test_file_{i}.txt"); let mut file = fs::File::create(&file_path).unwrap(); file.write_all(file_content.as_bytes()).unwrap(); } diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 6d4f7cef6..01146fb50 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -53,15 +53,10 @@ async fn test_basic_diff() { modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); // Run diff command - diff::execute(DiffArgs { - old: None, - new: None, - staged: false, - pathspec: vec![], - algorithm: Some("histogram".to_string()), - output: None, - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--algorithm", "histogram" + ]).unwrap(); + diff::execute(args).await; // We can't easily capture stdout, so we'll check that the command didn't panic } @@ -117,15 +112,10 @@ async fn test_diff_staged() { modify_file("file1.txt", "Modified content again\nLine 2\nLine 3 changed again\n"); // Run diff command with --staged flag - diff::execute(DiffArgs { - old: None, - new: None, - staged: true, - pathspec: vec![], - algorithm: Some("histogram".to_string()), - output: None, - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--staged", "--algorithm", "histogram" + ]).unwrap(); + diff::execute(args).await; // The command should complete without panicking } @@ -193,15 +183,10 @@ async fn test_diff_between_commits() { let second_commit = Head::current_commit().await.unwrap(); // Run diff command comparing the two commits - diff::execute(DiffArgs { - old: Some(first_commit.to_string()), - new: Some(second_commit.to_string()), - staged: false, - pathspec: vec![], - algorithm: Some("histogram".to_string()), - output: None, - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--old", &first_commit.to_string(), "--new", &second_commit.to_string(), "--algorithm", "histogram" + ]).unwrap(); + diff::execute(args).await; // The command should complete without panicking } @@ -244,15 +229,10 @@ async fn test_diff_with_pathspec() { modify_file("file2.txt", "File 2 modified\nLine 2\nLine 3 changed\n"); // Run diff command with specific file path - diff::execute(DiffArgs { - old: None, - new: None, - staged: false, - pathspec: vec![String::from("file1.txt")], - algorithm: Some("histogram".to_string()), - output: None, - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--algorithm", "histogram", "file1.txt" + ]).unwrap(); + diff::execute(args).await; // The command should complete without panicking } @@ -296,15 +276,10 @@ async fn test_diff_output_to_file() { let output_file = "diff_output.txt"; // Run diff command with output to file - diff::execute(DiffArgs { - old: None, - new: None, - staged: false, - pathspec: vec![], - algorithm: Some("histogram".to_string()), - output: Some(output_file.to_string()), - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--algorithm", "histogram", "--output", output_file + ]).unwrap(); + diff::execute(args).await; // Verify the output file exists assert!(fs::metadata(output_file).is_ok(), "Output file should exist"); @@ -356,37 +331,22 @@ async fn test_diff_algorithms() { ); // Test histogram algorithm - diff::execute(DiffArgs { - old: None, - new: None, - staged: false, - pathspec: vec![], - algorithm: Some("histogram".to_string()), - output: Some("histogram_diff.txt".to_string()), - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--algorithm", "histogram", "--output", "histogram_diff.txt" + ]).unwrap(); + diff::execute(args).await; // Test myers algorithm - diff::execute(DiffArgs { - old: None, - new: None, - staged: false, - pathspec: vec![], - algorithm: Some("myers".to_string()), - output: Some("myers_diff.txt".to_string()), - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--algorithm", "myers", "--output", "myers_diff.txt" + ]).unwrap(); + diff::execute(args).await; // Test myersMinimal algorithm - diff::execute(DiffArgs { - old: None, - new: None, - staged: false, - pathspec: vec![], - algorithm: Some("myersMinimal".to_string()), - output: Some("myersMinimal_diff.txt".to_string()), - }) - .await; + let args = DiffArgs::try_parse_from([ + "diff", "--algorithm", "myersMinimal", "--output", "myersMinimal_diff.txt" + ]).unwrap(); + diff::execute(args).await; // Verify all output files exist assert!(fs::metadata("histogram_diff.txt").is_ok(), "Histogram output file should exist"); diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 2bca0e6fd..aff536643 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -42,12 +42,10 @@ async fn test_remove_single_file() { assert!(file_path.exists(), "File should exist before removal"); // Remove the file - remove::execute(RemoveArgs { - pathspec: vec![String::from("test_file.txt")], - cached: false, - recursive: false, - }) - .unwrap(); + let args = RemoveArgs::try_parse_from([ + "remove", "test_file.txt" + ]).unwrap(); + remove::execute(args).unwrap(); // Verify the file was removed from the filesystem assert!(!file_path.exists(), "File should be removed from filesystem"); @@ -88,12 +86,10 @@ async fn test_remove_cached() { assert!(file_path.exists(), "File should exist before removal"); // Remove the file with --cached flag - remove::execute(RemoveArgs { - pathspec: vec![String::from("test_file.txt")], - cached: true, - recursive: false, - }) - .unwrap(); + let args = RemoveArgs::try_parse_from([ + "remove", "--cached", "test_file.txt" + ]).unwrap(); + remove::execute(args).unwrap(); // Verify the file still exists in the filesystem assert!(file_path.exists(), "File should still exist in filesystem"); @@ -136,15 +132,13 @@ async fn test_remove_directory_recursive() { assert!(file3.exists(), "File 3 should exist"); // Remove the directory recursively - remove::execute(RemoveArgs { - pathspec: vec![String::from("test_dir")], - cached: false, - recursive: true, - }) - .unwrap(); + let args = RemoveArgs::try_parse_from([ + "remove", "--recursive", "test_dir" + ]).unwrap(); + remove::execute(args).unwrap(); // Verify the directory and files were removed - assert!(!fs::metadata("test_dir").is_ok(), "Directory should be removed"); + assert!(fs::metadata("test_dir").is_err(), "Directory should be removed"); assert!(!file1.exists(), "File 1 should be removed"); assert!(!file2.exists(), "File 2 should be removed"); assert!(!file3.exists(), "File 3 should be removed"); @@ -192,12 +186,10 @@ async fn test_remove_directory_without_recursive() { assert!(file2.exists(), "File 2 should exist"); // Attempt to remove the directory without recursive flag - remove::execute(RemoveArgs { - pathspec: vec![String::from("test_dir")], - cached: false, - recursive: false, - }) - .unwrap(); // This should not error, but it should not remove anything either + let args = RemoveArgs::try_parse_from([ + "remove", "test_dir" + ]).unwrap(); + remove::execute(args).unwrap(); // This should not error, but it should not remove anything either // Verify the directory and files still exist assert!(fs::metadata("test_dir").is_ok(), "Directory should still exist"); @@ -220,12 +212,10 @@ async fn test_remove_untracked_file() { assert!(file_path.exists(), "File should exist"); // Attempt to remove the untracked file (should fail/do nothing) - remove::execute(RemoveArgs { - pathspec: vec![String::from("untracked_file.txt")], - cached: false, - recursive: false, - }) - .unwrap(); // Should not panic but should print error + let args = RemoveArgs::try_parse_from([ + "remove", "untracked_file.txt" + ]).unwrap(); + remove::execute(args).unwrap(); // Should not panic but should print error // Verify the file still exists assert!(file_path.exists(), "File should still exist"); @@ -258,12 +248,10 @@ async fn test_remove_modified_file() { file.write_all(b" - Modified").unwrap(); // Remove the file - remove::execute(RemoveArgs { - pathspec: vec![String::from("test_file.txt")], - cached: false, - recursive: false, - }) - .unwrap(); + let args = RemoveArgs::try_parse_from([ + "remove", "test_file.txt" + ]).unwrap(); + remove::execute(args).unwrap(); // Verify the file was removed assert!(!file_path.exists(), "File should be removed"); @@ -309,15 +297,10 @@ async fn test_remove_multiple_files() { assert!(file3.exists(), "File 3 should exist"); // Remove multiple files at once - remove::execute(RemoveArgs { - pathspec: vec![ - String::from("file1.txt"), - String::from("file3.txt"), - ], - cached: false, - recursive: false, - }) - .unwrap(); + let args = RemoveArgs::try_parse_from([ + "remove", "file1.txt", "file3.txt" + ]).unwrap(); + remove::execute(args).unwrap(); // Verify the specified files were removed assert!(!file1.exists(), "File 1 should be removed"); From 13d839f8c78b0447b70719d21ed001ad927c3e7d Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:35:37 +0800 Subject: [PATCH 08/23] Update libra/tests/command/add_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/add_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index cf193f6f5..821ff34fb 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -30,6 +30,7 @@ async fn test_add_single_file() { // Verify the file was added to index let changes = changes_to_be_staged(); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == file_path)); assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); } From 66fb59b7b435ed5e1a2b5cf515a6682dd81705cc Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:35:49 +0800 Subject: [PATCH 09/23] Update libra/tests/command/add_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/add_test.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 821ff34fb..52a589527 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -68,9 +68,9 @@ async fn test_add_multiple_files() { // Verify all files were added to index let changes = changes_to_be_staged(); - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); } #[tokio::test] From c65258ca78a838eff29f39f018584322ffb04d79 Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:35:55 +0800 Subject: [PATCH 10/23] Update libra/tests/command/add_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/add_test.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 52a589527..cc6a7f537 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -103,9 +103,9 @@ async fn test_add_all_flag() { // Verify all files were added to index let changes = changes_to_be_staged(); - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); } #[tokio::test] From 1b312cfe94faf05bed677d4b49f36d8a8394bc8e Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Thu, 31 Jul 2025 17:14:39 +0800 Subject: [PATCH 11/23] =?UTF-8?q?bugFix:=E4=BF=AE=E5=A4=8Dadd=5Ftest?= =?UTF-8?q?=E3=80=81remove=5Ftest=E3=80=81diff=5Ftest=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9A=84=E7=9B=B8=E5=BA=94ClipyCheck?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/add_test.rs | 2 +- libra/tests/command/diff_test.rs | 16 ++++++++-------- libra/tests/command/remove_test.rs | 14 +++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index cf193f6f5..d942b5ec5 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -30,7 +30,7 @@ async fn test_add_single_file() { // Verify the file was added to index let changes = changes_to_be_staged(); - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); } #[tokio::test] diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 01146fb50..bb811d2fa 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -53,7 +53,7 @@ async fn test_basic_diff() { modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); // Run diff command - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -112,7 +112,7 @@ async fn test_diff_staged() { modify_file("file1.txt", "Modified content again\nLine 2\nLine 3 changed again\n"); // Run diff command with --staged flag - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--staged", "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -183,7 +183,7 @@ async fn test_diff_between_commits() { let second_commit = Head::current_commit().await.unwrap(); // Run diff command comparing the two commits - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--old", &first_commit.to_string(), "--new", &second_commit.to_string(), "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -229,7 +229,7 @@ async fn test_diff_with_pathspec() { modify_file("file2.txt", "File 2 modified\nLine 2\nLine 3 changed\n"); // Run diff command with specific file path - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram", "file1.txt" ]).unwrap(); diff::execute(args).await; @@ -276,7 +276,7 @@ async fn test_diff_output_to_file() { let output_file = "diff_output.txt"; // Run diff command with output to file - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram", "--output", output_file ]).unwrap(); diff::execute(args).await; @@ -331,19 +331,19 @@ async fn test_diff_algorithms() { ); // Test histogram algorithm - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram", "--output", "histogram_diff.txt" ]).unwrap(); diff::execute(args).await; // Test myers algorithm - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "myers", "--output", "myers_diff.txt" ]).unwrap(); diff::execute(args).await; // Test myersMinimal algorithm - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "myersMinimal", "--output", "myersMinimal_diff.txt" ]).unwrap(); diff::execute(args).await; diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index aff536643..d6eeef32d 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -42,7 +42,7 @@ async fn test_remove_single_file() { assert!(file_path.exists(), "File should exist before removal"); // Remove the file - let args = RemoveArgs::try_parse_from([ + let args = RemoveArgs::parse_from([ "remove", "test_file.txt" ]).unwrap(); remove::execute(args).unwrap(); @@ -86,7 +86,7 @@ async fn test_remove_cached() { assert!(file_path.exists(), "File should exist before removal"); // Remove the file with --cached flag - let args = RemoveArgs::try_parse_from([ + let args = RemoveArgs::parse_from([ "remove", "--cached", "test_file.txt" ]).unwrap(); remove::execute(args).unwrap(); @@ -132,7 +132,7 @@ async fn test_remove_directory_recursive() { assert!(file3.exists(), "File 3 should exist"); // Remove the directory recursively - let args = RemoveArgs::try_parse_from([ + let args = RemoveArgs::parse_from([ "remove", "--recursive", "test_dir" ]).unwrap(); remove::execute(args).unwrap(); @@ -186,7 +186,7 @@ async fn test_remove_directory_without_recursive() { assert!(file2.exists(), "File 2 should exist"); // Attempt to remove the directory without recursive flag - let args = RemoveArgs::try_parse_from([ + let args = RemoveArgs::parse_from([ "remove", "test_dir" ]).unwrap(); remove::execute(args).unwrap(); // This should not error, but it should not remove anything either @@ -212,7 +212,7 @@ async fn test_remove_untracked_file() { assert!(file_path.exists(), "File should exist"); // Attempt to remove the untracked file (should fail/do nothing) - let args = RemoveArgs::try_parse_from([ + let args = RemoveArgs::parse_from([ "remove", "untracked_file.txt" ]).unwrap(); remove::execute(args).unwrap(); // Should not panic but should print error @@ -248,7 +248,7 @@ async fn test_remove_modified_file() { file.write_all(b" - Modified").unwrap(); // Remove the file - let args = RemoveArgs::try_parse_from([ + let args = RemoveArgs::parse_from([ "remove", "test_file.txt" ]).unwrap(); remove::execute(args).unwrap(); @@ -297,7 +297,7 @@ async fn test_remove_multiple_files() { assert!(file3.exists(), "File 3 should exist"); // Remove multiple files at once - let args = RemoveArgs::try_parse_from([ + let args = RemoveArgs::parse_from([ "remove", "file1.txt", "file3.txt" ]).unwrap(); remove::execute(args).unwrap(); From 37530149553d2231b8dfd0a92d481a24730ebbee Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Thu, 31 Jul 2025 17:50:26 +0800 Subject: [PATCH 12/23] =?UTF-8?q?bugFix:=E4=BF=AE=E5=A4=8DClipyCheck?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/diff_test.rs | 16 ++++++++-------- libra/tests/command/remove_test.rs | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index bb811d2fa..01146fb50 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -53,7 +53,7 @@ async fn test_basic_diff() { modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); // Run diff command - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -112,7 +112,7 @@ async fn test_diff_staged() { modify_file("file1.txt", "Modified content again\nLine 2\nLine 3 changed again\n"); // Run diff command with --staged flag - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--staged", "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -183,7 +183,7 @@ async fn test_diff_between_commits() { let second_commit = Head::current_commit().await.unwrap(); // Run diff command comparing the two commits - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--old", &first_commit.to_string(), "--new", &second_commit.to_string(), "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -229,7 +229,7 @@ async fn test_diff_with_pathspec() { modify_file("file2.txt", "File 2 modified\nLine 2\nLine 3 changed\n"); // Run diff command with specific file path - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--algorithm", "histogram", "file1.txt" ]).unwrap(); diff::execute(args).await; @@ -276,7 +276,7 @@ async fn test_diff_output_to_file() { let output_file = "diff_output.txt"; // Run diff command with output to file - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--algorithm", "histogram", "--output", output_file ]).unwrap(); diff::execute(args).await; @@ -331,19 +331,19 @@ async fn test_diff_algorithms() { ); // Test histogram algorithm - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--algorithm", "histogram", "--output", "histogram_diff.txt" ]).unwrap(); diff::execute(args).await; // Test myers algorithm - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--algorithm", "myers", "--output", "myers_diff.txt" ]).unwrap(); diff::execute(args).await; // Test myersMinimal algorithm - let args = DiffArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "diff", "--algorithm", "myersMinimal", "--output", "myersMinimal_diff.txt" ]).unwrap(); diff::execute(args).await; diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 0aa4dbe3c..6831b0cfc 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -42,7 +42,7 @@ async fn test_remove_single_file() { assert!(file_path.exists(), "File should exist before removal"); // Remove the file - let args = RemoveArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "remove", "test_file.txt" ]).unwrap(); remove::execute(args).unwrap(); @@ -86,7 +86,7 @@ async fn test_remove_cached() { assert!(file_path.exists(), "File should exist before removal"); // Remove the file with --cached flag - let args = RemoveArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "remove", "--cached", "test_file.txt" ]).unwrap(); remove::execute(args).unwrap(); @@ -132,7 +132,7 @@ async fn test_remove_directory_recursive() { assert!(file3.exists(), "File 3 should exist"); // Remove the directory recursively - let args = RemoveArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "remove", "--recursive", "test_dir" ]).unwrap(); remove::execute(args).unwrap(); @@ -186,7 +186,7 @@ async fn test_remove_directory_without_recursive() { assert!(file2.exists(), "File 2 should exist"); // Attempt to remove the directory without recursive flag - let args = RemoveArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "remove", "test_dir" ]).unwrap(); remove::execute(args).unwrap(); // This should not error, but it should not remove anything either @@ -212,7 +212,7 @@ async fn test_remove_untracked_file() { assert!(file_path.exists(), "File should exist"); // Attempt to remove the untracked file (should fail/do nothing) - let args = RemoveArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "remove", "untracked_file.txt" ]).unwrap(); remove::execute(args).unwrap(); // Should not panic but should print error @@ -248,7 +248,7 @@ async fn test_remove_modified_file() { file.write_all(b" - Modified").unwrap(); // Remove the file - let args = RemoveArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "remove", "test_file.txt" ]).unwrap(); remove::execute(args).unwrap(); @@ -297,7 +297,7 @@ async fn test_remove_multiple_files() { assert!(file3.exists(), "File 3 should exist"); // Remove multiple files at once - let args = RemoveArgs::parse_from([ + let args = DiffArgs::try_parse_from([ "remove", "file1.txt", "file3.txt" ]).unwrap(); remove::execute(args).unwrap(); From a501bd6c7e4d1386e30dd380034fa04751672d59 Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Fri, 1 Aug 2025 09:32:50 +0800 Subject: [PATCH 13/23] =?UTF-8?q?bugFix:=E7=BB=A7=E7=BB=AD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8DClipyCheck=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/diff_test.rs | 16 ++++++++-------- libra/tests/command/remove_test.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 01146fb50..bb811d2fa 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -53,7 +53,7 @@ async fn test_basic_diff() { modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); // Run diff command - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -112,7 +112,7 @@ async fn test_diff_staged() { modify_file("file1.txt", "Modified content again\nLine 2\nLine 3 changed again\n"); // Run diff command with --staged flag - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--staged", "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -183,7 +183,7 @@ async fn test_diff_between_commits() { let second_commit = Head::current_commit().await.unwrap(); // Run diff command comparing the two commits - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--old", &first_commit.to_string(), "--new", &second_commit.to_string(), "--algorithm", "histogram" ]).unwrap(); diff::execute(args).await; @@ -229,7 +229,7 @@ async fn test_diff_with_pathspec() { modify_file("file2.txt", "File 2 modified\nLine 2\nLine 3 changed\n"); // Run diff command with specific file path - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram", "file1.txt" ]).unwrap(); diff::execute(args).await; @@ -276,7 +276,7 @@ async fn test_diff_output_to_file() { let output_file = "diff_output.txt"; // Run diff command with output to file - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram", "--output", output_file ]).unwrap(); diff::execute(args).await; @@ -331,19 +331,19 @@ async fn test_diff_algorithms() { ); // Test histogram algorithm - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "histogram", "--output", "histogram_diff.txt" ]).unwrap(); diff::execute(args).await; // Test myers algorithm - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "myers", "--output", "myers_diff.txt" ]).unwrap(); diff::execute(args).await; // Test myersMinimal algorithm - let args = DiffArgs::try_parse_from([ + let args = DiffArgs::parse_from([ "diff", "--algorithm", "myersMinimal", "--output", "myersMinimal_diff.txt" ]).unwrap(); diff::execute(args).await; diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 6831b0cfc..ae1048360 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -2,8 +2,8 @@ use super::*; use std::fs; use std::io::Write; use std::path::PathBuf; +use libra::command::diff::DiffArgs; -use libra::command::remove::{self, RemoveArgs}; /// Helper function to create a file with content fn create_file(path: &str, content: &str) -> PathBuf { From cdf3bc81c5070fb82636771ec57b8a94023eb4e8 Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Fri, 1 Aug 2025 09:54:48 +0800 Subject: [PATCH 14/23] =?UTF-8?q?bugFix:=E7=BB=A7=E7=BB=AD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8DClipyCheck=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- .gitignore | 3 + aria/contents/docs/libra/command/rm/index.mdx | 3 + libra/Cargo.toml | 2 + libra/src/command/remove.rs | 50 ++- libra/tests/command/fetch_test.rs | 109 +++++++ libra/tests/command/lfs_test.rs | 106 ++++++ libra/tests/command/merge_test.rs | 186 +++++++++++ monobean/Cargo.toml | 4 + monobean/resources/css/common.css | 14 +- monobean/resources/gschemas.compiled | Bin 0 -> 2764 bytes monobean/resources/gtk/code_page.ui | 305 ++++++++++++++---- monobean/resources/gtk/file_row.ui | 6 + monobean/resources/gtk/mega_tab.ui | 214 ------------ monobean/resources/gtk/mega_tab_window.ui | 226 +++++++++++++ monobean/resources/gtk/window.ui | 218 ++++++++++--- .../symbolic/apps/monobean-css-symbolic.svg | 4 + .../apps/monobean-gitFile-symbolic.svg | 4 + .../apps/monobean-historybtn-symbolic.svg | 9 + .../symbolic/apps/monobean-json-symbolic.svg | 48 +++ .../apps/monobean-markdown-symbolic.svg | 4 + .../apps/monobean-picture-symbolic.svg | 2 + .../symbolic/apps/monobean-repo-symbolic.svg | 2 + .../symbolic/apps/monobean-rss-symbolic.svg | 6 + .../symbolic/apps/monobean-rust-symbolic.svg | 7 + .../apps/monobean-settingFile-symbolic.svg | 4 + .../apps/monobean-url-clone-symbolic.svg | 6 + ....Web3Infrastructure.Monobean.gresource.xml | 14 +- monobean/src/application.rs | 16 +- monobean/src/components/code_page.rs | 167 +++++++++- monobean/src/components/file_tree.rs | 42 ++- monobean/src/components/hello_page.rs | 140 ++++---- monobean/src/components/history_list.rs | 36 +++ monobean/src/components/mega_tab.rs | 26 +- monobean/src/components/mod.rs | 1 + monobean/src/core/mega_core.rs | 3 - monobean/src/window.rs | 77 ++++- .../web/components/Issues/IssueDetailPage.tsx | 123 ++++--- .../web/components/Issues/IssuesContent.tsx | 38 ++- .../components/Issues/utils/sideEffect.tsx | 74 ++++- .../web/components/Labels/NewLabelDialog.tsx | 114 +++++++ moon/apps/web/components/MrView/LabelItem.tsx | 42 +++ .../web/components/MrView/TimelineItems.tsx | 13 +- moon/apps/web/components/MrView/index.tsx | 6 +- moon/apps/web/hooks/useGetLabelList.ts | 44 +++ moon/apps/web/hooks/usePostIssueLabels.ts | 9 + moon/apps/web/hooks/usePostLabelNew.ts | 9 + moon/apps/web/hooks/usePostMRLabels.ts | 9 + moon/apps/web/pages/[org]/labels/index.tsx | 152 +++++---- moon/apps/web/pages/[org]/mr/[link]/[id].tsx | 45 ++- moon/apps/web/utils/getFontColor.ts | 11 + 50 files changed, 2193 insertions(+), 560 deletions(-) create mode 100644 monobean/resources/gschemas.compiled delete mode 100644 monobean/resources/gtk/mega_tab.ui create mode 100644 monobean/resources/gtk/mega_tab_window.ui create mode 100644 monobean/resources/icons/symbolic/apps/monobean-css-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-gitFile-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-historybtn-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-json-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-markdown-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-picture-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-repo-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-rss-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-rust-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-settingFile-symbolic.svg create mode 100644 monobean/resources/icons/symbolic/apps/monobean-url-clone-symbolic.svg create mode 100644 monobean/src/components/history_list.rs create mode 100644 moon/apps/web/components/Labels/NewLabelDialog.tsx create mode 100644 moon/apps/web/components/MrView/LabelItem.tsx create mode 100644 moon/apps/web/hooks/useGetLabelList.ts create mode 100644 moon/apps/web/hooks/usePostIssueLabels.ts create mode 100644 moon/apps/web/hooks/usePostLabelNew.ts create mode 100644 moon/apps/web/hooks/usePostMRLabels.ts create mode 100644 moon/apps/web/utils/getFontColor.ts diff --git a/.gitignore b/.gitignore index eeff576d1..ca19e2ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ Cargo.lock .DS_Store **/*.dylib +.cursor +.md + # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. .VSCodeCounter diff --git a/aria/contents/docs/libra/command/rm/index.mdx b/aria/contents/docs/libra/command/rm/index.mdx index 32ec0fa10..69694fbe8 100644 --- a/aria/contents/docs/libra/command/rm/index.mdx +++ b/aria/contents/docs/libra/command/rm/index.mdx @@ -26,3 +26,6 @@ that.) - `-r`, `--recursive`
Allow recursive removal when a leading directory name is given. + +- `-f`, `--force`
+ Force removal even if the specified paths are not tracked. This skips validation checks and deletes files or directories regardless of their index status. diff --git a/libra/Cargo.toml b/libra/Cargo.toml index 400e9a115..c373376d7 100644 --- a/libra/Cargo.toml +++ b/libra/Cargo.toml @@ -52,6 +52,8 @@ tracing-subscriber = { workspace = true } wax = { workspace = true } url = { workspace = true } ignore = { workspace = true } +tempfile = { workspace = true } +serial_test = { workspace = true } [target.'cfg(unix)'.dependencies] # only on Unix pager = { workspace = true } diff --git a/libra/src/command/remove.rs b/libra/src/command/remove.rs index fea07ccd7..4d954090a 100644 --- a/libra/src/command/remove.rs +++ b/libra/src/command/remove.rs @@ -13,13 +13,16 @@ use mercury::internal::index::Index; #[derive(Parser, Debug)] pub struct RemoveArgs { /// file or dir to remove - pathspec: Vec, + pub pathspec: Vec, /// whether to remove from index #[clap(long)] - cached: bool, + pub cached: bool, /// indicate recursive remove dir #[clap(short, long)] - recursive: bool, + pub recursive: bool, + /// force removal, skip validation + #[clap(short, long)] + pub force: bool, } pub fn execute(args: RemoveArgs) -> Result<(), GitError> { @@ -28,11 +31,13 @@ pub fn execute(args: RemoveArgs) -> Result<(), GitError> { } let idx_file = path::index(); let mut index = Index::load(&idx_file)?; - // check if pathspec is all in index - if !validate_pathspec(&args.pathspec, &index) { + + // check if pathspec is all in index (skip if force is enabled) + if !args.force && !validate_pathspec(&args.pathspec, &index) { return Ok(()); } - let dirs = get_dirs(&args.pathspec, &index); + + let dirs = get_dirs(&args.pathspec, &index, args.force); if !dirs.is_empty() && !args.recursive { println!( "fatal: not removing '{}' recursively without -r", @@ -44,6 +49,7 @@ pub fn execute(args: RemoveArgs) -> Result<(), GitError> { for path_str in args.pathspec.iter() { let path = PathBuf::from(path_str); let path_wd = path.to_workdir().to_string_or_panic(); + if dirs.contains(path_str) { // dir let removed = index.remove_dir_files(&path_wd); @@ -56,8 +62,20 @@ pub fn execute(args: RemoveArgs) -> Result<(), GitError> { } } else { // file - index.remove(&path_wd, 0); - println!("rm '{}'", path_wd.bright_green()); + if args.force { + // In force mode, remove from index if tracked, otherwise just delete from filesystem + if index.tracked(&path_wd, 0) { + index.remove(&path_wd, 0); + println!("rm '{}'", path_wd.bright_green()); + } else { + println!("rm '{}'", path_wd.bright_yellow()); + } + } else { + // Normal mode - only remove if tracked + index.remove(&path_wd, 0); + println!("rm '{}'", path_wd.bright_green()); + } + if !args.cached { fs::remove_file(&path)?; } @@ -90,14 +108,22 @@ fn validate_pathspec(pathspec: &[String], index: &Index) -> bool { } /// run after `validate_pathspec` -fn get_dirs(pathspec: &[String], index: &Index) -> Vec { +fn get_dirs(pathspec: &[String], index: &Index, force: bool) -> Vec { let mut dirs = Vec::new(); for path_str in pathspec.iter() { let path = PathBuf::from(path_str); let path_wd = path.to_workdir().to_string_or_panic(); - // valid but not tracked, means a dir - if !index.tracked(&path_wd, 0) { - dirs.push(path_str.clone()); + + if force { + // In force mode, check if the path exists and is a directory + if path.exists() && path.is_dir() { + dirs.push(path_str.clone()); + } + } else { + // valid but not tracked, means a dir + if !index.tracked(&path_wd, 0) { + dirs.push(path_str.clone()); + } } } dirs diff --git a/libra/tests/command/fetch_test.rs b/libra/tests/command/fetch_test.rs index 8b1378917..5cb255bef 100644 --- a/libra/tests/command/fetch_test.rs +++ b/libra/tests/command/fetch_test.rs @@ -1 +1,110 @@ +use std::process::Command; +use std::time::Duration; +use tempfile::TempDir; +use tokio::process::Command as TokioCommand; +use tokio::time::timeout; +/// Helper function: Initialize a temporary Libra repository +fn init_temp_repo() -> TempDir { + let temp_dir = tempfile::tempdir().expect("Failed to create temporary directory"); + let temp_path = temp_dir.path(); + + eprintln!("Temporary directory created at: {temp_path:?}"); + assert!(temp_path.is_dir(), "Temporary path is not a valid directory"); + + let output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .arg("init") + .output() + .expect("Failed to execute libra binary"); + + if !output.status.success() { + panic!( + "Failed to initialize libra repository: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + eprintln!("Initialized libra repo at: {temp_path:?}"); + temp_dir +} + +#[tokio::test] +/// Test fetching from an invalid remote repository with timeout +async fn test_fetch_invalid_remote() { + let temp_repo = init_temp_repo(); + let temp_path = temp_repo.path(); + + eprintln!("Starting test: fetch from invalid remote"); + + // Configure an invalid remote repository + eprintln!("Adding invalid remote: https://invalid-url.example/repo.git"); + let remote_output = TokioCommand::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["remote", "add", "origin", "https://invalid-url.example/repo.git"]) + .output() + .await + .expect("Failed to add remote"); + + assert!( + remote_output.status.success(), + "Failed to add remote: {}", + String::from_utf8_lossy(&remote_output.stderr) + ); + + // Set upstream branch + eprintln!("Setting upstream to origin/main"); + let branch_output = TokioCommand::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["branch", "--set-upstream-to", "origin/main"]) + .output() + .await + .expect("Failed to set upstream branch"); + + assert!( + branch_output.status.success(), + "Failed to set upstream: {}", + String::from_utf8_lossy(&branch_output.stderr) + ); + + // Attempt to fetch with 15-second timeout to avoid hanging CI + eprintln!("Attempting 'libra fetch' with 15s timeout..."); + let fetch_result = timeout(Duration::from_secs(15), async { + TokioCommand::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .arg("fetch") + .output() + .await + }) + .await; + + match fetch_result { + // Timeout occurred — this is expected for unreachable remotes + Err(_) => { + eprintln!("Fetch timed out after 15 seconds — expected for invalid remote"); + } + // Command completed within timeout + Ok(Ok(output)) => { + + eprintln!("Fetch completed (status: {:?})", output.status); + assert!( + !output.status.success(), + "Fetch should fail when remote is unreachable" + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !stderr.trim().is_empty(), + "Expected error message in stderr, but was empty" + ); + + eprintln!("Fetch failed as expected: {stderr}"); + } + // Failed to start the command + Ok(Err(e)) => { + + panic!("Failed to run 'libra fetch' command: {e}"); + } + } + + eprintln!("test_fetch_invalid_remote passed"); +} diff --git a/libra/tests/command/lfs_test.rs b/libra/tests/command/lfs_test.rs index 8b1378917..2e61d1bc1 100644 --- a/libra/tests/command/lfs_test.rs +++ b/libra/tests/command/lfs_test.rs @@ -1 +1,107 @@ +use std::process::Command; +use tempfile::TempDir; +/// Helper function: Initialize a temporary Libra repository +fn init_temp_repo() -> TempDir { + let temp_dir = tempfile::tempdir().expect("Failed to create temporary directory"); + let temp_path = temp_dir.path(); + + // Variables can be used directly in the `format!` string + // FIX: Removed {:?} and added variable directly with formatting + println!("Temporary directory created at: {temp_path:?}"); + assert!(temp_path.is_dir(), "Temporary path is not a valid directory"); + + // Using env!("CARGO_BIN_EXE_libra") to get the path to the libra executable + let output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .arg("init") + .output() + .expect("Failed to execute libra binary"); + + if !output.status.success() { + panic!( + "Failed to initialize libra repository: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + temp_dir +} + +#[tokio::test] +/// Test track/untrack path rule management +async fn test_lfs_track_untrack() { + let temp_repo = init_temp_repo(); + let temp_path = temp_repo.path(); + + // Add a path rule + // FIX: Removed & from args + let track_output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["lfs", "track", "*.txt"]) // Changed &[...] to [...] + .output() + .expect("Failed to track path"); + assert!( + track_output.status.success(), + "Failed to track path: {}", + String::from_utf8_lossy(&track_output.stderr) + ); + + // Remove a path rule + // FIX: Removed & from args + let untrack_output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["lfs", "untrack", "*.txt"]) // Changed &[...] to [...] + .output() + .expect("Failed to untrack path"); + assert!( + untrack_output.status.success(), + "Failed to untrack path: {}", + String::from_utf8_lossy(&untrack_output.stderr) + ); +} + +#[tokio::test] +/// Test file status viewing +async fn test_lfs_ls_files() { + let temp_repo = init_temp_repo(); + let temp_path = temp_repo.path(); + + // Create a test file and add it to LFS + let file_path = temp_path.join("tracked_file.txt"); + std::fs::write(&file_path, "Tracked content").expect("Failed to create tracked file"); + + // FIX: Removed & from args + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["lfs", "track", "*.txt"]) // Changed &[...] to [...] + .output() + .expect("Failed to track file"); + + // FIX: Removed & from args + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["add", "tracked_file.txt"]) // Changed &[...] to [...] + .output() + .expect("Failed to add file to LFS"); + + // View file status + // FIX: Removed & from args + let ls_files_output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["lfs", "ls-files"]) // Changed &[...] to [...] + .output() + .expect("Failed to list LFS files"); + assert!( + ls_files_output.status.success(), + "Failed to list LFS files: {}", + String::from_utf8_lossy(&ls_files_output.stderr) + ); + + let stdout = String::from_utf8_lossy(&ls_files_output.stdout); + // FIX: Variables can be used directly in the `format!` string + assert!( + stdout.contains("tracked_file.txt"), + "LFS file list does not contain expected file: {stdout}", // Changed {} to direct variable embed + ); +} diff --git a/libra/tests/command/merge_test.rs b/libra/tests/command/merge_test.rs index 8b1378917..22564decb 100644 --- a/libra/tests/command/merge_test.rs +++ b/libra/tests/command/merge_test.rs @@ -1 +1,187 @@ +use std::process::Command; +use tempfile::TempDir; + +/// Helper function: Initialize a temporary Libra repository +fn init_temp_repo() -> TempDir { + let temp_dir = tempfile::tempdir().expect("Failed to create temporary directory"); + let temp_path = temp_dir.path(); + + println!("Temporary directory created at: {temp_path:?}"); + assert!(temp_path.is_dir(), "Temporary path is not a valid directory"); + + let output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .arg("init") + .output() + .expect("Failed to execute libra binary"); + + if !output.status.success() { + panic!( + "Failed to initialize libra repository: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + temp_dir +} + +#[tokio::test] +/// Test fast-forward merge of local branches +async fn test_merge_fast_forward() { + let temp_repo = init_temp_repo(); + let temp_path = temp_repo.path(); + + // Create and switch to the feature branch + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["branch", "feature"]) + .output() + .expect("Failed to create branch"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["checkout", "feature"]) + .output() + .expect("Failed to checkout branch"); + + // Commit changes on the feature branch + let file_path = temp_path.join("file.txt"); + std::fs::write(&file_path, "Feature content").expect("Failed to write file"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["add", "."]) + .output() + .expect("Failed to add file"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["commit", "-m", "Add feature content"]) + .output() + .expect("Failed to commit"); + + // Switch back to the main branch and perform fast-forward merge + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["checkout", "main"]) + .output() + .expect("Failed to checkout main branch"); + + let merge_output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["merge", "feature"]) + .output() + .expect("Failed to merge branch"); + assert!( + merge_output.status.success(), + "Fast-forward merge failed: {}", + String::from_utf8_lossy(&merge_output.stderr) + ); +} + + +#[tokio::test] +/// Test merging a remote branch +async fn test_merge_remote_branch() { + let temp_repo = init_temp_repo(); + let temp_path = temp_repo.path(); + + // Simulate adding a remote branch + // FIX: Remove & + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["remote", "add", "origin", "https://example.com/repo.git"]) + .output() + .expect("Failed to add remote"); + + // Merge the remote branch + + let merge_output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["merge", "origin/feature"]) + .output() + .expect("Failed to merge remote branch"); + assert!( + merge_output.status.success(), + "Merge remote branch failed: {}", + String::from_utf8_lossy(&merge_output.stderr) + ); +} + + +#[tokio::test] +/// Test merging branches with no common ancestor +async fn test_merge_no_common_ancestor() { + let temp_repo = init_temp_repo(); + let temp_path = temp_repo.path(); + + // Create and switch to branch1 + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["branch", "branch1"]) + .output() + .expect("Failed to create branch"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["checkout", "branch1"]) + .output() + .expect("Failed to checkout branch"); + + // Commit changes on branch1 + let branch1_file = temp_path.join("branch1.txt"); + std::fs::write(&branch1_file, "Branch1 content").expect("Failed to write file"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["add", "."]) + .output() + .expect("Failed to add file"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["commit", "-m", "Add branch1 content"]) + .output() + .expect("Failed to commit"); + + // Create and switch to branch2 + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["checkout", "-b", "branch2", "HEAD~1"]) + .output() + .expect("Failed to create branch"); + + // Commit changes on branch2 + let branch2_file = temp_path.join("branch2.txt"); + std::fs::write(&branch2_file, "Branch2 content").expect("Failed to write file"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["add", "."]) + .output() + .expect("Failed to add file"); + + Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["commit", "-m", "Add branch2 content"]) + .output() + .expect("Failed to commit"); + + // Attempt to merge branches with no common ancestor + + let merge_output = Command::new(env!("CARGO_BIN_EXE_libra")) + .current_dir(temp_path) + .args(["merge", "branch2"]) + .output() + .expect("Failed to merge branch"); + assert!( + merge_output.status.success(), + "Merge no common ancestor branch failed: {}", + String::from_utf8_lossy(&merge_output.stderr) + ); +} diff --git a/monobean/Cargo.toml b/monobean/Cargo.toml index e6ed96f50..19189cad7 100644 --- a/monobean/Cargo.toml +++ b/monobean/Cargo.toml @@ -43,6 +43,10 @@ smallvec = "1.14.0" directories = "6.0.0" tracing-appender = "0.2.3" toml = "0.8.22" +#once_cell = "1.21.3" + +resvg = "0.27" +tiny-skia = "0.11" [build-dependencies] glib-build-tools = "0.20.0" diff --git a/monobean/resources/css/common.css b/monobean/resources/css/common.css index 27100c77d..8f992fe62 100644 --- a/monobean/resources/css/common.css +++ b/monobean/resources/css/common.css @@ -1,3 +1,15 @@ .right-side-box { background-color: @view_bg_color; -} \ No newline at end of file +} + +/*.path-label {*/ +/* color: #4a4a4a;*/ +/* font-family: "Fira Mono", "Consolas", "monospace";*/ +/* font-size: 13px;*/ +/* padding: 6px 12px;*/ +/* border-radius: 6px;*/ +/* border: 1px solid #e0e0e0;*/ +/* letter-spacing: 0.5px;*/ +/* margin-bottom: 4px;*/ +/* }*/ + diff --git a/monobean/resources/gschemas.compiled b/monobean/resources/gschemas.compiled new file mode 100644 index 0000000000000000000000000000000000000000..f3a16f64796eeb072d3af3c985053c9ce0bf9b4b GIT binary patch literal 2764 zcmZWqTWl0n7(Sqda&1K}MQ&TPu#24CLTe<%XhQ^wmq-+aH#$3Wx-)ckW}ORdB^XQ; z>w|#N7}BUQQId*aynvuD@>rD+!vpYwAxNSkF$6Us2CUzAW_Brbny=rt-#K$G|M|~9 z>t8Se$M7PNK26{)iO!Bsy>Z~nUv+&An>6!1I9xz@ik*;KjfLKnt)0SPJAu;nbIx=Wt!zbEDWUvS119 zxiO=GK6M@ZRp2$$XgU?*Oj_HlKa#5&G2ZzY)9zII;8PDLf}?j^71-75I7J%sBR^ zo&bLzct7C$)7wg)n(G__9|o?xy?Y&f>dEkrgTDuww*GjLKJ_&C=fJaZc>mhpb<(G1 z{|q<}oSn9!g+BFE_#I#u`1*-YPtm8I4u2PT5U`HDq4!12eGP#R19Q7hZeoAx2KXny z!$AFl=l0U4X8&{GYe4h2kM5yQ&3Oe9s}2~vIB=6b^+foM;B~+s*OpzUPtEvj1n&e6 zo%!S{eQJ(B06q;|`eN0y^r;z#3*bM2t%ZYI=u>l@cu7nG4s1TUlspz^`=zv)xYhHrz*z-v$L*5^gd_4I*v0-gT0 z$?Q+fyd40)37lJYOy6H>p8JR3L%_UiyWU}cYMnpeVW8whc3hOE>nJahj)ig+<_xRs z=+Y%bDq$uyi?xI`h;;?8i#fm&pjwBPLi543z`ijW&T)I0~%U29Nb^#xP!q^W% zq52^xV0pH3N_INnPdp5)!*ubLkfveUO0Pt(!zptTX*q!?M79%HvSI{Lzurt3^}9-L zuYS$kbVxJZ$4J;8MykwTBl=&`A<>o*^uV8hy=421IG6K_p$t^X@jcU%3g0i;rYE7V zJgee6)h{Nu9H4viH+{^DFcp12!l%%vNYA&DZ3(`Q*InN< zT-y($)hkxEwd>wGs772nA)EuUPny2xDKo;-Lhb|Q1ij;2>`2Qm8`bn2J8oNRYpWhN ze9`Tbu3suSUP-@P%vnVl^;h(Y(CS1`x8jx>)wor#$P!WszbDq>g#qHgwjYe&1Jz*`oW#soLUgfeX8A(`hNs<)iGQ?*LEaJ)~CaH`ACUwH_U7P&F_Qhc-+Tt<|BK zFhuDZg*IX^O(F@RXyKZd-RO5s_m~5AZKM5p_1{@_bEU^a(o=-sOz*mQwjWS z$g$-a%Qu&20)@L6Vn2866oc2cA==lEY1fxG4IXC1+enkNEF{0XF*VnOON{nnQb($l VimMd2)vL16cW3(R8p`;w@Bfc$KimKS literal 0 HcmV?d00001 diff --git a/monobean/resources/gtk/code_page.ui b/monobean/resources/gtk/code_page.ui index 6e8c3cc35..97c9a109f 100644 --- a/monobean/resources/gtk/code_page.ui +++ b/monobean/resources/gtk/code_page.ui @@ -7,79 +7,274 @@ true - - 300 - - - - - - - - - - crossfade - true - true - - - - - empty_view - Empty View - - - vertical - center - center - 12 + + horizontal + + + + 300 + + + + + + + + + + vertical + 0 + + + + + start + 0.0 + 6 + 4 + 4 + No file opened + + + + + + + crossfade + true + true + - - monobean-file-text-symbolic - 64 + + empty_view + Empty View + + + vertical + center + center + 12 + + + monobean-file-text-symbolic + 64 + + + + + No file selected + + + + + + - - No file selected - + + source_view + Source View + + + true + true + + + true + true + true + 4 + true + word + + + + - + + + + + + + + + vertical + 50 + 10 + + + + 15 + 40 + 40 + + + monobean-url-clone-symbolic + large + + + - - - source_view - Source View - - - true - true - - - true - true - true - 4 - true - word - - + + 40 + 40 + + + monobean-historybtn-symbolic + large - + + - + + + + true + + + vertical + 12 + 12 + 8 + 8 + 8 + + + + + + url_content_stack + center + + + + + + + + + http_page + HTTP + + + horizontal + 4 + + + + http://git.gitmegas.com/mega/code/tree/project + false + true + + + + + edit-copy-symbolic + 复制链接 + + + + + + + + + + + ssh_page + SSH + + + horizontal + 4 + + + + git@gitmegas.com:mega/code.git + false + true + + + + + edit-copy-symbolic + 复制链接 + + + + + + + + + + + + + Clone using the web URL. + start + 4 + + + + + + + + + true + + + vertical + + + History + + + + + True + True + False + 400 + 600 + 150 + automatic + automatic + + + + + + + + + + + + diff --git a/monobean/resources/gtk/file_row.ui b/monobean/resources/gtk/file_row.ui index b5da757ca..ab4691562 100644 --- a/monobean/resources/gtk/file_row.ui +++ b/monobean/resources/gtk/file_row.ui @@ -10,6 +10,12 @@ horizontal 6 + + + + + + diff --git a/monobean/resources/gtk/mega_tab.ui b/monobean/resources/gtk/mega_tab.ui deleted file mode 100644 index df8ee042c..000000000 --- a/monobean/resources/gtk/mega_tab.ui +++ /dev/null @@ -1,214 +0,0 @@ - - - - - diff --git a/monobean/resources/gtk/mega_tab_window.ui b/monobean/resources/gtk/mega_tab_window.ui new file mode 100644 index 000000000..6740b2742 --- /dev/null +++ b/monobean/resources/gtk/mega_tab_window.ui @@ -0,0 +1,226 @@ + + + + + diff --git a/monobean/resources/gtk/window.ui b/monobean/resources/gtk/window.ui index dafcd8f20..a06419f05 100644 --- a/monobean/resources/gtk/window.ui +++ b/monobean/resources/gtk/window.ui @@ -6,7 +6,7 @@ 820 800 600 - Monobean +
@@ -38,23 +38,23 @@ - - - strict + loose + horizontal 6 4 - - + true + - - mono-white-logo + + avatar-default-symbolic + false @@ -62,20 +62,58 @@ monobean-arrow-left-symbolic + + + + + wide + content_stack + + + + + + + + horizontal + 0 + + + + + true + 请输入文本 + + + + + + + 搜索 + + + + + + + + + + + + + + primary_menu monobean-menu-symbolic - - - wide - content_stack - - + + @@ -100,50 +138,132 @@ main_page Main - - - - mega_tab - Mega - monobean-code-symbolic - - - - - + + vertical + 0 + - - monorepo - Mono Repo - monobean-package-symbolic - - - - + + + + + + + + + + + + + + + + + + + + + + + + repository + repository + monobean-repo-symbolic + + + + + + + + + network + network + monobean-network-symbolic + + + + + + + + + + - - code - Code - monobean-package-2-symbolic - - - - + + - - p2p - P2P - monobean-crop-symbolic - - - + + horizontal + 6 + 6 + 6 + 4 + + + + + false + + + + horizontal + 4 + + + + dialog-warning + + + + + + Mega Started + + + + + + + + + + + true + + + + + + + horizontal + 4 + + + + 当前有 3 个链接 + + + + + + network-wired-symbolic + + + + + + - + diff --git a/monobean/resources/icons/symbolic/apps/monobean-css-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-css-symbolic.svg new file mode 100644 index 000000000..8e181ea4d --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-css-symbolic.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-gitFile-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-gitFile-symbolic.svg new file mode 100644 index 000000000..bd7ff1110 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-gitFile-symbolic.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-historybtn-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-historybtn-symbolic.svg new file mode 100644 index 000000000..88c59d792 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-historybtn-symbolic.svg @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-json-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-json-symbolic.svg new file mode 100644 index 000000000..d0270f347 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-json-symbolic.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-markdown-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-markdown-symbolic.svg new file mode 100644 index 000000000..b7a30b7f6 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-markdown-symbolic.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-picture-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-picture-symbolic.svg new file mode 100644 index 000000000..01cd19369 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-picture-symbolic.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-repo-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-repo-symbolic.svg new file mode 100644 index 000000000..b0914e9cf --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-repo-symbolic.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-rss-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-rss-symbolic.svg new file mode 100644 index 000000000..7d4d552b2 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-rss-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-rust-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-rust-symbolic.svg new file mode 100644 index 000000000..4b783a9de --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-rust-symbolic.svg @@ -0,0 +1,7 @@ + + + + rust + + + diff --git a/monobean/resources/icons/symbolic/apps/monobean-settingFile-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-settingFile-symbolic.svg new file mode 100644 index 000000000..473b8c273 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-settingFile-symbolic.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/monobean/resources/icons/symbolic/apps/monobean-url-clone-symbolic.svg b/monobean/resources/icons/symbolic/apps/monobean-url-clone-symbolic.svg new file mode 100644 index 000000000..5d5ec5ba3 --- /dev/null +++ b/monobean/resources/icons/symbolic/apps/monobean-url-clone-symbolic.svg @@ -0,0 +1,6 @@ + + + clone-line + + + \ No newline at end of file diff --git a/monobean/resources/org.Web3Infrastructure.Monobean.gresource.xml b/monobean/resources/org.Web3Infrastructure.Monobean.gresource.xml index 5c69814b7..99b2173ac 100644 --- a/monobean/resources/org.Web3Infrastructure.Monobean.gresource.xml +++ b/monobean/resources/org.Web3Infrastructure.Monobean.gresource.xml @@ -12,7 +12,7 @@ gtk/window.ui gtk/hello_page.ui gtk/code_page.ui - gtk/mega_tab.ui + gtk/mega_tab_window.ui gtk/repo_tab.ui gtk/repo_detail.ui gtk/preferences.ui @@ -60,6 +60,16 @@ icons/symbolic/apps/monobean-step-back-symbolic.svg icons/symbolic/apps/monobean-trash-symbolic.svg icons/symbolic/apps/monobean-upload-symbolic.svg - + icons/symbolic/apps/monobean-url-clone-symbolic.svg + icons/symbolic/apps/monobean-historybtn-symbolic.svg + icons/symbolic/apps/monobean-repo-symbolic.svg + icons/symbolic/apps/monobean-gitFile-symbolic.svg + icons/symbolic/apps/monobean-settingFile-symbolic.svg + icons/symbolic/apps/monobean-rust-symbolic.svg + icons/symbolic/apps/monobean-markdown-symbolic.svg + icons/symbolic/apps/monobean-picture-symbolic.svg + icons/symbolic/apps/monobean-rss-symbolic.svg + icons/symbolic/apps/monobean-css-symbolic.svg + icons/symbolic/apps/monobean-json-symbolic.svg diff --git a/monobean/src/application.rs b/monobean/src/application.rs index 9058346ea..5d62ec50e 100644 --- a/monobean/src/application.rs +++ b/monobean/src/application.rs @@ -23,6 +23,7 @@ use gtk::{gio, glib}; use std::cell::{OnceCell, RefCell}; use std::fmt::Debug; use std::net::{IpAddr, SocketAddr}; +use std::path::PathBuf; use tokio::sync::oneshot; use tracing_subscriber::fmt::writer::MakeWriterExt; @@ -43,12 +44,16 @@ pub enum Action { ShowHelloPage, ShowMainPage, MountRepo, - OpenEditorOn { hash: String, name: String }, + OpenEditorOn { + hash: String, + name: String, + path: PathBuf, + }, } mod imp { - use super::*; + use adw::gdk; use crate::core::delegate::MegaDelegate; @@ -114,6 +119,9 @@ mod imp { return; } + let theme = gtk::IconTheme::for_display(&gdk::Display::default().unwrap()); + theme.add_resource_path("/org/Web3Infrastructure/Monobean/icons/symbolic/apps"); + let window = app.create_window(); self.window.set(window.downgrade()).unwrap(); @@ -442,11 +450,11 @@ impl MonobeanApplication { window.show_main_page(); } Action::MountRepo => todo!(), - Action::OpenEditorOn { hash, name } => { + Action::OpenEditorOn { hash, name, path } => { CONTEXT.spawn_local(async move { let window = window.imp(); let code_page = window.code_page.get(); - code_page.show_editor_on(hash, name); + code_page.show_editor_on(hash, name, path); }); } } diff --git a/monobean/src/components/code_page.rs b/monobean/src/components/code_page.rs index eafa30fad..2e90b25fa 100644 --- a/monobean/src/components/code_page.rs +++ b/monobean/src/components/code_page.rs @@ -1,17 +1,18 @@ -use std::path::Path; - +use crate::application::Action; +use crate::components::history_list::HistoryItem; +use crate::core::mega_core::MegaCommands; +use crate::CONTEXT; +use adw::gdk; +use adw::gio::ListStore; use async_channel::Sender; -use gtk::glib::clone; +use glib::clone; use gtk::prelude::*; use gtk::subclass::prelude::*; -use gtk::{glib, CompositeTemplate}; +use gtk::{glib, CompositeTemplate, Label, ListItem, SignalListItemFactory, SingleSelection}; use scv::{prelude::*, Buffer}; +use std::path::{Path, PathBuf}; use tokio::sync::oneshot; -use crate::application::Action; -use crate::core::mega_core::MegaCommands; -use crate::CONTEXT; - mod imp { use adw::subclass::prelude::BinImpl; use tokio::sync::OnceCell; @@ -28,7 +29,10 @@ mod imp { // #[template_child] // pub search_entry: TemplateChild, #[template_child] + pub file_path_label: TemplateChild