From ebbc134117dfbc7178d5550df3697fc099e5039f Mon Sep 17 00:00:00 2001 From: mrbeanc Date: Fri, 29 Dec 2023 14:10:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E8=AD=A6=E5=91=8A=EF=BC=8C?= =?UTF-8?q?=E5=B0=86restore=E5=8A=9F=E8=83=BD=E8=BD=AC=E5=85=A5Blob?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/branch.rs | 4 +-- src/commands/merge.rs | 4 +-- src/commands/restore.rs | 11 ++++--- src/commands/switch.rs | 4 +-- src/models/blob.rs | 15 ++++++--- src/models/commit.rs | 10 +++--- src/models/index.rs | 13 ++------ src/models/tree.rs | 71 ++++++----------------------------------- src/utils/mod.rs | 1 + src/utils/store.rs | 10 ------ src/utils/test.rs | 4 +++ src/utils/util.rs | 53 ++++++++++-------------------- 12 files changed, 62 insertions(+), 138 deletions(-) diff --git a/src/commands/branch.rs b/src/commands/branch.rs index 889454e..0eee404 100644 --- a/src/commands/branch.rs +++ b/src/commands/branch.rs @@ -2,7 +2,7 @@ use colored::Colorize; use crate::{ models::*, - utils::{store, util}, + utils::{util, Store}, }; // branch error @@ -21,7 +21,7 @@ fn search_hash(commit_hash: Hash) -> Option { return Some(commit_hash); } // commit hash - let store = store::Store::new(); + let store = Store::new(); let commit = store.search(&commit_hash); commit } diff --git a/src/commands/merge.rs b/src/commands/merge.rs index 0b338c0..0be8d10 100644 --- a/src/commands/merge.rs +++ b/src/commands/merge.rs @@ -1,7 +1,7 @@ use crate::{ commands::{self, status::*}, models::{head, Commit, Hash}, - utils::{store, util}, + utils::{util, Store}, }; enum MergeErr { @@ -65,7 +65,7 @@ pub fn merge(branch: String) { head::get_branch_head(&branch) } else { // Commit Hash, e.g. a1b2c3d4 - let store = store::Store::new(); + let store = Store::new(); let commit = store.search(&branch); if commit.is_none() || !util::is_typeof_commit(commit.clone().unwrap()) { println!("fatal: 非法的 commit hash: '{}'", branch); diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 5c98e5b..fcea20d 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ models::*, - utils::{store, util}, + utils::{util, Store}, }; /// 统计[工作区]中相对于target_blobs已删除的文件(根据filters进行过滤) @@ -68,7 +68,6 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB file_paths.extend(deleted_files); //已删除的文件 let index = Index::get_instance(); - let store = store::Store::new(); for path in &file_paths { assert!(path.is_absolute()); // 绝对路径 @@ -76,7 +75,7 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB //文件不存在于workdir if target_blobs.contains_key(path) { //文件存在于target_commit (deleted),需要恢复 - store.restore_to_file(&target_blobs[path], &path); + Blob::load(&target_blobs[path]).restore(&path); } else { //在target_commit和workdir中都不存在(非法路径), 用户输入 println!("fatal: pathspec '{}' did not match any files", path.display()); @@ -87,7 +86,7 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB //文件已修改(modified) let file_hash = util::calc_file_hash(&path); //TODO tree没有存修改时间,所以这里只能用hash判断 if file_hash != target_blobs[path] { - store.restore_to_file(&target_blobs[path], &path); + Blob::load(&target_blobs[path]).restore(&path); } } else { //新文件,也分两种情况:1.已跟踪,需要删除 2.未跟踪,保留 @@ -167,7 +166,7 @@ pub fn restore(paths: Vec, source: Option, worktree: bool, stage head::get_branch_head(&src) // "" if not exist } else { // [Commit Hash, e.g. a1b2c3d4] || [Wrong Branch Name] - let store = store::Store::new(); + let store = Store::new(); let commit = store.search(&src); if commit.is_none() || !util::is_typeof_commit(commit.clone().unwrap()) { println!("fatal: 非法的 commit hash: '{}'", src); @@ -251,5 +250,7 @@ mod test { 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); + assert!(test::is_file_exist("a.txt")); + assert!(test::is_file_exist("test/in.txt")); } } diff --git a/src/commands/switch.rs b/src/commands/switch.rs index d46ad93..498cfb2 100644 --- a/src/commands/switch.rs +++ b/src/commands/switch.rs @@ -2,7 +2,7 @@ use colored::Colorize; use crate::{ models::{head, Commit, Hash}, - utils::{store, util}, + utils::{util, Store}, }; use super::{ @@ -41,7 +41,7 @@ fn switch_to(branch: String, detach: bool) -> Result<(), SwitchErr> { return Err(SwitchErr::NoClean); } - let store = store::Store::new(); + let store = Store::new(); if head::list_local_branches().contains(&branch) { // 切到分支 let branch_commit = head::get_branch_head(&branch); diff --git a/src/models/blob.rs b/src/models/blob.rs index e9a2acc..3acdf24 100644 --- a/src/models/blob.rs +++ b/src/models/blob.rs @@ -1,6 +1,6 @@ use crate::{ models::Hash, - utils::{store, util}, + utils::{util, Store}, }; use std::{fs, path::Path}; @@ -16,22 +16,29 @@ pub struct Blob { impl Blob { /// 从源文件新建blob对象,并直接保存到/objects/中 pub fn new(file: &Path) -> Blob { - let data = fs::read_to_string(file).expect(format!("无法读取文件:{:?}", file).as_str()); + let data = fs::read_to_string(file).expect("无法读取文件"); let hash = util::calc_hash(&data); let blob = Blob { hash, data }; blob.save(); blob } + /// 通过hash值加载blob(从/objects/) + #[allow(dead_code)] pub fn load(hash: &String) -> Blob { - let s = store::Store::new(); + let s = Store::new(); let data = s.load(hash); Blob { hash: hash.clone(), data } } + ///将hash对应的blob还原到file + pub fn restore(&self, file: &Path) { + util::write(file, &self.data).unwrap(); + } + /// 写入文件;优化:文件已存在时不做操作 pub fn save(&self) { - let s = store::Store::new(); + let s = Store::new(); if !s.contains(&self.hash) { let hash = s.save(&self.data); assert_eq!(hash, self.hash); diff --git a/src/models/commit.rs b/src/models/commit.rs index 5710f80..a9624ec 100644 --- a/src/models/commit.rs +++ b/src/models/commit.rs @@ -2,7 +2,7 @@ use std::time::SystemTime; use serde::{Deserialize, Serialize}; -use crate::utils::{store, util}; +use crate::utils::{util, Store}; use super::*; /*Commit @@ -29,7 +29,8 @@ impl Commit { pub fn get_date(&self) -> String { util::format_time(&self.date) } - pub fn get_tree_hash(&self) -> String { + #[allow(dead_code)] + pub fn get_tree_hash(&self) -> Hash { self.tree.clone() } pub fn get_tree(&self) -> Tree { @@ -44,6 +45,7 @@ impl Commit { pub fn get_author(&self) -> String { self.author.clone() } + #[allow(dead_code)] pub fn get_committer(&self) -> String { self.committer.clone() } @@ -63,7 +65,7 @@ impl Commit { } pub fn load(hash: &String) -> Commit { - let s = store::Store::new(); + let s = Store::new(); let commit_data = s.load(hash); let mut commit: Commit = serde_json::from_str(&commit_data).unwrap(); commit.hash = hash.clone(); @@ -72,7 +74,7 @@ impl Commit { pub fn save(&mut self) -> String { // unimplemented!() - let s = store::Store::new(); + let s = Store::new(); let commit_data = serde_json::to_string_pretty(&self).unwrap(); let hash = s.save(&commit_data); self.hash = hash.clone(); diff --git a/src/models/index.rs b/src/models/index.rs index 964b7d3..5a17d8c 100644 --- a/src/models/index.rs +++ b/src/models/index.rs @@ -68,6 +68,7 @@ impl Index { } /// 重置index,主要用于测试,防止单例模式的影响 + #[cfg(test)] pub fn reload() { let index = Index::get_instance(); index.load(); @@ -115,17 +116,6 @@ impl Index { self.contains(path) } - /// 与暂存区比较,获取工作区中被删除的文件 - pub fn get_deleted_files(&self, dir: &Path) -> Vec { - let mut files = Vec::new(); - self.entries.keys().for_each(|file| { - if !file.exists() && util::is_sub_path(file, dir) { - files.push(file.clone()); - } - }); - files - } - /// 与暂存区比较,确定文件自上次add以来是否被编辑(内容不一定修改,还需要算hash) pub fn is_modified(&self, file: &Path) -> bool { if let Some(self_data) = self.get(file) { @@ -201,6 +191,7 @@ impl Index { self.entries.clone() } + #[cfg(test)] pub fn is_empty(&self) -> bool { self.entries.is_empty() } diff --git a/src/models/tree.rs b/src/models/tree.rs index 76dabb1..539b118 100644 --- a/src/models/tree.rs +++ b/src/models/tree.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; -use crate::utils::{store, util}; +use crate::utils::{util, Store}; use super::{Hash, Index}; /*Tree @@ -92,7 +92,7 @@ impl Tree { } pub fn load(hash: &String) -> Tree { - let s = store::Store::new(); + let s = Store::new(); let tree_data = s.load(hash); let mut tree: Tree = serde_json::from_str(&tree_data).unwrap(); tree.hash = hash.clone(); @@ -100,35 +100,13 @@ impl Tree { } pub fn save(&mut self) -> String { - let s = store::Store::new(); + let s = Store::new(); let tree_data = serde_json::to_string_pretty(&self).unwrap(); let hash = s.save(&tree_data); self.hash = hash.clone(); hash } - /**递归获取Tree对应的所有文件 */ - pub fn get_recursive_file_entries(&self) -> Vec { - let mut files = Vec::new(); - for entry in self.entries.iter() { - if entry.filemode.0 == "blob" { - files.push(PathBuf::from(entry.name.clone())); - } else { - let sub_tree = Tree::load(&entry.object_hash); - let sub_files = sub_tree.get_recursive_file_entries(); - - files.append( - sub_files - .iter() - .map(|file| PathBuf::from(entry.name.clone()).join(file)) - .collect::>() - .as_mut(), - ); - } - } - files - } - ///注:相对路径(to workdir) pub fn get_recursive_blobs(&self) -> Vec<(PathBuf, Hash)> { //TODO 返回HashMap @@ -157,10 +135,7 @@ impl Tree { mod test { use std::path::PathBuf; - use crate::{ - models::*, - utils::{test, util}, - }; + use crate::{models::*, utils::test}; #[test] fn test_new() { @@ -174,8 +149,8 @@ mod test { } let tree = Tree::new(&index); - assert!(tree.entries.len() == 3); - assert!(tree.hash.len() != 0); + assert_eq!(tree.entries.len(), 3); + assert_ne!(tree.hash.len(), 0); } #[test] @@ -193,37 +168,9 @@ mod test { let tree_hash = tree.get_hash(); let loaded_tree = Tree::load(&tree_hash); - assert!(loaded_tree.entries.len() == tree.entries.len()); - assert!(tree.entries[0].name == loaded_tree.entries[0].name); - assert!(tree.entries[1].name == loaded_tree.entries[1].name); - } - - #[test] - fn test_get_recursive_file_entries() { - test::setup_with_clean_mit(); - let index = Index::get_instance(); - let mut test_files = vec![PathBuf::from("b.txt"), PathBuf::from("mit_src/a.txt")]; - for test_file in test_files.clone() { - test::ensure_file(&test_file, None); - index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file)); - } - - let tree = Tree::new(&index); - let tree_hash = tree.get_hash(); - - let loaded_tree = Tree::load(&tree_hash); - let mut files = loaded_tree.get_recursive_file_entries(); - files.sort(); - test_files.sort(); - assert_eq!(files.len(), test_files.len()); - assert_eq!( - util::to_workdir_absolute_path(&files[0]).to_str().unwrap(), //TODO 罪大恶极的路径问题 - util::get_absolute_path(&test_files[0]).to_str().unwrap() - ); - assert_eq!( - util::to_workdir_absolute_path(&files[1]).to_str().unwrap(), - util::get_absolute_path(&test_files[1]).to_str().unwrap() - ); + assert_eq!(loaded_tree.entries.len(), tree.entries.len()); + assert_eq!(tree.entries[0].name, loaded_tree.entries[0].name); + assert_eq!(tree.entries[1].name, loaded_tree.entries[1].name); } #[test] diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 381e0a1..91ff23b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,4 @@ pub mod store; +pub use store::Store; pub mod test; pub mod util; diff --git a/src/utils/store.rs b/src/utils/store.rs index 5299172..a8df75d 100644 --- a/src/utils/store.rs +++ b/src/utils/store.rs @@ -33,16 +33,6 @@ impl Store { path.exists() } - /// 将hash对应的文件内容(主要是blob)还原到file - pub fn restore_to_file(&self, hash: &Hash, file: &PathBuf) { - let content = self.load(hash); - // 保证文件层次存在 - let mut parent = file.clone(); - parent.pop(); - std::fs::create_dir_all(parent).unwrap(); - std::fs::write(file, content).unwrap(); - } - /** 根据前缀搜索,有歧义时返回 None */ pub fn search(&self, hash: &String) -> Option { if hash.is_empty() { diff --git a/src/utils/test.rs b/src/utils/test.rs index 039b0d8..9baa5e2 100644 --- a/src/utils/test.rs +++ b/src/utils/test.rs @@ -108,3 +108,7 @@ pub fn ensure_no_file(path: &Path) { fs::remove_file(util::get_working_dir().unwrap().join(path)).unwrap(); } } + +pub fn is_file_exist>(path: P) -> bool { + path.as_ref().exists() +} diff --git a/src/utils/util.rs b/src/utils/util.rs index eb2618e..71d24cc 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -67,6 +67,12 @@ pub fn get_working_dir() -> Option { } } +/// 将contents写入path,若文件不存在则创建,且确保父目录存在 +pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> { + fs::create_dir_all(path.as_ref().parent().unwrap())?; + fs::write(path, contents) +} + /// 检查文件是否在dir内(包括子文件夹), 若不存在则false pub fn is_inside_dir(file: &Path, dir: &Path) -> bool { if file.exists() { @@ -138,11 +144,6 @@ where .collect::() } -/// 检查文件是否在工作区内, 若不存在则false -pub fn is_inside_workdir(file: &Path) -> bool { - is_inside_dir(file, &get_working_dir().unwrap()) -} - /// 检查文件是否在.mit内, 若不存在则false pub fn is_inside_repo(file: &Path) -> bool { is_inside_dir(file, &get_storage_path().unwrap()) @@ -177,7 +178,8 @@ pub fn list_files(path: &Path) -> io::Result> { } /** 列出子文件夹 */ -pub fn list_subpath(path: &Path) -> io::Result> { +#[allow(dead_code)] +pub fn list_subdir(path: &Path) -> io::Result> { let mut files = Vec::new(); let path = get_absolute_path(path); if path.is_dir() { @@ -332,25 +334,6 @@ pub fn get_file_mode(path: &Path) -> String { } } -/// 清除Windows下的绝对路径前缀"\\\\?\\" (由[PathBuf::canonicalize]函数产生) -///
Windows 系统中的文件路径格式 -pub fn clean_win_abs_path_pre(path: PathBuf) -> PathBuf { - #[cfg(windows)] - { - const DOS_PREFIX: &str = "\\\\?\\"; - let path_str = path.to_string_lossy(); - if path_str.starts_with(DOS_PREFIX) { - PathBuf::from(&path_str[DOS_PREFIX.len()..]) - } else { - path - } - } - #[cfg(not(target_os = "windows"))] - { - path - } -} - /// 获取绝对路径(相对于目录current_dir) 不论是否存在 pub fn get_absolute_path(path: &Path) -> PathBuf { get_absolute_path_to_dir(path, &std::env::current_dir().unwrap()) @@ -434,6 +417,14 @@ mod tests { utils::{test, util::*}, }; + #[test] + fn test_write() { + test::setup_with_empty_workdir(); + let path = Path::new("src/test/a.txt"); + write(path, "test").unwrap(); + assert!(path.exists()); + } + #[test] fn test_get_storage_path() { let path = get_storage_path(); @@ -494,16 +485,6 @@ mod tests { assert_eq!(abs_path, cur_dir); } - #[test] - fn test_is_inside_repo() { - test::setup_with_clean_mit(); - let path = Path::new("../Cargo.toml"); - assert_eq!(is_inside_workdir(path), false); - - let path = Path::new(".mit/HEAD"); - assert_eq!(is_inside_workdir(path), true); - } - #[test] fn test_format_time() { let time = std::time::SystemTime::now(); @@ -550,7 +531,7 @@ mod tests { list_workdir_files().iter().for_each(|f| { fs::remove_file(f).unwrap(); }); - list_subpath(Path::new("./")).unwrap().iter().for_each(|f| { + list_subdir(Path::new("./")).unwrap().iter().for_each(|f| { if include_root_dir(f) { fs::remove_dir_all(f).unwrap(); }