From 3fc6ef9be46c9269d8b82fed8e4c7e8d8767a416 Mon Sep 17 00:00:00 2001 From: "Aid C." <57825561+AidCheng@users.noreply.github.com> Date: Sat, 26 Jul 2025 13:03:34 +0100 Subject: [PATCH 1/8] [libra]FIX: missing .gitignore tick in readme The ignore function is already been implemented but is marked as unfinished in the readme file --- libra/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/README.md b/libra/README.md index 5cd4d5cce..4c66a7a98 100644 --- a/libra/README.md +++ b/libra/README.md @@ -80,7 +80,7 @@ achieving unified management. - [x] `fetch` ### Others -- [ ] `.gitignore` +- [x] `.gitignore` - [x] `.gitattributes` (only for `lfs` now) - [x] `LFS` (embedded, with p2p feature) - [ ] `ssh` From 4a2ecfdcc8a73f7014be956c51b2d435fe878465 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Sun, 27 Jul 2025 22:01:53 +0100 Subject: [PATCH 2/8] [libra]FEAT: libra add --refresh implemented Signed-off-by: AidCheng --- libra/src/command/add.rs | 49 ++++++++++++++++++++++++++++++++--- mercury/src/internal/index.rs | 35 +++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/libra/src/command/add.rs b/libra/src/command/add.rs index 2ba9fbb65..6f89dc893 100644 --- a/libra/src/command/add.rs +++ b/libra/src/command/add.rs @@ -24,6 +24,14 @@ pub struct AddArgs { #[clap(short, long, group = "mode")] pub update: bool, + /// Refresh index entries for all files currently in the index. + /// + /// This updates only the metadata (e.g. file stat information such as + /// timestamps, file size, etc.) of existing index entries to match + /// the working tree, without adding new files or removing entries. + #[clap(long, group = "mode")] + pub refresh: bool, + /// more detailed output #[clap(short, long)] pub verbose: bool, @@ -64,6 +72,38 @@ pub async fn execute(args: AddArgs) { changes.deleted = util::filter_to_fit_paths(&changes.deleted, &paths); } + if args.refresh { + let index_path = path::index(); + let index = Index::load(&index_path).unwrap(); + + let files: Vec = changes + .modified + .into_iter() + .filter(|p| index.tracked(p.to_str().unwrap(), 0)) + .collect(); + + // check for dry_run + if args.dry_run { + for file in &files { + println!("refresh: {}", file.display()) + } + } + + let index_file = path::index(); + let mut index = Index::load(&index_file).unwrap(); + for file in &files { + if index.refresh(file.to_str().unwrap(), &util::working_dir()) + .expect(&format!("error refreshing {}", file.display())) { + if args.verbose { + println!("refreshed: {}", file.display()); + } + } + } + + index.save(&index_file).unwrap(); + return; + } + let mut files = changes.modified; files.extend(changes.deleted); // `--update` only operates on tracked files, not including `new` files @@ -203,9 +243,10 @@ mod test { use super::*; #[test] - #[should_panic] - /// Test that `-A` and `-u` cannot be used together - fn test_args_parse_update_conflict_with_all() { - AddArgs::try_parse_from(["test", "-A", "-u"]).unwrap(); + fn test_args_conflict_with_refresh() { + // "--refresh" cannot be combined with "-A", "--refresh" or "-u" + assert!(AddArgs::try_parse_from(&["test", "-A", "--refresh"]).is_err()); + assert!(AddArgs::try_parse_from(&["test", "-u", "--refresh"]).is_err()); + assert!(AddArgs::try_parse_from(&["test", "-A", "-u", "--refresh"]).is_err()); } } diff --git a/mercury/src/internal/index.rs b/mercury/src/internal/index.rs index ecf8ced26..dded1078a 100644 --- a/mercury/src/internal/index.rs +++ b/mercury/src/internal/index.rs @@ -339,6 +339,41 @@ impl Index { file.write_all(&file_hash)?; Ok(()) } + + pub fn refresh(&mut self, file: impl AsRef, workdir: &Path)-> Result { + let path = file.as_ref(); + let name = path.to_str() + .ok_or(GitError::InvalidPathError(format!("{path:?}")))?; + + if let Some(entry) = self.entries.get_mut(&(name.to_string(), 0)) { + let abs_path = workdir.join(path); + let meta = fs::symlink_metadata(&abs_path)?; + let new_ctime = Time::from_system_time(meta.created().unwrap_or_else(|_| SystemTime::now())); + let new_mtime= Time::from_system_time(meta.modified().unwrap_or_else(|_| SystemTime::now())); + let new_size = meta.len() as u32; + + // re-calculate SHA1 + let mut file = File::open(&abs_path)?; + let mut hasher = Sha1::new(); + io::copy(&mut file, &mut hasher)?; + let new_hash = SHA1::from_bytes(&hasher.finalize()); + + // refresh index + if entry.ctime != new_ctime + || entry.mtime != new_mtime + || entry.size != new_size + || entry.hash != new_hash + { + entry.ctime = new_ctime; + entry.mtime = new_mtime; + entry.size = new_size; + entry.hash = new_hash; + return Ok(true); + } + } + Ok(false) + } + } impl Index { From 68207b071fd060071a7529314a694fdb250018f7 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Sun, 27 Jul 2025 22:06:48 +0100 Subject: [PATCH 3/8] [libra]FIX: added refresh arg in the unit-test to pass --- libra/tests/command/commit_test.rs | 1 + libra/tests/command/reset_test.rs | 5 +++++ libra/tests/command/status_test.rs | 1 + libra/tests/command/switch_test.rs | 1 + 4 files changed, 8 insertions(+) diff --git a/libra/tests/command/commit_test.rs b/libra/tests/command/commit_test.rs index 9ad795042..233e8ea97 100644 --- a/libra/tests/command/commit_test.rs +++ b/libra/tests/command/commit_test.rs @@ -96,6 +96,7 @@ async fn test_execute_commit() { pathspec: vec![], dry_run: false, ignore_errors: false, + refresh: false, }; add::execute(args).await; } diff --git a/libra/tests/command/reset_test.rs b/libra/tests/command/reset_test.rs index 2f6038d92..3536e4d9c 100644 --- a/libra/tests/command/reset_test.rs +++ b/libra/tests/command/reset_test.rs @@ -16,6 +16,7 @@ async fn setup_standard_repo(temp_path: &std::path::Path) -> (SHA1, SHA1, SHA1, verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -46,6 +47,7 @@ async fn setup_standard_repo(temp_path: &std::path::Path) -> (SHA1, SHA1, SHA1, verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -76,6 +78,7 @@ async fn setup_standard_repo(temp_path: &std::path::Path) -> (SHA1, SHA1, SHA1, verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -106,6 +109,7 @@ async fn setup_standard_repo(temp_path: &std::path::Path) -> (SHA1, SHA1, SHA1, verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -145,6 +149,7 @@ async fn setup_test_state() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; } diff --git a/libra/tests/command/status_test.rs b/libra/tests/command/status_test.rs index ba10680d4..47714d80e 100644 --- a/libra/tests/command/status_test.rs +++ b/libra/tests/command/status_test.rs @@ -47,6 +47,7 @@ async fn test_changes_to_be_staged() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; diff --git a/libra/tests/command/switch_test.rs b/libra/tests/command/switch_test.rs index 55c0bb696..413d26cee 100644 --- a/libra/tests/command/switch_test.rs +++ b/libra/tests/command/switch_test.rs @@ -21,6 +21,7 @@ async fn test_check_status() { verbose: true, dry_run: false, ignore_errors: false, + refresh: false, }; add::execute(add_args).await; assert!(check_status().await); From f3bbeac44adb282e2cd59d404066e4d4c44b715c Mon Sep 17 00:00:00 2001 From: "Aid C." <57825561+AidCheng@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:14:39 +0100 Subject: [PATCH 4/8] Update mercury/src/internal/index.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Aid C. <57825561+AidCheng@users.noreply.github.com> --- mercury/src/internal/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mercury/src/internal/index.rs b/mercury/src/internal/index.rs index dded1078a..17eca7d03 100644 --- a/mercury/src/internal/index.rs +++ b/mercury/src/internal/index.rs @@ -349,7 +349,7 @@ impl Index { let abs_path = workdir.join(path); let meta = fs::symlink_metadata(&abs_path)?; let new_ctime = Time::from_system_time(meta.created().unwrap_or_else(|_| SystemTime::now())); - let new_mtime= Time::from_system_time(meta.modified().unwrap_or_else(|_| SystemTime::now())); + let new_mtime = Time::from_system_time(meta.modified().unwrap_or_else(|_| SystemTime::now())); let new_size = meta.len() as u32; // re-calculate SHA1 From 42dbf881d4f29fd98ed3a48f8f4fb1c5b712e2f8 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Mon, 28 Jul 2025 00:25:41 +0100 Subject: [PATCH 5/8] [libra]FIX: WorkflowErrors Signed-off-by: AidCheng --- libra/src/command/add.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libra/src/command/add.rs b/libra/src/command/add.rs index 6f89dc893..d166e95be 100644 --- a/libra/src/command/add.rs +++ b/libra/src/command/add.rs @@ -1,6 +1,6 @@ use crate::command::status; use crate::utils::object_ext::BlobExt; -use clap::Parser; +use clap::{Parser}; use mercury::internal::index::{Index, IndexEntry}; use mercury::internal::object::blob::Blob; use std::path::{Path, PathBuf}; @@ -29,7 +29,7 @@ pub struct AddArgs { /// This updates only the metadata (e.g. file stat information such as /// timestamps, file size, etc.) of existing index entries to match /// the working tree, without adding new files or removing entries. - #[clap(long, group = "mode")] + #[clap(long, default_value_t = false, group = "mode")] pub refresh: bool, /// more detailed output @@ -92,11 +92,12 @@ pub async fn execute(args: AddArgs) { let index_file = path::index(); let mut index = Index::load(&index_file).unwrap(); for file in &files { - if index.refresh(file.to_str().unwrap(), &util::working_dir()) - .expect(&format!("error refreshing {}", file.display())) { - if args.verbose { - println!("refreshed: {}", file.display()); - } + if index + .refresh(file, &util::working_dir()) + .unwrap_or_else(|_| panic!("error refreshing {}", file.display())) + && args.verbose + { + println!("refreshed: {}", file.display()); } } From 0d70600397ffee248746ad293c880c76eae989bb Mon Sep 17 00:00:00 2001 From: AidCheng Date: Mon, 28 Jul 2025 00:38:05 +0100 Subject: [PATCH 6/8] [libra]FIX: failed unit-test due to missing refresh arg Signed-off-by: AidCheng --- libra/src/command/add.rs | 8 ++++---- libra/tests/command/cherry_pick_test.rs | 8 ++++++++ libra/tests/command/revert_test.rs | 6 ++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/libra/src/command/add.rs b/libra/src/command/add.rs index d166e95be..1299595bc 100644 --- a/libra/src/command/add.rs +++ b/libra/src/command/add.rs @@ -29,7 +29,7 @@ pub struct AddArgs { /// This updates only the metadata (e.g. file stat information such as /// timestamps, file size, etc.) of existing index entries to match /// the working tree, without adding new files or removing entries. - #[clap(long, default_value_t = false, group = "mode")] + #[clap(long, group = "mode")] pub refresh: bool, /// more detailed output @@ -246,8 +246,8 @@ mod test { #[test] fn test_args_conflict_with_refresh() { // "--refresh" cannot be combined with "-A", "--refresh" or "-u" - assert!(AddArgs::try_parse_from(&["test", "-A", "--refresh"]).is_err()); - assert!(AddArgs::try_parse_from(&["test", "-u", "--refresh"]).is_err()); - assert!(AddArgs::try_parse_from(&["test", "-A", "-u", "--refresh"]).is_err()); + assert!(AddArgs::try_parse_from(["test", "-A", "--refresh"]).is_err()); + assert!(AddArgs::try_parse_from(["test", "-u", "--refresh"]).is_err()); + assert!(AddArgs::try_parse_from(["test", "-A", "-u", "--refresh"]).is_err()); } } diff --git a/libra/tests/command/cherry_pick_test.rs b/libra/tests/command/cherry_pick_test.rs index b9f0d8e78..60cf80fad 100644 --- a/libra/tests/command/cherry_pick_test.rs +++ b/libra/tests/command/cherry_pick_test.rs @@ -29,6 +29,7 @@ async fn test_basic_cherry_pick() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -61,6 +62,7 @@ async fn test_basic_cherry_pick() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -88,6 +90,7 @@ async fn test_basic_cherry_pick() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -217,6 +220,7 @@ async fn test_cherry_pick_with_commit() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -245,6 +249,7 @@ async fn test_cherry_pick_with_commit() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -320,6 +325,7 @@ async fn test_cherry_pick_multiple_commits() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -349,6 +355,7 @@ async fn test_cherry_pick_multiple_commits() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -371,6 +378,7 @@ async fn test_cherry_pick_multiple_commits() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { diff --git a/libra/tests/command/revert_test.rs b/libra/tests/command/revert_test.rs index 1542f3158..26185cc8e 100644 --- a/libra/tests/command/revert_test.rs +++ b/libra/tests/command/revert_test.rs @@ -30,6 +30,7 @@ async fn test_basic_revert() { verbose: false, dry_run: false, ignore_errors: false, + refresh : false, }) .await; commit::execute(CommitArgs { @@ -52,6 +53,7 @@ async fn test_basic_revert() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -75,6 +77,7 @@ async fn test_basic_revert() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -172,6 +175,7 @@ async fn test_revert_no_commit() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -192,6 +196,7 @@ async fn test_revert_no_commit() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -250,6 +255,7 @@ async fn test_revert_root_commit() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { From 10913e1f62ca6965565b31a10fdccf90bcd9b945 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Mon, 28 Jul 2025 10:39:40 +0100 Subject: [PATCH 7/8] [libra]Handling: Added more explicit error handling in refresh logic --- mercury/src/internal/index.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/mercury/src/internal/index.rs b/mercury/src/internal/index.rs index 17eca7d03..0e721cf77 100644 --- a/mercury/src/internal/index.rs +++ b/mercury/src/internal/index.rs @@ -340,7 +340,7 @@ impl Index { Ok(()) } - pub fn refresh(&mut self, file: impl AsRef, workdir: &Path)-> Result { + pub fn refresh(&mut self, file: impl AsRef, workdir: &Path) -> Result { let path = file.as_ref(); let name = path.to_str() .ok_or(GitError::InvalidPathError(format!("{path:?}")))?; @@ -348,8 +348,9 @@ impl Index { if let Some(entry) = self.entries.get_mut(&(name.to_string(), 0)) { let abs_path = workdir.join(path); let meta = fs::symlink_metadata(&abs_path)?; - let new_ctime = Time::from_system_time(meta.created().unwrap_or_else(|_| SystemTime::now())); - let new_mtime = Time::from_system_time(meta.modified().unwrap_or_else(|_| SystemTime::now())); + // Try creation time; on error, warn and use modification time (or now) + let new_ctime = Time::from_system_time(Self::time_or_now("creation time", &abs_path, meta.created())); + let new_mtime = Time::from_system_time(Self::time_or_now("modification time", &abs_path, meta.modified())); let new_size = meta.len() as u32; // re-calculate SHA1 @@ -374,6 +375,23 @@ impl Index { Ok(false) } + /// Try to get a timestamp, logging on error, and finally falling back to now. + fn time_or_now( + what: &str, + path: &Path, + res: io::Result, + ) -> SystemTime { + match res { + Ok(ts) => ts, + Err(e) => { + eprintln!( + "warning: failed to get {} for {:?}: {}; using SystemTime::now()", + what, path, e + ); + SystemTime::now() + } + } + } } impl Index { From ffd7281e2d463e0b93db65ec46ccf6b0ad2a0e43 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Mon, 28 Jul 2025 10:58:13 +0100 Subject: [PATCH 8/8] [libra]FIX: missing args in #rebase_test.rs --- libra/src/command/add.rs | 5 ++++- libra/tests/command/rebase_test.rs | 7 +++++++ mercury/src/internal/index.rs | 5 +---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/libra/src/command/add.rs b/libra/src/command/add.rs index 1299595bc..2b76c5add 100644 --- a/libra/src/command/add.rs +++ b/libra/src/command/add.rs @@ -79,7 +79,10 @@ pub async fn execute(args: AddArgs) { let files: Vec = changes .modified .into_iter() - .filter(|p| index.tracked(p.to_str().unwrap(), 0)) + .filter(|p| { + let s = p.to_str().unwrap_or_else(|| { panic!("path {:?} is not valid UTF-8", p.display()) }); + index.tracked(s, 0) + }) .collect(); // check for dry_run diff --git a/libra/tests/command/rebase_test.rs b/libra/tests/command/rebase_test.rs index de08df3fa..befcc908a 100644 --- a/libra/tests/command/rebase_test.rs +++ b/libra/tests/command/rebase_test.rs @@ -21,6 +21,7 @@ async fn test_basic_rebase() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -41,6 +42,7 @@ async fn test_basic_rebase() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -70,6 +72,7 @@ async fn test_basic_rebase() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -90,6 +93,7 @@ async fn test_basic_rebase() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -118,6 +122,7 @@ async fn test_basic_rebase() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -187,6 +192,7 @@ async fn test_rebase_already_up_to_date() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { @@ -207,6 +213,7 @@ async fn test_rebase_already_up_to_date() { verbose: false, dry_run: false, ignore_errors: false, + refresh: false, }) .await; commit::execute(CommitArgs { diff --git a/mercury/src/internal/index.rs b/mercury/src/internal/index.rs index 0e721cf77..1d30621c7 100644 --- a/mercury/src/internal/index.rs +++ b/mercury/src/internal/index.rs @@ -384,10 +384,7 @@ impl Index { match res { Ok(ts) => ts, Err(e) => { - eprintln!( - "warning: failed to get {} for {:?}: {}; using SystemTime::now()", - what, path, e - ); + eprintln!("warning: failed to get {what} for {path:?}: {e}; using SystemTime::now()", what = what, path = path.display()); SystemTime::now() } }