diff --git a/src/cli.rs b/src/cli.rs index 66d85fc..97af558 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,5 @@ use clap::{ArgGroup, Parser, Subcommand}; -use mit::commands::{ - add::add, branch::branch, commit::commit, init::init, log::log, merge::merge, remove::remove, restore::restore, - status::status, switch::switch, -}; +use mit::commands as cmd; /// Rust实现的简易版本的Git,用于学习Rust语言 #[derive(Parser)] @@ -127,28 +124,28 @@ pub fn handle_command() { let cli = Cli::parse(); match cli.command { Command::Init => { - init().expect("初始化失败"); + cmd::init().expect("初始化失败"); } Command::Add { files, all, update } => { - add(files, all, update); + cmd::add(files, all, update); } Command::Rm { files, cached, recursive } => { - remove(files, cached, recursive).expect("删除失败"); + cmd::rm(files, cached, recursive).expect("删除失败"); } Command::Commit { message, allow_empty } => { - commit(message, allow_empty); + cmd::commit(message, allow_empty); } Command::Status => { - status(); + cmd::status(); } Command::Log { all, number } => { - log(all, number); + cmd::log(all, number); } Command::Branch { list, delete, new_branch, commit_hash, show_current } => { - branch(new_branch, commit_hash, list, delete, show_current); + cmd::branch(new_branch, commit_hash, list, delete, show_current); } Command::Switch { branch, create, detach } => { - switch(branch, create, detach); + cmd::switch(branch, create, detach); } Command::Restore { path, source, mut worktree, staged } => { // 未指定stage和worktree时,默认操作worktree @@ -161,10 +158,10 @@ pub fn handle_command() { If `--source` not specified, the contents are restored from `HEAD` if `--staged` is given, otherwise from the [index]. */ - restore(path, source, worktree, staged); + cmd::restore(path, source, worktree, staged); } Command::Merge { branch } => { - merge(branch); + cmd::merge(branch); } } } diff --git a/src/commands/add.rs b/src/commands/add.rs index e788ade..8ab1092 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -3,10 +3,8 @@ use std::path::{Path, PathBuf}; use colored::Colorize; use crate::commands::status; -use crate::models::{ - blob::Blob, - index::{FileMetaData, Index}, -}; +use crate::models::index::FileMetaData; +use crate::models::*; use crate::utils::util; /// add是对index的操作,不会对工作区产生影响 diff --git a/src/commands/branch.rs b/src/commands/branch.rs index 867a103..5643228 100644 --- a/src/commands/branch.rs +++ b/src/commands/branch.rs @@ -1,11 +1,6 @@ use colored::Colorize; -use crate::{ - head, - models::{commit::Commit, object::Hash}, - store, - utils::util, -}; +use crate::{head, models::*, store, utils::util}; // branch error enum BranchErr { @@ -38,8 +33,8 @@ fn create_branch(branch_name: String, _base_commit: Hash) -> Result<(), BranchEr let base_commit = Commit::load(&base_commit.unwrap()); - let exist_branchs = head::list_local_branches(); - if exist_branchs.contains(&branch_name) { + let exist_branches = head::list_local_branches(); + if exist_branches.contains(&branch_name) { println!("fatal: 分支 '{}' 已存在", branch_name); return Err(BranchErr::BranchExist); } diff --git a/src/commands/commit.rs b/src/commands/commit.rs index c17d6c2..e3a6151 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -1,12 +1,9 @@ -use crate::{ - head, - models::{commit, index}, -}; +use crate::{head, models::*}; use super::status; pub fn commit(message: String, allow_empty: bool) { - let index = index::Index::new(); + let index = Index::new(); if !allow_empty && status::changes_to_be_committed().is_empty() { panic!("工作区没有任何改动,不需要提交"); } @@ -16,9 +13,9 @@ pub fn commit(message: String, allow_empty: bool) { let mut commit = { if current_commit_hash.is_empty() { - commit::Commit::new(&index, vec![], message.clone()) + Commit::new(&index, vec![], message.clone()) } else { - commit::Commit::new(&index, vec![current_commit_hash.clone()], message.clone()) + Commit::new(&index, vec![current_commit_hash.clone()], message.clone()) } }; let commit_hash = commit.save(); @@ -40,7 +37,7 @@ pub fn commit(message: String, allow_empty: bool) { mod test { use std::path::Path; - use crate::{commands, head, models, utils::util}; + use crate::{commands as cmd, head, models, utils::util}; #[test] #[should_panic] @@ -58,8 +55,8 @@ mod test { assert!(head_one.is_empty()); util::ensure_test_file(&Path::new(test_file), "test content".into()); - commands::add::add(vec![], true, false); - commands::commit::commit("test commit 1".to_string(), true); + cmd::add(vec![], true, false); + cmd::commit("test commit 1".to_string(), true); let head_two = head::current_head_commit(); assert!(head_two.len() > 0); diff --git a/src/commands/log.rs b/src/commands/log.rs index 4b32fc9..d0063ec 100644 --- a/src/commands/log.rs +++ b/src/commands/log.rs @@ -1,4 +1,4 @@ -use crate::{head, models::commit::Commit}; +use crate::{head, models::Commit}; use colored::Colorize; const DEFAULT_LOG_NUMBER: usize = 10; @@ -8,7 +8,7 @@ pub fn log(all: bool, number: Option) { let _ = __log(all, number); } -pub fn __log(all: bool, number: Option) -> usize { +fn __log(all: bool, number: Option) -> usize { let mut log_count = 0usize; let head = head::current_head(); diff --git a/src/commands/merge.rs b/src/commands/merge.rs index 135a9d2..3192222 100644 --- a/src/commands/merge.rs +++ b/src/commands/merge.rs @@ -1,10 +1,7 @@ use crate::{ - commands::{ - self, - status::{changes_to_be_committed, changes_to_be_staged}, - }, + commands::{self, status::*}, head, - models::{commit::Commit, object::Hash}, + models::{Commit, Hash}, store::Store, utils::util, }; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f5c1f45..fbb2881 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,10 +1,20 @@ pub mod add; +pub use add::add; pub mod branch; +pub use branch::branch; pub mod commit; +pub use commit::commit; pub mod init; +pub use init::init; pub mod log; +pub use log::log; pub mod merge; +pub use merge::merge; pub mod remove; +pub use remove::remove as rm; pub mod restore; +pub use restore::restore; pub mod status; +pub use status::status; pub mod switch; +pub use switch::switch; diff --git a/src/commands/remove.rs b/src/commands/remove.rs index d3d5034..a575ead 100644 --- a/src/commands/remove.rs +++ b/src/commands/remove.rs @@ -1,13 +1,10 @@ -use crate::{ - models::index::Index, - utils::{util, util::check_repo_exist}, -}; +use crate::{models::Index, utils::util}; use colored::Colorize; use std::{fs, io, path::PathBuf}; /// 从暂存区&|工作区删除文件 pub fn remove(files: Vec, cached: bool, recursive: bool) -> io::Result<()> { - check_repo_exist(); + util::check_repo_exist(); let mut index = Index::new(); for file in files.iter() { let path = PathBuf::from(file); diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 343e675..3d1ccfa 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -4,16 +4,7 @@ use std::{ path::PathBuf, }; -use crate::{ - head, - models::{ - commit::Commit, - index::{FileMetaData, Index}, - object::Hash, - }, - store::Store, - utils::{util, util::get_working_dir}, -}; +use crate::{head, models::*, store::Store, utils::util}; /// 统计[工作区]中相对于target_blobs已删除的文件(根据filters进行过滤) fn get_worktree_deleted_files_in_filters( @@ -51,7 +42,7 @@ 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 + vec![util::get_working_dir().unwrap()] //None == all(workdir), '.' == cur_dir } } @@ -222,20 +213,17 @@ pub fn restore(paths: Vec, source: Option, worktree: bool, stage mod test { use std::fs; //TODO 写测试! - use std::path::PathBuf; - - use crate::commands::add::add; - use crate::commands::restore::restore; use crate::commands::status; - use crate::{models::index::Index, utils::util}; + use crate::{commands as cmd, models::Index, utils::util}; + use std::path::PathBuf; #[test] fn test_restore_stage() { util::setup_test_with_empty_workdir(); let path = PathBuf::from("a.txt"); util::ensure_no_file(&path); - add(vec![], true, false); //add -A - restore(vec![".".to_string()], Some("HEAD".to_string()), false, true); + cmd::add(vec![], true, false); //add -A + cmd::restore(vec![".".to_string()], Some("HEAD".to_string()), false, true); let index = Index::new(); assert!(index.get_tracked_files().is_empty()); } @@ -246,10 +234,10 @@ mod test { let files = vec!["a.txt", "b.txt", "c.txt", "test/in.txt"]; util::ensure_test_files(&files); - add(vec![], true, false); + cmd::add(vec![], true, false); assert_eq!(status::changes_to_be_committed().new.iter().count(), 4); - restore(vec!["c.txt".to_string()], None, false, true); //restore c.txt --staged + cmd::restore(vec!["c.txt".to_string()], None, false, true); //restore c.txt --staged assert_eq!(status::changes_to_be_committed().new.iter().count(), 3); assert_eq!(status::changes_to_be_staged().new.iter().count(), 1); @@ -257,7 +245,7 @@ mod test { fs::remove_dir_all("test").unwrap(); //删除test文件夹 assert_eq!(status::changes_to_be_staged().deleted.iter().count(), 2); - restore(vec![".".to_string()], None, true, false); //restore . //from index + cmd::restore(vec![".".to_string()], None, true, false); //restore . //from index assert_eq!(status::changes_to_be_committed().new.iter().count(), 3); assert_eq!(status::changes_to_be_staged().new.iter().count(), 1); assert_eq!(status::changes_to_be_staged().deleted.iter().count(), 0); diff --git a/src/commands/status.rs b/src/commands/status.rs index 9be5dc3..adcfd1e 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -1,8 +1,8 @@ use crate::{ head, head::Head, - models::{commit::Commit, index::Index}, - utils::{util, util::check_repo_exist}, + models::{Commit, Index}, + utils::util, }; use colored::Colorize; use std::path::PathBuf; @@ -137,7 +137,7 @@ pub fn changes_to_be_staged() -> Changes { 2. staged to be committed: 暂存区与HEAD(最后一次Commit::Tree)比较,即上次的暂存区 */ pub fn status() { - check_repo_exist(); + util::check_repo_exist(); match head::current_head() { Head::Detached(commit) => { println!("HEAD detached at {}", commit[0..7].to_string()); @@ -197,10 +197,7 @@ pub fn status() { #[cfg(test)] mod tests { use super::*; - use crate::{ - commands::{self, commit}, - utils::util, - }; + use crate::{commands as cmd, utils::util}; use std::path::Path; #[test] @@ -209,8 +206,8 @@ mod tests { let test_file = "a.txt"; util::ensure_test_file(Path::new(test_file), None); - commit::commit("test commit".to_string(), true); - commands::add::add(vec![test_file.to_string()], false, false); + cmd::commit("test commit".to_string(), true); + cmd::add(vec![test_file.to_string()], false, false); let change = changes_to_be_committed(); assert_eq!(change.new.len(), 1); assert_eq!(change.modified.len(), 0); @@ -218,9 +215,9 @@ mod tests { println!("{:?}", change.to_absolute()); - commit::commit("test commit".to_string(), true); + cmd::commit("test commit".to_string(), true); util::ensure_test_file(Path::new(test_file), Some("new content")); - commands::add::add(vec![test_file.to_string()], false, false); + cmd::add(vec![test_file.to_string()], false, false); let change = changes_to_be_committed(); assert_eq!(change.new.len(), 0); assert_eq!(change.modified.len(), 1); @@ -228,8 +225,8 @@ mod tests { println!("{:?}", change); - commit::commit("test commit".to_string(), true); - let _ = commands::remove::remove(vec![test_file.to_string()], false, false); + cmd::commit("test commit".to_string(), true); + let _ = cmd::rm(vec![test_file.to_string()], false, false); let change = changes_to_be_committed(); assert_eq!(change.new.len(), 0); assert_eq!(change.modified.len(), 0); diff --git a/src/commands/switch.rs b/src/commands/switch.rs index 28961f7..aa50bc8 100644 --- a/src/commands/switch.rs +++ b/src/commands/switch.rs @@ -2,7 +2,7 @@ use colored::Colorize; use crate::{ head::{self}, - models::{commit::Commit, object::Hash}, + models::{Commit, Hash}, store::Store, utils::util, }; @@ -10,7 +10,7 @@ use crate::{ use super::{ branch, restore::{restore_index, restore_worktree}, - status::{changes_to_be_committed, changes_to_be_staged}, + status, }; enum SwitchErr { @@ -33,10 +33,10 @@ fn switch_to_commit(commit_hash: Hash) { fn switch_to(branch: String, detach: bool) -> Result<(), SwitchErr> { // 检查更改 - if !changes_to_be_staged().is_empty() { + if !status::changes_to_be_staged().is_empty() { println!("fatal: 你有未暂存的更改,切换分支会导致更改丢失"); return Err(SwitchErr::NoClean); - } else if !changes_to_be_committed().is_empty() { + } else if !status::changes_to_be_committed().is_empty() { println!("fatal: 你有未提交的更改,无法切换分支"); return Err(SwitchErr::NoClean); } @@ -85,21 +85,16 @@ pub fn switch(target_branch: Option, create: Option, detach: boo #[cfg(test)] mod test { - use std::{fs, path::PathBuf}; - - use crate::commands::{self, status}; - use super::*; + use crate::commands::{self as cmd}; + use std::path::PathBuf; #[test] fn test_switch() { - util::setup_test_with_clean_mit(); - util::list_workdir_files().iter().for_each(|f| { - fs::remove_file(f).unwrap(); - }); + util::setup_test_with_empty_workdir(); - commands::commit::commit("init".to_string(), true); + cmd::commit("init".to_string(), true); let test_branch_1 = "test_branch_1".to_string(); - commands::branch::branch(Some(test_branch_1.clone()), None, false, None, false); + cmd::branch(Some(test_branch_1.clone()), None, false, None, false); /* test 1: NoClean */ let test_file_1 = PathBuf::from("test_file_1"); @@ -108,10 +103,10 @@ mod test { assert!(result.is_err()); assert!(matches!(result.unwrap_err(), SwitchErr::NoClean)); - commands::add::add(vec![], true, false); // add all - commands::commit::commit("add file 1".to_string(), true); + cmd::add(vec![], true, false); // add all + cmd::commit("add file 1".to_string(), true); let test_branch_2 = "test_branch_2".to_string(); - commands::branch::branch(Some(test_branch_2.clone()), None, false, None, false); // branch2: test_file_1 exists + cmd::branch(Some(test_branch_2.clone()), None, false, None, false); // branch2: test_file_1 exists /* test 2: InvalidBranch */ let result = switch_to("invalid_branch".to_string(), false); @@ -125,14 +120,14 @@ mod test { let tees_file_2 = PathBuf::from("test_file_2"); util::ensure_test_file(&tees_file_2, None); - commands::add::add(vec![], true, false); // add all - commands::commit::commit("add file 2".to_string(), false); + cmd::add(vec![], true, false); // add all + cmd::commit("add file 2".to_string(), false); let history_commit = head::current_head_commit(); // commit: test_file_1 exists, test_file_2 exists util::ensure_no_file(&test_file_1); - commands::add::add(vec![], true, false); // add all + cmd::add(vec![], true, false); // add all assert!(!test_file_1.exists()); - commands::commit::commit("delete file 1".to_string(), false); + cmd::commit("delete file 1".to_string(), false); let branch_master = match head::current_head()/* master: test_file_1 not exists, test_file_2 exists */{ head::Head::Branch(branch) => branch, _ => panic!("current head is not branch"), diff --git a/src/head.rs b/src/head.rs index 513a070..8e7d1cd 100644 --- a/src/head.rs +++ b/src/head.rs @@ -1,4 +1,4 @@ -use crate::{models::object::Hash, utils::util}; +use crate::{models::Hash, utils::util}; pub enum Head { Detached(String), diff --git a/src/lib.rs b/src/lib.rs index 493a380..28ecdc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ // 不使用lib.rs的话,就无法在tests里引用到src中的模块 pub mod commands; -mod head; +pub mod head; pub mod models; -mod store; +pub mod store; pub mod utils; diff --git a/src/main.rs b/src/main.rs index 1156d39..20a6322 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ mod cli; fn main() { color_backtrace::install(); // colorize backtrace - cli::handle_command(); } diff --git a/src/models/blob.rs b/src/models/blob.rs index 2e943b6..aebcd45 100644 --- a/src/models/blob.rs +++ b/src/models/blob.rs @@ -1,4 +1,4 @@ -use crate::{models::object::Hash, store::Store, utils::util::calc_hash}; +use crate::{models::Hash, store::Store, utils::util}; use std::{fs, path::Path}; /**Blob
@@ -14,7 +14,7 @@ impl Blob { /// 从源文件新建blob对象,并直接保存到/objects/中 pub fn new(file: &Path) -> Blob { let data = fs::read_to_string(file).expect(format!("无法读取文件:{:?}", file).as_str()); - let hash = calc_hash(&data); + let hash = util::calc_hash(&data); let blob = Blob { hash, data }; blob.save(); blob diff --git a/src/models/commit.rs b/src/models/commit.rs index 89323b3..2b55d32 100644 --- a/src/models/commit.rs +++ b/src/models/commit.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{store, utils::util}; -use super::{index::Index, object::Hash, tree::Tree}; +use super::*; /*Commit * git中版本控制的单位。 * 一份Commit中对应一份版Tree,记录了该版本所包含的文件;parent记录本次commit的来源,形成了版本树; diff --git a/src/models/index.rs b/src/models/index.rs index f7a2274..28f16b1 100644 --- a/src/models/index.rs +++ b/src/models/index.rs @@ -1,7 +1,4 @@ -use crate::{ - models::{blob::Blob, object::Hash}, - utils::{util, util::get_relative_path}, -}; +use crate::{models::*, utils::util}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -171,7 +168,7 @@ impl Index { .entries .iter() .map(|(path, value)| { - let relative_path = get_relative_path(path, &self.working_dir); + let relative_path = util::get_relative_path(path, &self.working_dir); (relative_path, value.clone()) }) .collect(); diff --git a/src/models/mod.rs b/src/models/mod.rs index 5691ebb..7245fad 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,5 +1,11 @@ pub mod blob; +pub use blob::Blob; pub mod commit; +pub use commit::Commit; pub mod index; +pub use index::FileMetaData; +pub use index::Index; pub mod object; +pub use object::Hash; pub mod tree; +pub use tree::Tree; diff --git a/src/models/tree.rs b/src/models/tree.rs index c1b8f81..2187b16 100644 --- a/src/models/tree.rs +++ b/src/models/tree.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{store, utils::util}; -use super::{index::Index, object::Hash}; +use super::{Hash, Index}; /*Tree * Tree是一个版本中所有文件的集合。从根目录还是,每个目录是一个Tree,每个文件是一个Blob。Tree之间互相嵌套表示文件的层级关系。 * 每一个Tree对象也是对应到git储存仓库的一个文件,其内容是一个或多个TreeEntry。 @@ -157,18 +157,12 @@ impl Tree { mod test { use std::path::PathBuf; - use crate::{ - models::{blob::Blob, index::FileMetaData}, - utils::{ - util, - util::{get_absolute_path, to_workdir_absolute_path}, - }, - }; + use crate::{models::*, utils::util}; #[test] fn test_new() { util::setup_test_with_clean_mit(); - let mut index = super::Index::new(); + let mut index = 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); @@ -176,7 +170,7 @@ mod test { index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file)); } - let tree = super::Tree::new(&index); + let tree = Tree::new(&index); assert!(tree.entries.len() == 3); assert!(tree.hash.len() != 0); } @@ -184,7 +178,7 @@ mod test { #[test] fn test_load() { util::setup_test_with_clean_mit(); - let mut index = super::Index::new(); + let mut index = 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); @@ -192,10 +186,10 @@ mod test { index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file)); } - let tree = super::Tree::new(&index); + let tree = Tree::new(&index); let tree_hash = tree.get_hash(); - let loaded_tree = super::Tree::load(&tree_hash); + let loaded_tree = 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); @@ -204,35 +198,35 @@ mod test { #[test] fn test_get_recursive_file_entries() { util::setup_test_with_clean_mit(); - let mut index = super::Index::new(); + let mut index = 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 = Tree::new(&index); let tree_hash = tree.get_hash(); - let loaded_tree = super::Tree::load(&tree_hash); + let loaded_tree = 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() + util::to_workdir_absolute_path(&files[0]).to_str().unwrap(), //TODO 罪大恶极的路径问题 + util::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() + util::to_workdir_absolute_path(&files[1]).to_str().unwrap(), + util::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 mut index = Index::new(); let test_files = vec!["b.txt", "mit_src/a.txt"]; let mut test_blobs = vec![]; for test_file in test_files.clone() { @@ -243,10 +237,10 @@ mod test { index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file)); } - let tree = super::Tree::new(&index); + let tree = Tree::new(&index); let tree_hash = tree.get_hash(); - let loaded_tree = super::Tree::load(&tree_hash); + let loaded_tree = 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()))); diff --git a/src/store.rs b/src/store.rs index 5f8ee31..0fb52c5 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::models::object::Hash; +use crate::models::Hash; use super::utils::util;