Files
MIT/src/models/tree.rs

256 lines
9.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::{collections::HashSet, path::PathBuf};
use serde::{Deserialize, Serialize};
use crate::{store, utils::util};
use super::{index::Index, object::Hash};
/*Tree
* Tree是一个版本中所有文件的集合。从根目录还是每个目录是一个Tree每个文件是一个Blob。Tree之间互相嵌套表示文件的层级关系。
* 每一个Tree对象也是对应到git储存仓库的一个文件其内容是一个或多个TreeEntry。
*/
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TreeEntry {
pub filemode: (String, String), // (type, mode), type: blob or tree; mode: 100644 or 04000
pub object_hash: Hash, // blob hash or tree hash
pub name: String, // file name
}
/// 相对路径(to workdir)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tree {
#[serde(skip)]
pub hash: Hash,
pub entries: Vec<TreeEntry>,
}
/** 将文件列表保存为Tree Object并返回最上层的Tree */
fn store_path_to_tree(index: &Index, current_root: PathBuf) -> Tree {
let get_blob_entry = |path: &PathBuf| {
let mete = index.get(path).unwrap().clone();
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
let entry = TreeEntry {
filemode: (String::from("blob"), mete.mode),
object_hash: mete.hash,
name: filename,
};
entry
};
let mut tree = Tree { hash: "".to_string(), entries: Vec::new() };
let mut processed_path: HashSet<String> = HashSet::new();
let path_entries: Vec<PathBuf> = index
.get_tracked_files()
.iter()
.map(|file| util::to_workdir_relative_path(file))
.filter(|path| path.starts_with(&current_root))
.collect();
for path in path_entries.iter() {
// 判断是不是直接在根目录下
let in_path = path.parent().unwrap() == current_root;
// 一定是文件,不会是目录
if in_path {
let entry = get_blob_entry(path);
tree.entries.push(entry);
} else {
if path.components().count() == 1 {
continue;
}
// 拿到下一级别目录
let process_path = path
.components()
.nth(current_root.components().count())
.unwrap()
.as_os_str()
.to_str()
.unwrap();
// TODO 函数整体逻辑错误,等待修复@houxiaoxuan
if processed_path.contains(process_path) {
continue;
}
processed_path.insert(process_path.to_string());
let sub_tree = store_path_to_tree(index, current_root.clone().join(process_path));
let mode = util::get_file_mode(&util::get_working_dir().unwrap().join(process_path));
tree.entries.push(TreeEntry {
filemode: (String::from("tree"), mode),
object_hash: sub_tree.get_hash(),
name: process_path.to_string(),
});
}
}
tree.save();
tree
}
impl Tree {
pub fn get_hash(&self) -> String {
self.hash.clone()
}
pub fn new(index: &Index) -> Tree {
store_path_to_tree(index, "".into())
}
pub fn load(hash: &String) -> Tree {
let s = store::Store::new();
let tree_data = s.load(hash);
let mut tree: Tree = serde_json::from_str(&tree_data).unwrap();
tree.hash = hash.clone();
tree
}
pub fn save(&mut self) -> String {
let s = store::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<PathBuf> {
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::<Vec<PathBuf>>()
.as_mut(),
);
}
}
files
}
///注:相对路径(to workdir)
pub fn get_recursive_blobs(&self) -> Vec<(PathBuf, Hash)> {
//TODO 返回HashMap
let mut blob_hashes = Vec::new();
for entry in self.entries.iter() {
if entry.filemode.0 == "blob" {
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_hashes.append(
sub_blobs
.iter()
.map(|(path, blob_hash)| (PathBuf::from(entry.name.clone()).join(path), blob_hash.clone()))
.collect::<Vec<(PathBuf, Hash)>>()
.as_mut(),
);
}
}
blob_hashes
}
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use crate::{
models::{blob::Blob, index::FileMetaData},
utils::{
util,
util::{get_absolute_path, to_workdir_absolute_path},
},
};
#[test]
fn test_new() {
util::setup_test_with_clean_mit();
let mut index = super::Index::new();
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);
index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file));
index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file));
}
let tree = super::Tree::new(&index);
assert!(tree.entries.len() == 3);
assert!(tree.hash.len() != 0);
}
#[test]
fn test_load() {
util::setup_test_with_clean_mit();
let mut index = super::Index::new();
let test_files = vec!["b.txt", "mit_src/a.txt"];
for test_file in test_files.clone() {
let test_file = PathBuf::from(test_file);
util::ensure_test_file(&test_file, None);
index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file));
}
let tree = super::Tree::new(&index);
let tree_hash = tree.get_hash();
let loaded_tree = super::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() {
util::setup_test_with_clean_mit();
let mut index = super::Index::new();
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);
index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file));
}
let tree = super::Tree::new(&index);
let tree_hash = tree.get_hash();
let loaded_tree = super::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!(
to_workdir_absolute_path(&files[0]).to_str().unwrap(), //TODO 罪大恶极的路径问题
get_absolute_path(&test_files[0]).to_str().unwrap()
);
assert_eq!(
to_workdir_absolute_path(&files[1]).to_str().unwrap(),
get_absolute_path(&test_files[1]).to_str().unwrap()
);
}
#[test]
fn test_get_recursive_blobs() {
util::setup_test_with_clean_mit();
let mut index = super::Index::new();
let test_files = vec!["b.txt", "mit_src/a.txt"];
let mut test_blobs = vec![];
for test_file in test_files.clone() {
let test_file = PathBuf::from(test_file);
util::ensure_test_file(&test_file, None);
let blob = Blob::new(&test_file);
test_blobs.push(blob.clone());
index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file));
}
let tree = super::Tree::new(&index);
let tree_hash = tree.get_hash();
let loaded_tree = super::Tree::load(&tree_hash);
let blobs = loaded_tree.get_recursive_blobs();
assert!(blobs.len() == test_files.len());
assert!(blobs.contains(&(PathBuf::from(test_files[0]), test_blobs[0].get_hash())));
assert!(blobs.contains(&(PathBuf::from(test_files[1]), test_blobs[1].get_hash())));
}
}