mirror of
https://github.com/MrBeanCpp/MIT.git
synced 2026-04-13 15:20:01 +08:00
restore: 实现restore_worktree()
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user