From b568e879253e73e20bf91ee91164f9944a1e75a8 Mon Sep 17 00:00:00 2001 From: mrbeanc Date: Wed, 27 Dec 2023 16:19:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86Index=E6=94=B9=E4=B8=BA=E5=8D=95?= =?UTF-8?q?=E4=BE=8B=E6=A8=A1=E5=BC=8F=EF=BC=8C=E9=98=B2=E6=AD=A2=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=B8=8D=E4=B8=80=E8=87=B4=EF=BC=9B=20=E6=B3=A8?= =?UTF-8?q?=E6=84=8F=EF=BC=9A=E6=B5=8B=E8=AF=95=E4=B8=BA=E5=8D=95=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=EF=BC=8C=E9=9C=80=E8=A6=81reset=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E5=8D=95=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 ++ src/commands/add.rs | 6 +++--- src/commands/commit.rs | 6 +++--- src/commands/remove.rs | 2 +- src/commands/restore.rs | 8 ++++---- src/commands/status.rs | 4 ++-- src/models/commit.rs | 2 +- src/models/index.rs | 45 ++++++++++++++++++++++++++++++----------- src/models/tree.rs | 8 ++++---- src/utils/util.rs | 21 ++++++++++++------- 10 files changed, 67 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 826a87b..14a7e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,5 @@ serde_json = "1.0.108" colored = "2.1.0" rand = "0.8.5" color-backtrace = "0.6.1" +once_cell = "1.19.0" +backtrace = "0.3.69" \ No newline at end of file diff --git a/src/commands/add.rs b/src/commands/add.rs index 8ab1092..2a1b8a0 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -31,13 +31,12 @@ pub fn add(raw_paths: Vec, all: bool, mut update: bool) { println!("{}", "--update 只对已跟踪文件进行操作 不包含new".bright_green()); } - let mut index = Index::new(); for file in &files { - add_a_file(file, &mut index); + add_a_file(file); } } -fn add_a_file(file: &Path, index: &mut Index) { +fn add_a_file(file: &Path) { let workdir = util::get_working_dir().unwrap(); if !util::is_sub_path(file, &workdir) { //文件不在工作区内 @@ -50,6 +49,7 @@ fn add_a_file(file: &Path, index: &mut Index) { return; } + let index = Index::get_instance(); let rel_path = util::to_cur_relative_path(file); if !file.exists() { //文件被删除 diff --git a/src/commands/commit.rs b/src/commands/commit.rs index e3a6151..de4a43c 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -3,7 +3,7 @@ use crate::{head, models::*}; use super::status; pub fn commit(message: String, allow_empty: bool) { - let index = Index::new(); + let index = Index::get_instance(); if !allow_empty && status::changes_to_be_committed().is_empty() { panic!("工作区没有任何改动,不需要提交"); } @@ -13,9 +13,9 @@ pub fn commit(message: String, allow_empty: bool) { let mut commit = { if current_commit_hash.is_empty() { - Commit::new(&index, vec![], message.clone()) + Commit::new(index, vec![], message.clone()) } else { - Commit::new(&index, vec![current_commit_hash.clone()], message.clone()) + Commit::new(index, vec![current_commit_hash.clone()], message.clone()) } }; let commit_hash = commit.save(); diff --git a/src/commands/remove.rs b/src/commands/remove.rs index a575ead..2ff50b6 100644 --- a/src/commands/remove.rs +++ b/src/commands/remove.rs @@ -5,7 +5,7 @@ use std::{fs, io, path::PathBuf}; /// 从暂存区&|工作区删除文件 pub fn remove(files: Vec, cached: bool, recursive: bool) -> io::Result<()> { util::check_repo_exist(); - let mut index = Index::new(); + let index = Index::get_instance(); for file in files.iter() { let path = PathBuf::from(file); if !path.exists() { diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 3d1ccfa..6a5a1a3 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -64,7 +64,7 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB let mut file_paths = util::integrate_paths(&input_paths); //根据用户输入整合存在的文件(绝对路径) file_paths.extend(deleted_files); //已删除的文件 - let index = Index::new(); + let index = Index::get_instance(); let store = Store::new(); for path in &file_paths { @@ -103,7 +103,7 @@ pub fn restore_index(filter: Option<&Vec>, target_blobs: &Vec<(PathBuf, let input_paths = preprocess_filters(filter); //预处理filter 将None转化为workdir let target_blobs = preprocess_blobs(target_blobs); //预处理target_blobs 转化为绝对路径HashMap - let mut index = Index::new(); + let index = Index::get_instance(); let deleted_files_index = get_index_deleted_files_in_filters(&index, &input_paths, &target_blobs); //统计已删除的文件 //1.获取index中包含于input_path的文件(使用paths进行过滤) @@ -181,7 +181,7 @@ pub fn restore(paths: Vec, source: Option, worktree: bool, stage otherwise from the [index].*/ if source.is_none() && !staged { // 没有指定source,且没有指定--staged,从[index]中恢复到worktree //只有这种情况是从[index]恢复 - let entries = Index::new().get_tracked_entries(); + let entries = Index::get_instance().get_tracked_entries(); entries.into_iter().map(|(p, meta)| (p, meta.hash)).collect() } else { //从[target_commit]中恢复 @@ -224,7 +224,7 @@ mod test { util::ensure_no_file(&path); cmd::add(vec![], true, false); //add -A cmd::restore(vec![".".to_string()], Some("HEAD".to_string()), false, true); - let index = Index::new(); + let index = Index::get_instance(); assert!(index.get_tracked_files().is_empty()); } diff --git a/src/commands/status.rs b/src/commands/status.rs index adcfd1e..cc78a69 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -70,7 +70,7 @@ impl Changes { */ pub fn changes_to_be_committed() -> Changes { let mut change = Changes::default(); - let index = Index::new(); + let index = Index::get_instance(); let head_hash = head::current_head_commit(); let tracked_files = index .get_tracked_files() @@ -111,7 +111,7 @@ pub fn changes_to_be_committed() -> Changes { /// 比较工作区与暂存区的差异,返回相对路径(to workdir),不筛选 pub fn changes_to_be_staged() -> Changes { let mut change = Changes::default(); - let index = Index::new(); + let index = Index::get_instance(); for file in index.get_tracked_files() { if !file.exists() { change.deleted.push(util::to_workdir_relative_path(&file)); diff --git a/src/models/commit.rs b/src/models/commit.rs index 2b55d32..c42353d 100644 --- a/src/models/commit.rs +++ b/src/models/commit.rs @@ -88,7 +88,7 @@ mod test { fn test_commit() { util::setup_test_with_clean_mit(); - let index = super::Index::new(); + let index = super::Index::get_instance(); let mut commit = super::Commit::new(&index, vec!["123".to_string(), "456".to_string()], "test".to_string()); assert_eq!(commit.hash.len(), 0); diff --git a/src/models/index.rs b/src/models/index.rs index 28f16b1..4d973ca 100644 --- a/src/models/index.rs +++ b/src/models/index.rs @@ -1,4 +1,5 @@ use crate::{models::*, utils::util}; +use once_cell::unsync::Lazy; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -43,7 +44,8 @@ impl FileMetaData { } /** Index -注意:逻辑处理均为绝对路径,但是存储时为相对路径 +注意:逻辑处理均为绝对路径,但是存储时为相对路径(to workdir)
+理解 Git index 文件 */ #[derive(Serialize, Deserialize, Debug, Default)] pub struct Index { @@ -53,8 +55,7 @@ pub struct Index { impl Index { /// 从index文件加载 - pub(crate) fn new() -> Index { - //TODO! 设计为单例模式 + fn new() -> Index { let mut index = Index { entries: HashMap::new(), working_dir: util::get_working_dir().unwrap(), @@ -63,26 +64,44 @@ impl Index { return index; } + /// 单例模式,线程不安全,但是本程序默认单线程 + pub fn get_instance() -> &'static mut Index { + static mut INSTANCE: Lazy = Lazy::new(Index::new); //延迟初始化,线程不安全 + unsafe { &mut INSTANCE } + } + + /// 重置index,主要用于测试,防止单例模式的影响 + pub fn reset() { + let index = Index::get_instance(); + index.clear(); + *index = Index::new(); //drop happened 导致旧数据写入文件 + } + + fn clear(&mut self) { + self.entries.clear(); + self.working_dir.clear(); + } + /// 预处理路径,统一形式为绝对路径 - fn preprocess_path(path: &Path) -> PathBuf { + fn preprocess(path: &Path) -> PathBuf { util::get_absolute_path(&path) } // 添加文件 pub fn add(&mut self, mut path: PathBuf, data: FileMetaData) { - path = Index::preprocess_path(&path); + path = Index::preprocess(&path); self.entries.insert(path, data); } // 删除文件 pub fn remove(&mut self, path: &Path) { - let path = Index::preprocess_path(&path); + let path = Index::preprocess(&path); self.entries.remove(&path); } // 获取文件元数据 pub fn get(&self, path: &Path) -> Option { - let path = Index::preprocess_path(path); + let path = Index::preprocess(path); self.entries.get(&path).cloned() } @@ -96,7 +115,7 @@ impl Index { } pub fn contains(&self, path: &Path) -> bool { - let path = Index::preprocess_path(path); + let path = Index::preprocess(path); self.entries.contains_key(&path) } @@ -134,7 +153,7 @@ impl Index { } pub fn update(&mut self, mut path: PathBuf, data: FileMetaData) { - path = Index::preprocess_path(&path); + path = Index::preprocess(&path); self.entries.insert(path, data); } @@ -151,6 +170,8 @@ impl Index { (abs_path, value) }) .collect(); + } else { + // println!("index文件不存在,创建空index"); } } @@ -161,7 +182,7 @@ impl Index { path } - /// 二进制序列化 + /// 保存到文件 pub fn save(&mut self) { //要先转化为相对路径 let relative_index: HashMap = self @@ -214,14 +235,14 @@ mod tests { #[test] fn test_load() { util::setup_test_with_clean_mit(); - let index = Index::new(); + let index = Index::get_instance(); println!("{:?}", index); } #[test] fn test_save() { util::setup_test_with_clean_mit(); - let mut index = Index::new(); + let index = Index::get_instance(); let path = PathBuf::from("../mit_test_storage/.mit/HEAD"); //测试../相对路径的处理 index.add(path.clone(), FileMetaData::new(&Blob::new(&path), &path)); diff --git a/src/models/tree.rs b/src/models/tree.rs index 2187b16..b9b3c6e 100644 --- a/src/models/tree.rs +++ b/src/models/tree.rs @@ -162,7 +162,7 @@ mod test { #[test] fn test_new() { util::setup_test_with_clean_mit(); - let mut index = Index::new(); + let index = Index::get_instance(); for test_file in vec!["b.txt", "mit_src/a.txt", "test/test.txt"] { let test_file = PathBuf::from(test_file); util::ensure_test_file(&test_file, None); @@ -178,7 +178,7 @@ mod test { #[test] fn test_load() { util::setup_test_with_clean_mit(); - let mut index = Index::new(); + let index = Index::get_instance(); let test_files = vec!["b.txt", "mit_src/a.txt"]; for test_file in test_files.clone() { let test_file = PathBuf::from(test_file); @@ -198,7 +198,7 @@ mod test { #[test] fn test_get_recursive_file_entries() { util::setup_test_with_clean_mit(); - let mut index = Index::new(); + 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() { util::ensure_test_file(&test_file, None); @@ -226,7 +226,7 @@ mod test { #[test] fn test_get_recursive_blobs() { util::setup_test_with_clean_mit(); - let mut index = Index::new(); + let index = Index::get_instance(); let test_files = vec!["b.txt", "mit_src/a.txt"]; let mut test_blobs = vec![]; for test_file in test_files.clone() { diff --git a/src/utils/util.rs b/src/utils/util.rs index 99dc3c5..9013d9b 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -6,7 +6,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::models::{commit::Commit, object::Hash, tree::Tree}; +use crate::models::{commit::Commit, object::Hash, tree::Tree, Index}; pub const ROOT_DIR: &str = ".mit"; pub const TEST_DIR: &str = "mit_test_storage"; // 执行测试的储存库 @@ -33,26 +33,33 @@ fn find_cargo_dir() -> PathBuf { } } -fn setup_test_dir() { +/// 准备测试环境,切换到测试目录 +fn setup_test_env() { color_backtrace::install(); // colorize backtrace + let mut path = find_cargo_dir(); path.push(TEST_DIR); if !path.exists() { fs::create_dir(&path).unwrap(); } - std::env::set_current_dir(&path).unwrap(); + std::env::set_current_dir(&path).unwrap(); // 将执行目录切换到测试目录 +} + +pub fn init_mit() { + let _ = crate::commands::init(); + Index::reset(); // 重置index, 以防止其他测试修改了index单例 } /// with 初始化的干净的mit pub fn setup_test_with_clean_mit() { setup_test_without_mit(); - let _ = crate::commands::init::init(); + init_mit(); } pub fn setup_test_without_mit() { // 将执行目录切换到测试目录,并清除测试目录下的.mit目录 - setup_test_dir(); - let mut path = std::env::current_dir().unwrap(); + setup_test_env(); + let mut path = cur_dir(); path.push(ROOT_DIR); if path.exists() { fs::remove_dir_all(&path).unwrap(); @@ -632,7 +639,7 @@ mod tests { ensure_test_file(Path::new("test.txt"), Some("test")); let hash = Blob::new(get_working_dir().unwrap().join("test.txt").as_path()).get_hash(); assert_eq!(check_object_type(hash), ObjectType::Blob); - let mut commit = Commit::new(&Index::new(), vec![], "test".to_string()); + let mut commit = Commit::new(&Index::get_instance(), vec![], "test".to_string()); assert_eq!(check_object_type(commit.get_tree_hash()), ObjectType::Tree); commit.save(); assert_eq!(check_object_type(commit.get_hash()), ObjectType::Commit);