diff --git a/git-rs b/git-rs deleted file mode 160000 index 5f3bb36..0000000 --- a/git-rs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5f3bb36574167640943af9b73e9adb65fd14f4ac diff --git a/src/commands/add.rs b/src/commands/add.rs index 67c16b9..edba0d1 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -1,12 +1,13 @@ use std::env; use std::path::{Path, PathBuf}; use colored::Colorize; -use crate::models::index::Index; -use crate::utils::util::{check_repo_exist, get_working_dir, list_files}; +use crate::models::blob::Blob; +use crate::models::index::{FileMetaData, Index}; +use crate::utils::util::{check_repo_exist, get_file_mode, get_relative_path, get_working_dir, list_files}; pub fn add(files: Vec, all: bool, mut update: bool) { check_repo_exist(); - let index = Index::new(); + let mut index = Index::new(); let mut dot = files.contains(&".".to_string()); let mut files: Vec = files.into_iter().map(PathBuf::from).collect(); @@ -33,13 +34,30 @@ pub fn add(files: Vec, all: bool, mut update: bool) { index.contains(file) }); } + + files.extend(index.get_deleted_files()); //包含已删除的文件 } - for file in files { - add_a_file(file.as_path(), &index); + for file in &files { + add_a_file(file, &mut index); } } -fn add_a_file(file: &Path, index: &Index) { - println!("add a file: {:?}", file); +fn add_a_file(file: &Path, index: &mut Index) { + println!("add a file: {}", get_relative_path(file, get_working_dir().unwrap()).display()); + if !file.exists() { //文件被删除 + index.remove(file); + } else if !index.contains(file) { //文件未被跟踪 + let blob = Blob::new(file); + let meta = file.metadata().unwrap(); + index.add(file.to_path_buf(), FileMetaData{ + hash: blob.get_hash(), + size: meta.len(), + created_time: meta.created().unwrap(), + modified_time: meta.modified().unwrap(), + mode: get_file_mode(file) + }); + } else { //文件已被跟踪,可能被修改 + + } } \ No newline at end of file diff --git a/src/models/blob.rs b/src/models/blob.rs index dd5c452..14dc96a 100644 --- a/src/models/blob.rs +++ b/src/models/blob.rs @@ -1,9 +1,25 @@ +use std::fs; +use std::path::Path; use crate::models::object::Hash; -/*Blob -* Blob是git中最基本的对象,他储存一份文件的内容,并使用hash作为标识符。 +use crate::utils::util::calc_hash; + +/** + Blob +Blob是git中最基本的对象,他储存一份文件的内容,并使用hash作为标识符。 */ #[derive(Debug, Clone)] pub struct Blob { hash: Hash, data: String, } + +impl Blob { + pub fn new(file: &Path) -> Blob { + let data = fs::read_to_string(file).unwrap(); + let hash = calc_hash(&data); + Blob { hash, data } + } + pub fn get_hash(&self) -> String { + self.hash.clone() + } +} \ No newline at end of file diff --git a/src/models/index.rs b/src/models/index.rs index 4d41f1d..916d62a 100644 --- a/src/models/index.rs +++ b/src/models/index.rs @@ -4,21 +4,23 @@ use std::path::{Path, PathBuf}; use std::time::SystemTime; use serde::{Deserialize, Serialize}; use crate::models::object::Hash; +use crate::utils::util::get_working_dir; // 文件元数据结构 #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FileMetaData { - hash: Hash, // SHA-1 哈希值 - size: u64, // 文件大小 - created_time: SystemTime, // 创建时间 - modified_time: SystemTime, // 修改时间 - mode: String, // 文件模式 + pub hash: Hash, // SHA-1 哈希值 + pub size: u64, // 文件大小 + pub created_time: SystemTime, // 创建时间 + pub modified_time: SystemTime, // 修改时间 + pub mode: String, // 文件模式 } // 索引数据结构 #[derive(Serialize, Deserialize, Debug, Default)] pub struct Index { entries: HashMap, + working_dir: PathBuf, } impl Index { @@ -26,19 +28,20 @@ impl Index { pub(crate) fn new() -> Index { let mut index = Index { entries: HashMap::new(), + working_dir: get_working_dir().unwrap() }; index.load(); return index; } // 添加文件 - fn add(&mut self, path: PathBuf, data: FileMetaData) { + pub fn add(&mut self, path: PathBuf, data: FileMetaData) { self.entries.insert(path, data); } // 删除文件 - fn remove(&mut self, path: PathBuf) { - self.entries.remove(&path); + pub fn remove(&mut self, path: &Path) { + self.entries.remove(path); } // 获取文件元数据 @@ -55,12 +58,23 @@ impl Index { self.entries.contains_key(path) } + /// 获取所有已删除的文件 + pub fn get_deleted_files(&self) -> Vec { + let mut files = Vec::new(); + self.entries.keys().for_each(|file| { + if !file.exists() { + files.push(file.clone()); + } + }); + files + } + fn load(&mut self) { } /// 二进制序列化 - fn save(&self) { + fn save(&self) { //要先转化为相对路径 let ser = serde_json::to_string(&self).unwrap(); println!("{}", ser); } diff --git a/src/utils/util.rs b/src/utils/util.rs index be84d92..749bafe 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -26,18 +26,18 @@ pub fn check_repo_exist() { } } -pub fn get_storage_path() -> Result { +pub fn get_storage_path() -> Result { /*递归获取储存库 */ let mut current_dir = std::env::current_dir()?; loop { let mut git_path = current_dir.clone(); git_path.push(ROOT_DIR); if git_path.exists() { - return Ok(git_path); + return Ok(git_path); } if !current_dir.pop() { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, + return Err(io::Error::new( + io::ErrorKind::NotFound, "Not a git repository", )); } @@ -46,7 +46,7 @@ pub fn get_storage_path() -> Result { /// 获取项目工作区目录, 也就是.mit的父目录 pub fn get_working_dir() -> Option { - if let Some(path) = PathBuf::from(get_storage_path().unwrap()).parent() { + if let Some(path) = get_storage_path().unwrap().parent() { Some(path.to_path_buf()) } else { None @@ -81,6 +81,38 @@ pub fn list_files(path: &Path) -> io::Result> { Ok(files) } +pub fn get_relative_path(path: &Path, dir: PathBuf) -> PathBuf { + let relative_path = path.strip_prefix(dir).unwrap(); + relative_path.to_path_buf() +} + +fn is_executable(path: &str) -> bool { + #[cfg(not(target_os = "windows"))] + { + use std::os::unix::fs::PermissionsExt; + fs::metadata(path) + .map(|metadata| metadata.permissions().mode() & 0o111 != 0) + .unwrap_or(false) + } + + #[cfg(windows)] + { + let path = Path::new(path); + match path.extension().and_then(|s| s.to_str()) { + Some(ext) => ext.eq_ignore_ascii_case("exe") || ext.eq_ignore_ascii_case("bat"), + None => false, + } + } +} + +pub fn get_file_mode(path: &Path) -> String { + if is_executable(path.to_str().unwrap()) { + "100755".to_string() + } else { + "100644".to_string() + } +} + #[cfg(test)] mod tests { use super::*;