diff --git a/README.md b/README.md index 42f3793..aa638c2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Git in Rust. 用 `Rust` 实现的mini `Git`. Called `mit`. - 支持 `mit init`, `mit add`, `mit rm`, `mit commit` - - [x] `init`: 初始化(若仓库已存在,则不执行) + - [x] `init`: 初始化(若仓库已存在,则不执行)- `idempotent` - [x] `add`: 将变更添加至暂存区(包括新建、修改、删除),可指定文件或目录 - `-A(all)` : 暂存工作区中的所有文件(从根目录开始)变更(新建√ 修改√ 删除√) - `-u(update)`: 仅对暂存区[`index`]中已跟踪的文件进行操作(新建× 修改√ 删除√) @@ -55,7 +55,6 @@ Git in Rust. 用 `Rust` 实现的mini `Git`. Called `mit`. ### 名词释义 - 暂存区:`index` or `stage`,保存下一次`commit`需要的的文件快照 - 工作区:`worktree`,用户直接操作的文件夹 -- 工作目录:`working directory`,代码仓库的根目录,即`.mit`所在的目录 -- 仓库:`repository`,包含`.mit`目录的目录 +- 工作目录:`working directory` or `repository`,代码仓库的根目录,即`.mit`所在的目录 - `HEAD`:指向当前`commit`的指针 - 已跟踪:`tracked`,指已经在暂存区[`index`]中的文件(即曾经`add`过的文件) \ No newline at end of file diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 0e42f81..343e675 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -68,9 +68,7 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB let input_paths = preprocess_filters(filter); //预处理filter 将None转化为workdir let target_blobs = preprocess_blobs(target_blobs); //预处理target_blobs 转化为绝对路径HashMap - //TODO 输出不存在于target和worktree中文件 - - let deleted_files = get_worktree_deleted_files_in_filters(&input_paths, &target_blobs); //统计所有目录中已删除的文件 + let deleted_files = get_worktree_deleted_files_in_filters(&input_paths, &target_blobs); //统计已删除的文件 let mut file_paths = util::integrate_paths(&input_paths); //根据用户输入整合存在的文件(绝对路径) file_paths.extend(deleted_files); //已删除的文件 @@ -86,9 +84,8 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB //文件存在于target_commit (deleted),需要恢复 store.restore_to_file(&target_blobs[path], &path); } else { - //在target_commit和workdir中都不存在(非法路径) - // println!("fatal: pathspec '{}' did not match any files", path.display()); - // TODO 如果是用户输入的路径,才应该报错,integrate_paths产生的不应该报错 + //在target_commit和workdir中都不存在(非法路径), 用户输入 + println!("fatal: pathspec '{}' did not match any files", path.display()); } } else { //文件存在,有两种情况:1.修改 2.新文件 @@ -116,7 +113,7 @@ pub fn restore_index(filter: Option<&Vec>, target_blobs: &Vec<(PathBuf, let target_blobs = preprocess_blobs(target_blobs); //预处理target_blobs 转化为绝对路径HashMap let mut index = Index::new(); - let deleted_files_index = get_index_deleted_files_in_filters(&index, &input_paths, &target_blobs); //统计所有目录中已删除的文件 + let deleted_files_index = get_index_deleted_files_in_filters(&index, &input_paths, &target_blobs); //统计已删除的文件 //1.获取index中包含于input_path的文件(使用paths进行过滤) let mut file_paths: HashSet = util::filter_to_fit_paths(&index.get_tracked_files(), &input_paths); @@ -133,8 +130,7 @@ pub fn restore_index(filter: Option<&Vec>, target_blobs: &Vec<(PathBuf, index.add(path.clone(), FileMetaData { hash: target_blobs[path].clone(), ..Default::default() }); } else { //在target_commit和index中都不存在(非法路径) - // println!("fatal: pathspec '{}' did not match any files", path.display()); - // TODO 如果是用户输入的路径,才应该报错,integrate_paths产生的不应该报错 + println!("fatal: pathspec '{}' did not match any files", path.display()); } } else { //文件存在于index,有两种情况:1.修改 2.新文件 @@ -224,18 +220,46 @@ pub fn restore(paths: Vec, source: Option, worktree: bool, stage #[cfg(test)] mod test { + use std::fs; + //TODO 写测试! use std::path::PathBuf; - use crate::{commands, models::index::Index, utils::util}; + use crate::commands::add::add; + use crate::commands::restore::restore; + use crate::commands::status; + use crate::{models::index::Index, utils::util}; #[test] fn test_restore_stage() { - util::setup_test_with_clean_mit(); + util::setup_test_with_empty_workdir(); let path = PathBuf::from("a.txt"); util::ensure_no_file(&path); - commands::add::add(vec![], true, false); - super::restore(vec![".".to_string()], Some("HEAD".to_string()), false, true); + add(vec![], true, false); //add -A + restore(vec![".".to_string()], Some("HEAD".to_string()), false, true); let index = Index::new(); assert!(index.get_tracked_files().is_empty()); } + + #[test] + fn test_restore_worktree() { + util::setup_test_with_empty_workdir(); + let files = vec!["a.txt", "b.txt", "c.txt", "test/in.txt"]; + util::ensure_test_files(&files); + + add(vec![], true, false); + assert_eq!(status::changes_to_be_committed().new.iter().count(), 4); + + restore(vec!["c.txt".to_string()], None, false, true); //restore c.txt --staged + assert_eq!(status::changes_to_be_committed().new.iter().count(), 3); + assert_eq!(status::changes_to_be_staged().new.iter().count(), 1); + + fs::remove_file("a.txt").unwrap(); //删除a.txt + fs::remove_dir_all("test").unwrap(); //删除test文件夹 + assert_eq!(status::changes_to_be_staged().deleted.iter().count(), 2); + + restore(vec![".".to_string()], None, true, false); //restore . //from index + assert_eq!(status::changes_to_be_committed().new.iter().count(), 3); + assert_eq!(status::changes_to_be_staged().new.iter().count(), 1); + assert_eq!(status::changes_to_be_staged().deleted.iter().count(), 0); + } } diff --git a/src/utils/util.rs b/src/utils/util.rs index bf6bf40..99dc3c5 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -3,7 +3,6 @@ use std::{ collections::HashSet, fs, io, io::Write, - option, path::{Path, PathBuf}, }; @@ -13,29 +12,30 @@ pub const ROOT_DIR: &str = ".mit"; pub const TEST_DIR: &str = "mit_test_storage"; // 执行测试的储存库 /* tools for test */ +fn find_cargo_dir() -> PathBuf { + let cargo_path = std::env::var("CARGO_MANIFEST_DIR"); + if cargo_path.is_err() { + // vscode DEBUG test没有CARGO_MANIFEST_DIR宏,手动尝试查找cargo.toml + let mut path = cur_dir(); + loop { + path.push("Cargo.toml"); + if path.exists() { + break; + } + if !path.pop() { + panic!("找不到CARGO_MANIFEST_DIR"); + } + } + path.pop(); + path + } else { + PathBuf::from(cargo_path.unwrap()) + } +} + fn setup_test_dir() { color_backtrace::install(); // colorize backtrace - let cargo_path = std::env::var("CARGO_MANIFEST_DIR"); - let path: PathBuf = { - if cargo_path.is_err() { - // vscode DEBUG test没有CARGO_MANIFEST_DIR宏,手动尝试查找cargo.toml - let mut path = std::env::current_dir().unwrap(); - loop { - path.push("Cargo.toml"); - if path.exists() { - break; - } - if !path.pop() { - panic!("找不到CARGO_MANIFEST_DIR"); - } - } - path.pop(); - path - } else { - PathBuf::from(cargo_path.unwrap()) - } - }; - let mut path = PathBuf::from(path); + let mut path = find_cargo_dir(); path.push(TEST_DIR); if !path.exists() { fs::create_dir(&path).unwrap(); @@ -59,7 +59,32 @@ pub fn setup_test_without_mit() { } } -pub fn ensure_test_file(path: &Path, content: option::Option<&str>) { +pub fn ensure_test_files>(paths: &Vec) { + for path in paths { + ensure_test_file(path.as_ref().as_ref(), None); + } +} + +pub fn ensure_empty_dir>(path: P) -> io::Result<()> { + let entries = fs::read_dir(path.as_ref())?; + for entry in entries { + let path = entry?.path(); + if path.is_dir() { + fs::remove_dir_all(&path)?; // 如果是目录,则递归删除 + } else { + fs::remove_file(&path)?; // 如果是文件,则直接删除 + } + } + Ok(()) +} + +pub fn setup_test_with_empty_workdir() { + let test_dir = find_cargo_dir().join(TEST_DIR); + ensure_empty_dir(&test_dir).unwrap(); + setup_test_with_clean_mit(); +} + +pub fn ensure_test_file(path: &Path, content: Option<&str>) { // 以测试目录为根目录,创建文件 fs::create_dir_all(path.parent().unwrap()).unwrap(); // ensure父目录 let mut file =