restore: 实现restore_worktree()

This commit is contained in:
mrbeanc
2023-12-22 21:02:22 +08:00
parent d719cc43da
commit 1e16179e0a
9 changed files with 121 additions and 27 deletions

View File

@@ -147,11 +147,16 @@ pub fn handle_command() {
switch(branch, create, detach);
}
Command::Restore { path, mut source, mut worktree, staged } => {
// 未指定stage和worktree时默认操作stage
// 未指定stage和worktree时默认操作worktree
// 指定 --staged 将仅还原index
if !staged {
worktree = true;
}
// 未指定source时默认操作HEAD
/*TODO
If `--source` not specified, the contents are restored from `HEAD` if `--staged` is given,
otherwise from the [index].
*/
if source.is_none() {
source = Some("HEAD".to_string());
}

View File

@@ -1,49 +1,91 @@
use std::fs;
use std::path::PathBuf;
use crate::models::index::Index;
use crate::utils::util;
use crate::utils::util::get_working_dir;
use crate::{
head,
models::{commit::Commit, object::Hash},
store::Store,
};
/** 根据filte restore workdir */
pub fn restore_workdir_into_files(filter: Option<PathBuf>, target_blobs: Vec<(PathBuf, Hash)>) {
// TODO
unimplemented!("TODO");
/** 根据filter restore workdir */
pub fn restore_worktree(filter: Option<&Vec<PathBuf>>, target_blobs: &Vec<(PathBuf, Hash)>) {
let paths: Vec<PathBuf> = if let Some(filter) = filter {
filter.clone()
} else {
vec![get_working_dir().unwrap()] //None == all(workdir), '.' == cur_dir
};
let paths = util::integrate_paths(&paths); // file paths
let target_blobs = target_blobs // 转为相对路径(cur_dir) 与filter统一 //TODO tree改变路径表示方式后这里需要修改
.iter()
.map(|(path, hash)| (util::to_workdir_relative_path(path), hash.clone()))
.collect::<Vec<(PathBuf, Hash)>>();
let index = Index::new();
let store = Store::new();
for (path, hash) in &target_blobs {
if path.exists() {
let file_hash = util::calc_file_hash(&path); //TODO tree没有存修改时间所以这里只能用hash判断
if file_hash == *hash {
continue; //文件未修改 不需要还原
}
}
//文件不存在或已修改
store.restore_to_file(hash, &path);
}
//处理工作区的新文件
for path in paths {
if target_blobs.iter().any(|(target_path, _)| target_path == &path) {
//TODO 最好返回HashMap 方便优化
continue; //已处理
}
//未找到则对于target_commit来说是新文件若已跟踪则删除若未跟踪则保留
if index.tracked(&path) {
fs::remove_file(&path).unwrap();
}
}
}
/** 根据filte restore staged */
pub fn restore_staged_into_files(filter: Option<PathBuf>, target_blobs: Vec<(PathBuf, Hash)>) {
pub fn restore_index(filter: Option<&Vec<PathBuf>>, target_blobs: &Vec<(PathBuf, Hash)>) {
// TODO
unimplemented!("TODO");
}
pub fn restore(path: Vec<String>, source: String, worktree: bool, staged: bool) {
/**
对于工作区中的新文件,若已跟踪,则删除;若未跟踪,则保留
*/
pub fn restore(paths: Vec<String>, source: String, worktree: bool, staged: bool) {
// TODO
let target_commit = {
let paths = paths.iter().map(PathBuf::from).collect::<Vec<PathBuf>>();
let target_commit: Hash = {
if source == "HEAD" {
//Default
head::current_head_commit()
} else if head::list_local_branches().contains(&source) {
// Branch Name, e.g. master
head::get_branch_head(&source)
} else {
// Commit Hash, e.g. a1b2c3d4
let store = Store::new();
let commit = store.search(&source);
if commit.is_none() {
println!("fatal: 非法的 commit: '{}'", source);
if commit.is_none() || !util::is_typeof_commit(commit.clone().unwrap()) {
println!("fatal: 非法的 commit hash: '{}'", source);
return;
}
commit.unwrap()
}
};
// TODO 处理筛选path的互相包含的情况
// 分别处理worktree和staged
let tree = Commit::load(&target_commit).get_tree();
let target_blobs = tree.get_recursive_blobs();
let target_blobs = tree.get_recursive_blobs(); // 相对路径
if worktree {
unimplemented!("TODO")
restore_worktree(Some(&paths), &target_blobs);
}
if staged {
unimplemented!("TODO")
restore_index(Some(&paths), &target_blobs);
}
unimplemented!("TODO");
}

View File

@@ -113,6 +113,7 @@ pub fn status() {
if !staged.is_empty() {
println!("Changes to be committed:");
println!(" use \"mit restore --staged <file>...\" to unstage");
staged.deleted.iter().for_each(|f| {
let str = format!("\tdeleted: {}", f.display());
println!("{}", str.bright_green());

View File

@@ -11,7 +11,7 @@ use crate::{
use super::{
branch,
restore::{restore_staged_into_files, restore_workdir_into_files},
restore::{restore_index, restore_worktree},
status::{changes_to_be_committed, changes_to_be_staged},
};
@@ -28,9 +28,9 @@ fn switch_to_commit(commit_hash: Hash) {
let target_files = tree.get_recursive_blobs(); // 相对路径
// 借用逻辑类似的restore_workdir_into_files
restore_workdir_into_files(None, target_files.clone());
restore_worktree(None, &target_files);
// 同时restore index
restore_staged_into_files(None, target_files);
restore_index(None, &target_files);
}
fn switch_to(branch: String, detach: bool) -> Result<(), SwitchErr> {

View File

@@ -52,6 +52,7 @@ pub fn delete_branch(branch_name: &String) {
/**返回当前head指向的commit hash如果是分支则返回分支的commit hash */
pub fn current_head_commit() -> String {
//TODO 明确返回Hash
let head = current_head();
match head {
Head::Branch(branch_name) => {

View File

@@ -46,6 +46,7 @@ pub struct Index {
impl Index {
/// 从index文件加载
pub(crate) fn new() -> Index {
//TODO! 设计为单例模式
let mut index = Index {
entries: HashMap::new(),
working_dir: util::get_working_dir().unwrap(),

View File

@@ -16,7 +16,7 @@ pub struct TreeEntry {
pub name: String, // file name
}
/// 相对路径
/// 相对路径(to workdir)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tree {
#[serde(skip)]
@@ -80,6 +80,7 @@ impl Tree {
.get_tracked_files()
.iter_mut()
.map(|file| util::to_workdir_relative_path(file))
//TODO! 改用相对路径(cur_dir)或者绝对路径相对workdir的路径会在外部造成迷惑且无法直接使用在save&load时转换参照index转换可抽象为util函数
.collect();
store_path_to_tree(&file_entries, "".into())
@@ -123,17 +124,18 @@ impl Tree {
files
}
///注:相对路径
///注:相对路径(to workdir)
pub fn get_recursive_blobs(&self) -> Vec<(PathBuf, Hash)> {
let mut blob_hashs = Vec::new();
//TODO 返回HashMap
let mut blob_hashes = Vec::new();
for entry in self.entries.iter() {
if entry.filemode.0 == "blob" {
blob_hashs.push((PathBuf::from(entry.name.clone()), entry.object_hash.clone()));
blob_hashes.push((PathBuf::from(entry.name.clone()), entry.object_hash.clone()));
} else {
let sub_tree = Tree::load(&entry.object_hash);
let sub_blobs = sub_tree.get_recursive_blobs();
blob_hashs.append(
blob_hashes.append(
sub_blobs
.iter()
.map(|(path, blob_hash)| (PathBuf::from(entry.name.clone()).join(path), blob_hash.clone()))
@@ -142,7 +144,7 @@ impl Tree {
);
}
}
blob_hashs
blob_hashes
}
}

View File

@@ -4,6 +4,7 @@ use crate::models::object::Hash;
use super::utils::util;
/// 管理.mit仓库的读写
pub struct Store {
store_path: PathBuf,
}
@@ -32,6 +33,12 @@ impl Store {
path.exists()
}
/// 将hash对应的文件内容(主要是blob)还原到file
pub fn restore_to_file(&self, hash: &Hash, file: &PathBuf) {
let content = self.load(hash);
std::fs::write(file, content).unwrap();
}
/** 根据前缀搜索,有歧义时返回 None */
pub fn search(&self, hash: &String) -> Option<Hash> {
if hash.is_empty() {

View File

@@ -300,6 +300,23 @@ pub fn get_absolute_path(path: &Path) -> PathBuf {
abs_path
}
}
/// 整理输入的路径数组(相对、绝对、文件、目录、甚至包括不存在),返回一个绝对路径的文件数组
pub fn integrate_paths(paths: &Vec<PathBuf>) -> Vec<PathBuf> {
let mut abs_paths = Vec::new();
for path in paths {
let path = get_absolute_path(&path); // 统一转换为绝对路径
if path.is_dir() {
// 包括目录下的所有文件(子文件夹)
let files = list_files(&path).unwrap();
abs_paths.extend(files);
} else {
abs_paths.push(path);
}
}
abs_paths.sort();
abs_paths.dedup(); // 去重
abs_paths
}
#[derive(Debug, PartialEq)]
pub enum ObjectType {
@@ -309,9 +326,9 @@ pub enum ObjectType {
Invalid,
}
pub fn check_object_type(hash: Hash) -> ObjectType {
let path = get_storage_path().unwrap().join("objects").join(hash.to_string());
let path = get_storage_path().unwrap().join("objects").join(hash);
if path.exists() {
let data = fs::read_to_string(path).unwrap();
let data = fs::read_to_string(path).unwrap(); //TODO store::load?
let result: Result<Commit, serde_json::Error> = serde_json::from_str(&data);
if result.is_ok() {
return ObjectType::Commit;
@@ -325,6 +342,11 @@ pub fn check_object_type(hash: Hash) -> ObjectType {
ObjectType::Invalid
}
/// 判断hash对应的文件是否是commit
pub fn is_typeof_commit(hash: Hash) -> bool {
check_object_type(hash) == ObjectType::Commit
}
#[cfg(test)]
mod tests {
use crate::models::{blob::Blob, index::Index};
@@ -343,6 +365,19 @@ mod tests {
}
}
#[test]
fn test_integrate_paths() {
let mut paths = Vec::new();
paths.push(PathBuf::from("src/utils"));
paths.push(PathBuf::from("../test_del.txt"));
paths.push(PathBuf::from("src/utils/util.rs"));
// paths.push(PathBuf::from("."));
let abs_paths = integrate_paths(&paths);
for path in abs_paths {
println!("{}", path.display());
}
}
#[test]
fn test_get_absolute_path() {
let path = Path::new("mit_test_storage/../src/main.rs");