From d4009cd2cfc52cdd95dc56be1be3985d94da06d0 Mon Sep 17 00:00:00 2001 From: mrbeanc Date: Sat, 23 Dec 2023 16:10:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=9E=E7=8E=B0restore?= =?UTF-8?q?=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/add.rs | 1 + src/commands/restore.rs | 128 ++++++++++++++++++++++++++++++++-------- src/models/index.rs | 12 ++++ src/utils/util.rs | 17 ++++-- 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/src/commands/add.rs b/src/commands/add.rs index c082882..a7c3991 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -24,6 +24,7 @@ pub fn add(files: Vec, all: bool, mut update: bool) { let mut files: Vec = files.into_iter().map(PathBuf::from).collect(); if dot || all || update { + //TODO files中可能包含文件夹,需要统计文件夹中被删除的文件 if all { // all 优先级最高 dot = false; diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 1b914ee..1a39cbf 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::{fs, path::PathBuf}; -use crate::utils::util::{get_absolute_path, list_files}; +use crate::models::index::FileMetaData; use crate::{ head, models::{commit::Commit, index::Index, object::Hash}, @@ -9,25 +9,14 @@ use crate::{ utils::{util, util::get_working_dir}, }; -/** 根据filter restore workdir */ -pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathBuf, Hash)>) { - let paths: Vec = if let Some(filter) = filter { - filter.clone() - } else { - vec![get_working_dir().unwrap()] //None == all(workdir), '.' == cur_dir - }; - - let target_blobs = target_blobs // 转为绝对路径 //TODO tree改变路径表示方式后,这里需要修改 - .iter() - .map(|(path, hash)| (util::to_workdir_absolute_path(path), hash.clone())) - .collect::>(); - - let dirs: Vec = paths.iter().filter(|path| path.is_dir()).cloned().collect(); - let del_files = target_blobs //统计所有目录中(包括None & '.'),删除的文件 +/// 统计[工作区]中的dirs文件夹中,相对于target_blobs已删除的文件 +fn get_worktree_deleted_files_in_dirs(dirs: &Vec, target_blobs: &HashMap) -> HashSet { + target_blobs //统计所有目录中(包括None & '.'),删除的文件 .iter() .filter(|(path, _)| { + assert!(path.is_absolute()); // if !path.exists() { - for dir in &dirs { + for dir in dirs { if util::is_parent_dir(path, dir) { //需要包含在指定dir内 return true; @@ -37,23 +26,76 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB false }) .map(|(path, _)| path.clone()) - .collect::>(); //HashSet自动去重 - let mut paths = util::integrate_paths(&paths); //存在的文件路径 - paths.extend(del_files); //不存在的文件路径 + .collect() //HashSet自动去重 +} + +/// 统计[暂存区index]中相对于target_blobs已删除的文件,且包含在指定dirs内 +fn get_index_deleted_files_in_dirs( + index: &Index, + dirs: &Vec, + target_blobs: &HashMap, +) -> HashSet { + target_blobs //统计index中相对target已删除的文件,且包含在指定dir内 + .iter() + .filter(|(path, _)| { + assert!(path.is_absolute()); // + if !index.contains(path) { + //index中不存在 + for dir in dirs { + if util::is_parent_dir(path, dir) { + //需要包含在指定dir内 + return true; + } + } + } + false + }) + .map(|(path, _)| path.clone()) + .collect() //HashSet自动去重 +} + +/// 将None转化为workdir +fn preprocess_filters(filters: Option<&Vec>) -> Vec { + if let Some(filter) = filters { + filter.clone() + } else { + vec![get_working_dir().unwrap()] //None == all(workdir), '.' == cur_dir + } +} + +/// 转化为绝对路径(to workdir)的HashMap +fn preprocess_blobs(blobs: &Vec<(PathBuf, Hash)>) -> HashMap { + blobs // 转为绝对路径 //TODO tree改变路径表示方式后,这里需要修改 + .iter() + .map(|(path, hash)| (util::to_workdir_absolute_path(path), hash.clone())) + .collect() //to HashMap +} + +/** 根据filter restore workdir */ +pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathBuf, Hash)>) { + let paths = preprocess_filters(filter); //预处理filter 将None转化为workdir + let target_blobs = preprocess_blobs(target_blobs); //预处理target_blobs 转化为绝对路径HashMap + + let dirs = util::filter_dirs(&paths); //统计所有目录 + let deleted_files = get_worktree_deleted_files_in_dirs(&dirs, &target_blobs); //统计所有目录中已删除的文件 + + let mut file_paths = util::integrate_paths(&paths); //整合存在的文件(绝对路径) + file_paths.extend(deleted_files); //已删除的文件 let index = Index::new(); let store = Store::new(); - for path in &paths { + for path in &file_paths { assert!(path.is_absolute() && !path.is_dir()); // 绝对路径且不是目录 if !path.exists() { //文件不存在于workdir if target_blobs.contains_key(path) { - //文件存在于target_commit + //文件存在于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()); + // println!("fatal: pathspec '{}' did not match any files", path.display()); + // TODO 如果是用户输入的路径,才应该报错,integrate_paths产生的不应该报错 } } else { //文件存在,有两种情况:1.修改 2.新文件 @@ -73,10 +115,44 @@ pub fn restore_worktree(filter: Option<&Vec>, target_blobs: &Vec<(PathB } } } -/** 根据filte restore staged */ +/** 根据filter restore staged */ pub fn restore_index(filter: Option<&Vec>, target_blobs: &Vec<(PathBuf, Hash)>) { - // TODO 让@mrbeanc来写吧 - unimplemented!("TODO"); + 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 dirs = util::filter_dirs(&input_paths); //统计所有目录 + let deleted_files_index = get_index_deleted_files_in_dirs(&index, &dirs, &target_blobs); //统计所有目录中已删除的文件 + + let mut file_paths = util::integrate_paths(&input_paths); //整合存在的文件(绝对路径) + file_paths.extend(deleted_files_index); //已删除的文件 + + for path in &file_paths { + assert!(path.is_absolute() && !path.is_dir()); // 绝对路径且不是目录 + if !index.contains(path) { + //文件不存在于index + if target_blobs.contains_key(path) { + //文件存在于target_commit (deleted),需要恢复 + 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产生的不应该报错 + } + } else { + //文件存在于index,有两种情况:1.修改 2.新文件 + if target_blobs.contains_key(path) { + if !index.verify_hash(path, &target_blobs[path]) { + //文件已修改(modified) + index.update(path.clone(), FileMetaData { hash: target_blobs[path].clone(), ..Default::default() }); + } + } else { + //新文件 需要从index中删除 + index.remove(path); + } + } + } } /** 对于工作区中的新文件,若已跟踪,则删除;若未跟踪,则保留
diff --git a/src/models/index.rs b/src/models/index.rs index 5299671..bc3e089 100644 --- a/src/models/index.rs +++ b/src/models/index.rs @@ -20,6 +20,18 @@ pub struct FileMetaData { pub mode: String, // 文件模式 } +impl Default for FileMetaData { + fn default() -> Self { + FileMetaData { + hash: Default::default(), + size: Default::default(), + created_time: SystemTime::now(), // 或者使用 UNIX_EPOCH + modified_time: SystemTime::now(), // 或者使用 UNIX_EPOCH + mode: Default::default(), + } + } +} + impl FileMetaData { pub fn new(blob: &Blob, file: &Path) -> FileMetaData { let meta = file.metadata().unwrap(); diff --git a/src/utils/util.rs b/src/utils/util.rs index 376bcfa..695c5cc 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -1,4 +1,5 @@ use sha1::{Digest, Sha1}; +use std::collections::HashSet; use std::{ fs, io, io::Write, @@ -148,6 +149,7 @@ pub fn is_inside_dir(file: &Path, dir: &Path) -> bool { /// 检测dir是否是file的父目录 (不论文件是否存在) pub fn is_parent_dir(file: &Path, dir: &Path) -> bool { + assert!(dir.is_dir()); let file = get_absolute_path(file); let dir = get_absolute_path(dir); file.starts_with(dir) @@ -168,6 +170,11 @@ pub fn format_time(time: &std::time::SystemTime) -> String { datetime.format("%Y-%m-%d %H:%M:%S.%3f").to_string() } +/// 过滤出路径数组中的目录 +pub fn filter_dirs(paths: &Vec) -> Vec { + paths.iter().filter(|path| path.is_dir()).cloned().collect() +} + /// 将路径中的分隔符统一为当前系统的分隔符 fn unify_path_separator(path: &Path) -> PathBuf { #[cfg(windows)] @@ -315,9 +322,9 @@ pub fn get_absolute_path(path: &Path) -> PathBuf { abs_path } } -/// 整理输入的路径数组(相对、绝对、文件、目录),返回一个绝对路径的文件数组 -pub fn integrate_paths(paths: &Vec) -> Vec { - let mut abs_paths = Vec::new(); +/// 整理输入的路径数组(相对、绝对、文件、目录),返回一个绝对路径的文件数组(只包含exist) +pub fn integrate_paths(paths: &Vec) -> HashSet { + let mut abs_paths = HashSet::new(); for path in paths { let path = get_absolute_path(&path); // 统一转换为绝对路径 if path.is_dir() { @@ -325,11 +332,9 @@ pub fn integrate_paths(paths: &Vec) -> Vec { let files = list_files(&path).unwrap(); abs_paths.extend(files); } else { - abs_paths.push(path); + abs_paths.insert(path); } } - abs_paths.sort(); - abs_paths.dedup(); // 去重 abs_paths }