From 5f1d1156580525160b3994c541ef36a09fa65f4d Mon Sep 17 00:00:00 2001 From: mrbeanc Date: Fri, 22 Dec 2023 00:09:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=EF=BC=9Astatus=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=20TODO=EF=BC=9Aadd=20=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rustfmt.toml | 2 +- src/cli.rs | 6 +++ src/commands/add.rs | 4 +- src/commands/status.rs | 109 +++++++++++++++++++++++++++++++++++------ src/models/index.rs | 7 ++- src/utils/util.rs | 20 +++++++- 6 files changed, 128 insertions(+), 20 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index cdaa36e..dc9c3ee 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -3,7 +3,7 @@ version = "Two" use_small_heuristics = "Max" max_width = 120 struct_lit_width = 60 -chain_width = 60 +chain_width = 80 single_line_if_else_max_width = 60 single_line_let_else_max_width = 60 diff --git a/src/cli.rs b/src/cli.rs index 67216cd..c064157 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,6 +4,7 @@ use mit::commands::commit::commit; use mit::commands::init::init; use mit::commands::log::log; use mit::commands::remove::remove; +use mit::commands::status::status; /// Rust实现的简易版本的Git,用于学习Rust语言 #[derive(Parser)] @@ -51,6 +52,8 @@ enum Command { #[clap(long, action)] allow_empty: bool, }, + /// 查看当前状态 + Status, /// log 现实提交历史 Log { #[clap(short = 'A', long)] @@ -75,6 +78,9 @@ pub fn handle_command() { Command::Commit { message, allow_empty } => { commit(message, allow_empty); } + Command::Status => { + status(); + } Command::Log { all, number } => { log(all, number); } diff --git a/src/commands/add.rs b/src/commands/add.rs index 8b31e12..6a4b54b 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -10,7 +10,6 @@ use crate::utils::util::{ ROOT_DIR, }; -// TODO: fatal: ../moj/app.py: '../moj/app.py' is outside repository at 'Git-Rust' pub fn add(files: Vec, all: bool, mut update: bool) { check_repo_exist(); let mut index = Index::new(); @@ -53,7 +52,8 @@ pub fn add(files: Vec, all: bool, mut update: bool) { } fn add_a_file(file: &Path, index: &mut Index) { - if !is_inside_workdir(file) { + //TODO 文件不存在会报错 + if !is_inside_workdir(file) && file.exists() { //文件不在工作区内 println!("fatal: '{}' is outside repository at '{}'", file.display(), get_working_dir().unwrap().display()); return; diff --git a/src/commands/status.rs b/src/commands/status.rs index 5c4d6b4..74622cc 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -1,13 +1,14 @@ +use crate::head::Head; +use crate::models::commit::Commit; +use crate::models::index::Index; +use crate::utils::util::check_repo_exist; +use crate::{head, utils::util}; +use colored::Colorize; use std::path::PathBuf; -use crate::utils::util::to_workdir_absolute_path; -use crate::{ - head, - models::{commit, index}, - utils::util, -}; - -/** 获取需要commit的更改(staged) */ +/** 获取需要commit的更改(staged) + 注:相对路径 +*/ #[derive(Debug, Default)] pub struct Changes { pub new: Vec, @@ -15,9 +16,15 @@ pub struct Changes { pub deleted: Vec, } +impl Changes { + pub fn is_empty(&self) -> bool { + self.new.is_empty() && self.modified.is_empty() && self.deleted.is_empty() + } +} + pub fn changes_to_be_committed() -> Changes { let mut change = Changes::default(); - let index = index::Index::new(); + let index = Index::new(); let head_hash = head::current_head_commit(); let tracked_files = index .get_tracked_files() @@ -30,7 +37,7 @@ pub fn changes_to_be_committed() -> Changes { return change; } - let commit = commit::Commit::load(&head_hash); + let commit = Commit::load(&head_hash); let tree = commit.get_tree(); let tree_files = tree.get_recursive_blobs(); //相对路径 let index_files: Vec = tracked_files; @@ -38,7 +45,7 @@ pub fn changes_to_be_committed() -> Changes { for (tree_file, blob_hash) in tree_files.iter() { let index_file = index_files.iter().find(|&f| f == tree_file); if let Some(index_file) = index_file { - let index_path = to_workdir_absolute_path(index_file); + let index_path = util::to_workdir_absolute_path(index_file); if !index.verify_hash(&index_path, blob_hash) { change.modified.push(tree_file.clone()); } @@ -55,8 +62,29 @@ pub fn changes_to_be_committed() -> Changes { change } -pub fn changes_to_be_staged() { - unimplemented!() +pub fn changes_to_be_staged() -> Changes { + let mut change = Changes::default(); + let index = Index::new(); + for file in index.get_tracked_files() { + //TODO 考虑当前目录 + if !file.exists() { + change.deleted.push(util::to_workdir_relative_path(&file)); + } else if index.is_modified(&file) { + // 若文件元数据被修改,才需要比较暂存区与文件的hash来判别内容修改 + if !index.verify_hash(&file, &util::calc_file_hash(&file)) { + change.modified.push(util::to_workdir_relative_path(&file)); + } + } + } + let files = util::list_workdir_files(); //TODO 考虑当前目录 + for file in files { + if !index.tracked(&file) { + //文件未被跟踪 + change.new.push(util::to_workdir_relative_path(&file)); + } + } + + change } /** 分为两个部分 @@ -64,7 +92,60 @@ pub fn changes_to_be_staged() { 2. staged to be committed: 暂存区与HEAD(最后一次Commit::Tree)比较,即上次的暂存区 */ pub fn status() { - unimplemented!() + check_repo_exist(); + //TODO: 输出文件与当前所在目录有关 输出时过滤 + match head::current_head() { + Head::Detached(commit) => { + println!("HEAD detached at {}", commit[0..7].to_string()); + } + Head::Branch(branch) => { + println!("On branch {}", branch); + } + } + + let staged = changes_to_be_committed(); + let unstaged = changes_to_be_staged(); + if staged.is_empty() && unstaged.is_empty() { + println!("nothing to commit, working tree clean"); + return; + } + + if !staged.is_empty() { + println!("Changes to be committed:"); + staged.deleted.iter().for_each(|f| { + let str = format!("\tdeleted: {}", f.display()); + println!("{}", str.bright_green()); + }); + staged.modified.iter().for_each(|f| { + let str = format!("\tmodified: {}", f.display()); + println!("{}", str.bright_green()); + }); + staged.new.iter().for_each(|f| { + let str = format!("\tnew file: {}", f.display()); + println!("{}", str.bright_green()); + }); + } + + if !unstaged.deleted.is_empty() || !unstaged.modified.is_empty() { + println!("Changes not staged for commit:"); + println!(" use \"mit add ...\" to update what will be committed"); + unstaged.deleted.iter().for_each(|f| { + let str = format!("\tdeleted: {}", f.display()); + println!("{}", str.bright_red()); + }); + unstaged.modified.iter().for_each(|f| { + let str = format!("\tmodified: {}", f.display()); + println!("{}", str.bright_red()); + }); + } + if !unstaged.new.is_empty() { + println!("Untracked files:"); + println!(" use \"mit add ...\" to include in what will be committed"); + unstaged.new.iter().for_each(|f| { + let str = format!("\t{}", f.display()); + println!("{}", str.bright_red()); + }); + } } #[cfg(test)] diff --git a/src/models/index.rs b/src/models/index.rs index adb34b8..dd7aef5 100644 --- a/src/models/index.rs +++ b/src/models/index.rs @@ -90,6 +90,11 @@ impl Index { self.entries.contains_key(&path) } + /// 检查文件是否被跟踪, same as [Index::contains] + pub fn tracked(&self, path: &Path) -> bool { + self.contains(path) + } + /// 与暂存区比较,获取工作区中被删除的文件 pub fn get_deleted_files(&self) -> Vec { let mut files = Vec::new(); @@ -172,7 +177,7 @@ impl Index { impl Drop for Index { fn drop(&mut self) { self.save(); - println!("{}", "Index auto saved".bright_green()); + // println!("{}", "Index auto saved".bright_green()); } } diff --git a/src/utils/util.rs b/src/utils/util.rs index a7b50d9..1827ee9 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -67,8 +67,7 @@ pub fn ensure_test_file(path: &Path, content: option::Option<&str>) { file.write(content.as_bytes()).unwrap(); } else { // 写入文件名 - file.write(path.file_name().unwrap().to_str().unwrap().as_bytes()) - .unwrap(); + file.write(path.file_name().unwrap().to_str().unwrap().as_bytes()).unwrap(); } } @@ -80,6 +79,12 @@ pub fn calc_hash(data: &String) -> String { hex::encode(hash) } +/// 计算文件的hash +pub fn calc_file_hash(path: &Path) -> String { + let data = fs::read_to_string(path).expect(&format!("无法读取文件:{}", path.display())); + calc_hash(&data) +} + pub fn storage_exist() -> bool { /*检查是否存在储存库 */ let rt = get_storage_path(); @@ -91,6 +96,7 @@ pub fn storage_exist() -> bool { pub fn check_repo_exist() { if !storage_exist() { + println!("fatal: not a mit repository (or any of the parent directories): .mit"); panic!("不是合法的mit仓库"); } } @@ -172,6 +178,15 @@ pub fn list_files(path: &Path) -> io::Result> { Ok(files) } +/// 列出工作区所有文件(包括子文件夹) +pub fn list_workdir_files() -> Vec { + if let Ok(files) = list_files(&get_working_dir().unwrap()) { + files + } else { + Vec::new() + } +} + /// 获取相对于dir的相对路径 pub fn get_relative_path(path: &Path, dir: &Path) -> PathBuf { let path = if path.is_relative() { @@ -252,6 +267,7 @@ pub fn clean_win_abs_path_pre(path: PathBuf) -> PathBuf { /// 获取绝对路径(相对于当前current_dir) pub fn get_absolute_path(path: &Path) -> PathBuf { + //TODO 不能处理不存在的文件 if path.is_absolute() { path.to_path_buf() } else {