mirror of
https://github.com/MrBeanCpp/MIT.git
synced 2026-03-25 22:40:52 +08:00
git branch实现&测试,show类型需要结合checkout测试
This commit is contained in:
29
src/cli.rs
29
src/cli.rs
@@ -1,5 +1,6 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{ArgGroup, Parser, Subcommand};
|
||||
use mit::commands::add::add;
|
||||
use mit::commands::branch::branch;
|
||||
use mit::commands::commit::commit;
|
||||
use mit::commands::init::init;
|
||||
use mit::commands::log::log;
|
||||
@@ -55,6 +56,7 @@ enum Command {
|
||||
/// 查看当前状态
|
||||
Status,
|
||||
/// log 现实提交历史
|
||||
#[clap(group = ArgGroup::new("sub").required(false))]
|
||||
Log {
|
||||
#[clap(short = 'A', long)]
|
||||
all: bool,
|
||||
@@ -62,6 +64,28 @@ enum Command {
|
||||
#[clap(short, long)]
|
||||
number: Option<usize>,
|
||||
},
|
||||
/// branch
|
||||
Branch {
|
||||
/// 新分支名
|
||||
#[clap(group = "sub")]
|
||||
new_branch: Option<String>,
|
||||
|
||||
/// 基于某个commit创建分支
|
||||
#[clap(requires = "new_branch")]
|
||||
commit_hash: Option<String>,
|
||||
|
||||
/// 列出所有分支
|
||||
#[clap(short, long, action, group = "sub", default_value = "true")]
|
||||
list: bool,
|
||||
|
||||
/// 删除制定分支,不能删除当前所在分支
|
||||
#[clap(short = 'D', long, group = "sub")]
|
||||
delete: Option<String>,
|
||||
|
||||
/// 显示当前分支
|
||||
#[clap(long, action, group = "sub")]
|
||||
show_current: bool,
|
||||
},
|
||||
}
|
||||
pub fn handle_command() {
|
||||
let cli = Cli::parse();
|
||||
@@ -84,5 +108,8 @@ pub fn handle_command() {
|
||||
Command::Log { all, number } => {
|
||||
log(all, number);
|
||||
}
|
||||
Command::Branch { list, delete, new_branch, commit_hash, show_current } => {
|
||||
branch(new_branch, commit_hash, list, delete, show_current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
201
src/commands/branch.rs
Normal file
201
src/commands/branch.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
use colored::Colorize;
|
||||
|
||||
use crate::{
|
||||
head,
|
||||
models::{commit::Commit, object::Hash},
|
||||
store,
|
||||
};
|
||||
|
||||
// branch error
|
||||
enum BranchErr {
|
||||
BranchExist,
|
||||
InvalidObject,
|
||||
|
||||
BranchNoExist,
|
||||
BranchCheckedOut,
|
||||
}
|
||||
// 从分支名、commit hash中搜索commit
|
||||
fn search_hash(commit_hash: Hash) -> Option<Hash> {
|
||||
// 分支名
|
||||
if head::list_local_branches().contains(&commit_hash) {
|
||||
let commit_hash = head::get_branch_head(&commit_hash);
|
||||
return Some(commit_hash);
|
||||
}
|
||||
// commit hash
|
||||
let store = store::Store::new();
|
||||
let commit = store.search(&commit_hash);
|
||||
commit
|
||||
}
|
||||
|
||||
fn create_branch(branch_name: String, _base_commit: Hash) -> Result<(), BranchErr> {
|
||||
// 找到正确的base_commit_hash
|
||||
let base_commit = search_hash(_base_commit.clone());
|
||||
if base_commit.is_none() {
|
||||
println!("fatal: 非法的 commit: '{}'", _base_commit);
|
||||
return Err(BranchErr::InvalidObject);
|
||||
}
|
||||
|
||||
let base_commit = Commit::load(&base_commit.unwrap()); // TODO 这里会直接panic,可以优化一下错误处理流程
|
||||
|
||||
let exist_branchs = head::list_local_branches();
|
||||
if exist_branchs.contains(&branch_name) {
|
||||
println!("fatal: 分支 '{}' 已存在", branch_name);
|
||||
return Err(BranchErr::BranchExist);
|
||||
}
|
||||
|
||||
head::update_branch(&branch_name, &base_commit.get_hash());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_branch(branch_name: String) -> Result<(), BranchErr> {
|
||||
let branches = head::list_local_branches();
|
||||
if !branches.contains(&branch_name) {
|
||||
println!("error: 分支 '{}' 不存在", branch_name);
|
||||
return Err(BranchErr::BranchNoExist);
|
||||
}
|
||||
|
||||
// 仅在当前分支为删除分支时,不允许删除(在历史commit上允许删除)
|
||||
let current_branch = match head::current_head() {
|
||||
head::Head::Branch(branch_name) => branch_name,
|
||||
_ => "".to_string(),
|
||||
};
|
||||
if current_branch == branch_name {
|
||||
println!("error: 不能删除当前所在分支 {:?}", branch_name);
|
||||
return Err(BranchErr::BranchCheckedOut);
|
||||
}
|
||||
|
||||
head::delete_branch(&branch_name); // 删除refs/heads/branch_name,不删除任何commit
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_current_branch() {
|
||||
println!("show_current_branch");
|
||||
let head = head::current_head();
|
||||
match head {
|
||||
head::Head::Branch(branch_name) => println!("{}", branch_name),
|
||||
_ => (), // do nothing
|
||||
}
|
||||
}
|
||||
|
||||
fn list_branches() {
|
||||
println!("list_branches");
|
||||
let branches = head::list_local_branches();
|
||||
match head::current_head() {
|
||||
head::Head::Branch(branch_name) => {
|
||||
println!("* {}", branch_name.green());
|
||||
for branch in branches {
|
||||
if branch != branch_name {
|
||||
println!(" {}", branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
head::Head::Detached(commit_hash) => {
|
||||
println!("* (HEAD detached at {}) {}", commit_hash.green(), commit_hash[0..7].green());
|
||||
for branch in branches {
|
||||
println!(" {}", branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn branch(
|
||||
new_branch: Option<String>,
|
||||
commit_hash: Option<Hash>,
|
||||
list: bool,
|
||||
delete: Option<String>,
|
||||
show_current: bool,
|
||||
) {
|
||||
if new_branch.is_some() {
|
||||
let basic_commit = if commit_hash.is_some() {
|
||||
commit_hash.unwrap()
|
||||
} else {
|
||||
head::current_head_commit() // 默认使用当前commit
|
||||
};
|
||||
let _ = create_branch(new_branch.unwrap(), basic_commit);
|
||||
} else if delete.is_some() {
|
||||
let _ = delete_branch(delete.unwrap());
|
||||
} else if show_current {
|
||||
show_current_branch();
|
||||
} else if list {
|
||||
// 兜底list
|
||||
list_branches();
|
||||
} else {
|
||||
panic!("should not reach here")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::commands;
|
||||
use crate::utils::util;
|
||||
#[test]
|
||||
fn test_create_branch() {
|
||||
util::setup_test_with_clean_mit();
|
||||
|
||||
// no commit: invalid object
|
||||
let result = create_branch("test_branch".to_string(), head::current_head_commit());
|
||||
assert!(result.is_err());
|
||||
assert!(match result.unwrap_err() {
|
||||
BranchErr::InvalidObject => true,
|
||||
_ => false,
|
||||
});
|
||||
assert!(head::list_local_branches().is_empty());
|
||||
|
||||
commands::commit::commit("test commit 1".to_string(), true);
|
||||
let commit_hash_one = head::current_head_commit();
|
||||
commands::commit::commit("test commit 2".to_string(), true);
|
||||
let commit_hash_two = head::current_head_commit();
|
||||
|
||||
// success, use part of commit hash
|
||||
let new_branch_one = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
let result = create_branch(new_branch_one.clone(), commit_hash_one[0..7].to_string());
|
||||
assert!(result.is_ok());
|
||||
assert!(head::list_local_branches().contains(&new_branch_one), "new branch not in list");
|
||||
assert!(head::get_branch_head(&new_branch_one) == commit_hash_one, "new branch head error");
|
||||
|
||||
// branch exist
|
||||
let result = create_branch(new_branch_one.clone(), commit_hash_two.clone());
|
||||
assert!(result.is_err());
|
||||
assert!(match result.unwrap_err() {
|
||||
BranchErr::BranchExist => true,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
// use branch name as commit hash, success
|
||||
let new_branch_two = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
let result = create_branch(new_branch_two.clone(), new_branch_one.clone());
|
||||
assert!(result.is_ok());
|
||||
assert!(head::list_local_branches().contains(&new_branch_two), "new branch not in list");
|
||||
assert!(head::get_branch_head(&new_branch_two) == commit_hash_one, "new branch head error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_branch() {
|
||||
util::setup_test_with_clean_mit();
|
||||
|
||||
// no commit: invalid object
|
||||
let result = delete_branch("test_branch".to_string());
|
||||
assert!(result.is_err());
|
||||
assert!(match result.unwrap_err() {
|
||||
BranchErr::BranchNoExist => true,
|
||||
_ => false,
|
||||
});
|
||||
assert!(head::list_local_branches().is_empty());
|
||||
|
||||
commands::commit::commit("test commit 1".to_string(), true);
|
||||
let commit_hash = head::current_head_commit();
|
||||
|
||||
// success
|
||||
let new_branch = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
let result = create_branch(new_branch.clone(), commit_hash.clone());
|
||||
assert!(result.is_ok());
|
||||
assert!(head::list_local_branches().contains(&new_branch), "new branch not in list");
|
||||
assert!(head::get_branch_head(&new_branch) == commit_hash, "new branch head error");
|
||||
|
||||
// branch exist
|
||||
let result = delete_branch(new_branch.clone());
|
||||
assert!(result.is_ok());
|
||||
assert!(!head::list_local_branches().contains(&new_branch), "new branch not in list");
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ pub mod commit;
|
||||
pub mod init;
|
||||
pub mod remove;
|
||||
pub mod status;
|
||||
pub mod log;
|
||||
pub mod log;
|
||||
pub mod branch;
|
||||
13
src/head.rs
13
src/head.rs
@@ -41,6 +41,17 @@ pub fn get_branch_head(branch_name: &String) -> String {
|
||||
"".to_string() // 分支不存在或者没有commit
|
||||
}
|
||||
}
|
||||
pub fn delete_branch(branch_name: &String) {
|
||||
let mut branch = util::get_storage_path().unwrap();
|
||||
branch.push("refs");
|
||||
branch.push("heads");
|
||||
branch.push(branch_name);
|
||||
if branch.exists() {
|
||||
std::fs::remove_file(branch).expect("无法删除branch");
|
||||
} else {
|
||||
panic!("branch file not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**返回当前head指向的commit hash,如果是分支,则返回分支的commit hash*/
|
||||
pub fn current_head_commit() -> String {
|
||||
@@ -69,7 +80,6 @@ pub fn update_head_commit(commit_hash: &String) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 列出本地的branch */
|
||||
pub fn list_local_branches() -> Vec<String> {
|
||||
let mut branches = Vec::new();
|
||||
@@ -96,7 +106,6 @@ pub fn change_head_to_branch(branch_name: &String) {
|
||||
update_head_commit(&branch_head);
|
||||
}
|
||||
|
||||
|
||||
/** 切换head到非branchcommit */
|
||||
pub fn change_head_to_commit(commit_hash: &String) {
|
||||
let mut head = util::get_storage_path().unwrap();
|
||||
|
||||
@@ -34,6 +34,9 @@ impl Store {
|
||||
|
||||
/** 根据前缀搜索,有歧义时返回 None*/
|
||||
pub fn search(&self, hash: &String) -> Option<Hash> {
|
||||
if hash.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let objects = util::list_files(self.store_path.join("objects").as_path()).unwrap();
|
||||
// 转string
|
||||
let objects = objects
|
||||
|
||||
Reference in New Issue
Block a user