mirror of
https://github.com/MrBeanCpp/MIT.git
synced 2026-02-14 15:45:02 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1b82f5705 | ||
|
|
bad6b1a057 | ||
|
|
3bfb97860a | ||
|
|
87c09e400b | ||
|
|
90627f48cc | ||
|
|
9ac243d01a | ||
|
|
4db1042b38 | ||
|
|
a802778797 | ||
|
|
642094cfca | ||
|
|
1bb5332eaa | ||
|
|
c5d43e7eb3 |
84
README.md
84
README.md
@@ -1,56 +1,86 @@
|
||||
# MIT: Mini-Git implementation in Rust
|
||||
<h1 align="center">
|
||||
MIT: Mini-Git implementation in Rust
|
||||
</h1>
|
||||
|
||||
<u>中文文档</u> | **[English](./README_en.md)**
|
||||
|
||||
[项目链接](https://github.com/MrBeanCpp/MIT)
|
||||
|
||||
Git in Rust. 用 `Rust` 实现的mini `Git`. Called `mit`.
|
||||
|
||||
> 旨在简洁、高效且安全
|
||||
> 旨在简洁易读、高效且安全
|
||||
|
||||
> 学习`Git`的最好方法就是去实现`Git`
|
||||
>
|
||||
>
|
||||
> 本项目旨在提供一套 [小学二年级] 都能看懂的`Git`实现
|
||||
>
|
||||
> `// rm -rf 死板的设计模式 & 复杂的仓库架构`
|
||||
>
|
||||
**注意:** 更完善的`Git`实现,可以参考我们的另一个项目:
|
||||
[Mega-Libra](https://github.com/web3infra-foundation/mega/tree/main/libra)
|
||||
|
||||
## 良好的跨平台支持
|
||||
|
||||
- [x] Windows
|
||||
- [x] MacOS
|
||||
- [x] Linux (Unix-like...)
|
||||
|
||||
## 主要功能
|
||||
- 支持的输入路径(`pathspec`):文件路径、目录路径(绝对或相对,包括`.` `./` `../`)
|
||||
|
||||
- 支持的输入路径(`pathspec`):文件路径、目录路径(绝对或相对,包括`.` `./` `../`)
|
||||
|
||||
|
||||
- 支持 `mit init`, `mit add`, `mit rm`, `mit commit`
|
||||
- 支持 `mit init`, `mit add`, `mit rm`, `mit commit`
|
||||
|
||||
- [x] `init`: 初始化(若仓库已存在,则不执行)- `idempotent`
|
||||
- [x] `add`: 将变更添加至暂存区(包括新建、修改、删除),可指定文件或目录
|
||||
- `-A(all)` : 暂存工作区中的所有文件(从根目录开始)变更(新建√ 修改√ 删除√)
|
||||
- `-u(update)`: 仅对暂存区[`index`]中已跟踪的文件进行操作(新建× 修改√ 删除√)
|
||||
- [x] `rm`: 将文件从暂存区 &| 工作区移除.
|
||||
- `--cached` : 仅从暂存区移除,取消跟踪
|
||||
- `-r(recursive)`: 递归删除目录,删除目录时必须指定该参数
|
||||
- `-A(all)` : 暂存工作区中的所有文件(从根目录开始)变更(新建√ 修改√ 删除√)
|
||||
- `-u(update)`: 仅对暂存区[`index`]中已跟踪的文件进行操作(新建× 修改√ 删除√)
|
||||
- [x] `rm`: 将文件从暂存区 &| 工作区移除.
|
||||
- `--cached` : 仅从暂存区移除,取消跟踪
|
||||
- `-r(recursive)`: 递归删除目录,删除目录时必须指定该参数
|
||||
- [x] `commit`
|
||||
- [x] `status`: 显示工作区、暂存区、`HEAD` 的状态,(只包含当前目录);分为三部分:
|
||||
- **Staged to be committed:** 暂存区与`HEAD`(最后一次`Commit::Tree`)比较,即上次的暂存区
|
||||
- **Unstaged:** 暂存区与工作区比较,未暂存的工作区变更
|
||||
- **Untracked:** 暂存区与工作区比较,从未暂存过的文件(即未跟踪的文件)
|
||||
- **Staged to be committed:** 暂存区与`HEAD`(最后一次`Commit::Tree`)比较,即上次的暂存区
|
||||
- **Unstaged:** 暂存区与工作区比较,未暂存的工作区变更
|
||||
- **Untracked:** 暂存区与工作区比较,从未暂存过的文件(即未跟踪的文件)
|
||||
- [x] `log`
|
||||
|
||||
- 支持分支 `mit branch`, `mit switch`, `mit restore`
|
||||
- 支持分支 `mit branch`, `mit switch`, `mit restore`
|
||||
|
||||
- [x] `branch`
|
||||
- [x] `switch`
|
||||
与 `checkout` 不同,`switch` 需要指明`--detach`,才能切换到一个`commit`,否则只能切换分支。
|
||||
同时为里简化实现,有任何未提交的修改,都不能切换分支。
|
||||
与 `checkout` 不同,`switch` 需要指明`--detach`,才能切换到一个`commit`,否则只能切换分支。
|
||||
同时为里简化实现,有任何未提交的修改,都不能切换分支。
|
||||
- [x] `restore`: 回滚文件
|
||||
- 将指定路径(可包含目录)的文件恢复到`--source` 指定的版本,可指定操作暂存区 &| 工作区
|
||||
- 将指定路径(可包含目录)的文件恢复到`--source` 指定的版本,可指定操作暂存区 &| 工作区
|
||||
- `--source`:可指定`Commit Hash` `HEAD` `Branch Name`
|
||||
- 若不指定`--source`,且无`--staged`,则恢复到`HEAD`版本,否则从暂存区[`index`]恢复
|
||||
- 若`--staged`和`--worktree`均未指定,则默认恢复到`--worktree`
|
||||
- 对于`--source`中不存在的文件,若已跟踪,则删除;否则忽略
|
||||
- 若不指定`--source`,且无`--staged`,则恢复到`HEAD`版本,否则从暂存区[`index`]恢复
|
||||
- 若`--staged`和`--worktree`均未指定,则默认恢复到`--worktree`
|
||||
- 对于`--source`中不存在的文件,若已跟踪,则删除;否则忽略
|
||||
|
||||
- 支持简单的合并 `mit merge` (fast-forward)
|
||||
- - [x] Merge(FF)
|
||||
- 支持简单的合并 `mit merge` (fast-forward)
|
||||
-
|
||||
- [x] Merge(FF)
|
||||
|
||||
## 备注
|
||||
|
||||
### ⚠️测试需要单线程
|
||||
|
||||
⚠️注意:为了避免冲突,执行测试时请加上`--test-threads=1`
|
||||
|
||||
如:`cargo test -- --test-threads=1`
|
||||
|
||||
因为测试需要对同一个文件夹进行IO
|
||||
|
||||
### 名词释义
|
||||
- 暂存区:`index` or `stage`,保存下一次`commit`需要的的文件快照
|
||||
- 工作区:`worktree`,用户直接操作的文件夹
|
||||
- 工作目录:`working directory` or `repository`,代码仓库的根目录,即`.mit`所在的目录
|
||||
- `HEAD`:指向当前`commit`的指针
|
||||
- 已跟踪:`tracked`,指已经在暂存区[`index`]中的文件(即曾经`add`过的文件)
|
||||
|
||||
- 暂存区:`index` or `stage`,保存下一次`commit`需要的的文件快照
|
||||
- 工作区:`worktree`,用户直接操作的文件夹
|
||||
- 工作目录:`working directory` or `repository`,代码仓库的根目录,即`.mit`所在的目录
|
||||
- `HEAD`:指向当前`commit`的指针
|
||||
- 已跟踪:`tracked`,指已经在暂存区[`index`]中的文件(即曾经`add`过的文件)
|
||||
|
||||
### 介绍视频
|
||||
|
||||
[【Mit】Rust实现的迷你Git - 系统软件开发实践 结课报告_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1p64y1E78W/)
|
||||
|
||||
89
README_en.md
Normal file
89
README_en.md
Normal file
@@ -0,0 +1,89 @@
|
||||
<h1 align="center">
|
||||
MIT: Mini-Git implementation in Rust
|
||||
</h1>
|
||||
|
||||
**[中文文档](./README.md)** | <u>English</u>
|
||||
|
||||
[Project Link](https://github.com/MrBeanCpp/MIT)
|
||||
|
||||
Git in Rust. A mini Git implementation called`mit`, implemented in `Rust`.
|
||||
|
||||
> Designed to be concise, readable, efficient, and secure.
|
||||
>
|
||||
> The best way to learn Git is to implement Git.
|
||||
>
|
||||
> This project aims to provide a `Git` implementation that even a second-grader can understand.
|
||||
>
|
||||
> `// rm -rf rigid design patterns & complex repository architecture`
|
||||
>
|
||||
|
||||
**NOTE:** For a more comprehensive implementation of `Git`, please refer to another project of ours:
|
||||
[Mega-Libra](https://github.com/web3infra-foundation/mega/tree/main/libra)
|
||||
|
||||
## Cross-Platform Support
|
||||
|
||||
- [x] Windows
|
||||
- [x] MacOS
|
||||
- [x] Linux (Unix-like...)
|
||||
|
||||
## Key Features
|
||||
|
||||
- Supports input paths (pathspec): file paths, directory paths (absolute or relative, including `.`, `./`, `../`)
|
||||
|
||||
- Supports `mit init`, `mit add`, `mit rm`, `mit commit`
|
||||
|
||||
- [x] `init`: Initialize (does nothing if the repository already exists) - `idempotent`
|
||||
- [x] `add`: Add changes to the staging area (including new, modified, deleted), can specify files or directories
|
||||
- `-A(all)` : Stage all changes in the working directory (from the root) (new✅ modified✅ deleted✅)
|
||||
- `-u(update)`: Operate only on tracked files in the staging area [`index`] (new❌ modified✅ deleted✅)
|
||||
- [x] `rm`: Remove files from the staging area & working directory
|
||||
- `--cached` : Remove only from the staging area, untrack
|
||||
- `-r(recursive)`: Recursively delete directories, must specify this parameter when deleting directories
|
||||
- [x] `commit`
|
||||
- [x] `status`: Display the status of the working directory, staging area, and `HEAD` (only for the current
|
||||
directory); divided into three parts:
|
||||
- **Staged to be committed:** Changes staged in the staging area compared to `HEAD` (last `Commit::Tree`),
|
||||
i.e., the last staging area
|
||||
- **Unstaged:** Changes in the working directory not staged in the staging area
|
||||
- **Untracked:** Files in the working directory not staged or tracked before
|
||||
- [x] `log`
|
||||
|
||||
- Supports branches`mit branch`, `mit switch`, `mit restore`
|
||||
|
||||
- [x] `branch`
|
||||
- [x] `switch`
|
||||
Unlike `checkout`, `switch` requires specifying `--detach` to switch to a `commit`, otherwise, it can only
|
||||
switch branches.
|
||||
- [x] `restore`: Rollback files
|
||||
- Restore files at the specified path (including directories) to the version specified by `--source`, can
|
||||
specify staging area & working directory
|
||||
- `--source`: Can specify `Commit Hash`, `HEAD`, or `Branch Name`
|
||||
- If `--source` is not specified and neither `--staged` nor `--worktree` is specified, restore to the `HEAD`
|
||||
version, otherwise, restore from the staging area [`index`]
|
||||
- If neither `--staged` nor `--worktree` is specified, default to restore to `--worktree`
|
||||
- For files not present in `--source`, if tracked, delete; otherwise, ignore
|
||||
|
||||
- Supports simple merging `mit merge` (fast-forward)
|
||||
- [x] Merge(FF)
|
||||
|
||||
## Notes
|
||||
|
||||
### ⚠️Testing requires single-threading
|
||||
|
||||
⚠️ Note: To avoid conflicts, please use `--test-threads=1` when executing tests.
|
||||
|
||||
For example:`cargo test -- --test-threads=1`
|
||||
|
||||
This is because testing involves IO on the same folder.
|
||||
|
||||
### Term Definitions
|
||||
|
||||
- Staging area: `index` or `stage`, stores file snapshots needed for the next `commit`
|
||||
- Working directory: `worktree`, the folder directly manipulated by the user
|
||||
- Repository: `working directory` or `repository`, the root directory of the code repository, where `.mit` is located
|
||||
- `HEAD`:Points to the current`commit`
|
||||
- Tracked:`tracked`,files already in the staging area [`index`](i.e., files that have been `add`-ed)
|
||||
|
||||
### Introductory Video
|
||||
|
||||
[【Mit】Rust implementation of Mini-Git - System Software Development Practice Final Report_Bilibili](https://www.bilibili.com/video/BV1p64y1E78W/)
|
||||
5
clippy.toml
Normal file
5
clippy.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
avoid-breaking-exported-api = false
|
||||
|
||||
# use the various `span_lint_*` methods instead, which also add a link to the docs
|
||||
disallowed-methods = [
|
||||
]
|
||||
@@ -5,6 +5,7 @@ use colored::Colorize;
|
||||
use crate::commands::status;
|
||||
use crate::models::index::FileMetaData;
|
||||
use crate::models::*;
|
||||
use crate::utils::path_ext::PathExt;
|
||||
use crate::utils::util;
|
||||
|
||||
/// add是对index的操作,不会对工作区产生影响
|
||||
@@ -40,7 +41,7 @@ pub fn add(raw_paths: Vec<String>, all: bool, mut update: bool) {
|
||||
|
||||
fn add_a_file(file: &Path, index: &mut Index) {
|
||||
let workdir = util::get_working_dir().unwrap();
|
||||
if !util::is_sub_path(file, &workdir) {
|
||||
if !file.is_sub_to(&workdir) {
|
||||
//文件不在工作区内
|
||||
println!("fatal: '{}' is outside workdir at '{}'", file.display(), workdir.display());
|
||||
return;
|
||||
@@ -51,7 +52,7 @@ fn add_a_file(file: &Path, index: &mut Index) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rel_path = util::to_cur_relative_path(file);
|
||||
let rel_path = file.to_relative();
|
||||
if !file.exists() {
|
||||
//文件被删除
|
||||
index.remove(file);
|
||||
|
||||
@@ -2,7 +2,7 @@ use colored::Colorize;
|
||||
|
||||
use crate::{
|
||||
models::*,
|
||||
utils::{head, store, util},
|
||||
utils::{store, util},
|
||||
};
|
||||
|
||||
// branch error
|
||||
@@ -22,8 +22,7 @@ fn search_hash(commit_hash: Hash) -> Option<Hash> {
|
||||
}
|
||||
// commit hash
|
||||
let store = store::Store::new();
|
||||
let commit = store.search(&commit_hash);
|
||||
commit
|
||||
store.search(&commit_hash)
|
||||
}
|
||||
|
||||
fn create_branch(branch_name: String, _base_commit: Hash) -> Result<(), BranchErr> {
|
||||
@@ -70,9 +69,8 @@ fn delete_branch(branch_name: String) -> Result<(), BranchErr> {
|
||||
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
|
||||
if let head::Head::Branch(branch_name) = head {
|
||||
println!("{}", branch_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,11 +103,7 @@ pub fn branch(
|
||||
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 basic_commit = commit_hash.unwrap_or_else(head::current_head_commit); // 默认使用当前commit
|
||||
let _ = create_branch(new_branch.unwrap(), basic_commit);
|
||||
} else if delete.is_some() {
|
||||
let _ = delete_branch(delete.unwrap());
|
||||
@@ -126,18 +120,15 @@ pub fn branch(
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{commands, utils::test_util};
|
||||
use crate::{commands, utils::test};
|
||||
#[test]
|
||||
fn test_create_branch() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_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!(matches!(result.unwrap_err(), BranchErr::InvalidObject));
|
||||
assert!(head::list_local_branches().is_empty());
|
||||
|
||||
commands::commit::commit("test commit 1".to_string(), true);
|
||||
@@ -155,10 +146,7 @@ mod test {
|
||||
// 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,
|
||||
});
|
||||
assert!(matches!(result.unwrap_err(), BranchErr::BranchExist));
|
||||
|
||||
// use branch name as commit hash, success
|
||||
let new_branch_two = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
@@ -170,15 +158,12 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_delete_branch() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_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!(matches!(result.unwrap_err(), BranchErr::BranchNoExist));
|
||||
assert!(head::list_local_branches().is_empty());
|
||||
|
||||
commands::commit::commit("test commit 1".to_string(), true);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{models::*, utils::head};
|
||||
use crate::models::*;
|
||||
|
||||
use super::status;
|
||||
|
||||
@@ -38,33 +38,31 @@ pub fn commit(message: String, allow_empty: bool) {
|
||||
mod test {
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{
|
||||
commands as cmd, models,
|
||||
utils::{head, test_util},
|
||||
};
|
||||
use crate::models::head;
|
||||
use crate::{commands as cmd, models, utils::test};
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_commit_empty() {
|
||||
test_util::setup_test_with_empty_workdir();
|
||||
test::setup_with_empty_workdir();
|
||||
super::commit("".to_string(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_commit() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let test_file = "a.txt";
|
||||
let head_one = head::current_head_commit();
|
||||
assert!(head_one.is_empty());
|
||||
|
||||
test_util::ensure_test_file(&Path::new(test_file), "test content".into());
|
||||
test::ensure_file(Path::new(test_file), "test content".into());
|
||||
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);
|
||||
assert_eq!(head_two.is_empty(), false);
|
||||
|
||||
let commit = models::commit::Commit::load(&head_two);
|
||||
assert!(commit.get_parent_hash().len() == 0);
|
||||
assert!(commit.get_parent_hash().is_empty());
|
||||
assert!(commit.get_message() == "test commit 1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn init() -> io::Result<()> {
|
||||
}
|
||||
fs::write(mit_dir.join("HEAD"), "ref: refs/heads/master\n")?;
|
||||
|
||||
set_dir_hidden(&mit_dir.to_str().unwrap())?; // 设置目录隐藏 (跨平台)
|
||||
set_dir_hidden(mit_dir.to_str().unwrap())?; // 设置目录隐藏 (跨平台)
|
||||
println!("Initialized empty mit repository in {}", dir.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{models::Commit, utils::head};
|
||||
use crate::models::{head, Commit};
|
||||
use colored::Colorize;
|
||||
|
||||
const DEFAULT_LOG_NUMBER: usize = 10;
|
||||
@@ -59,7 +59,7 @@ fn __log(all: bool, number: Option<usize>) -> usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if commit.get_parent_hash().len() == 0 {
|
||||
if commit.get_parent_hash().is_empty() {
|
||||
break;
|
||||
}
|
||||
head_commit = commit.get_parent_hash().first().unwrap().clone();
|
||||
@@ -70,10 +70,10 @@ fn __log(all: bool, number: Option<usize>) -> usize {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::commands;
|
||||
use crate::utils::test_util;
|
||||
use crate::utils::test;
|
||||
#[test]
|
||||
fn test_log() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
assert_eq!(super::__log(false, None), 0);
|
||||
commands::commit::commit("test commit 2".into(), true);
|
||||
assert_eq!(super::__log(false, Some(1)), 1);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
commands::{self, status::*},
|
||||
models::{Commit, Hash},
|
||||
utils::{head, store, util},
|
||||
models::{head, Commit, Hash},
|
||||
utils::{store, util},
|
||||
};
|
||||
|
||||
enum MergeErr {
|
||||
@@ -21,7 +21,7 @@ fn check_ff(current: &Hash, target: Hash) -> Result<bool, MergeErr> {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return Err(MergeErr::NoFastForward);
|
||||
Err(MergeErr::NoFastForward)
|
||||
}
|
||||
|
||||
/** commit 以fast forward到形式合并到当前分支 */
|
||||
@@ -38,9 +38,7 @@ fn merge_ff(commit_hash: String) -> Result<(), MergeErr> {
|
||||
// 检查当前分支是否可以fast forward到commit
|
||||
let current_commit = head::current_head_commit();
|
||||
let check = check_ff(¤t_commit, commit_hash.clone());
|
||||
if check.is_err() {
|
||||
return Err(check.unwrap_err());
|
||||
}
|
||||
check?;
|
||||
|
||||
// 执行fast forward
|
||||
let head = head::current_head();
|
||||
@@ -83,12 +81,12 @@ mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
commands::{commit, switch::switch},
|
||||
utils::test_util,
|
||||
utils::test,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_check_ff() {
|
||||
test_util::setup_test_with_empty_workdir();
|
||||
test::setup_with_empty_workdir();
|
||||
commit::commit("init".to_string(), true);
|
||||
let commit1 = head::current_head_commit();
|
||||
let origin_branch = match head::current_head() {
|
||||
|
||||
@@ -4,9 +4,10 @@ use std::{
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::utils::path_ext::PathExt;
|
||||
use crate::{
|
||||
models::*,
|
||||
utils::{head, store, util},
|
||||
utils::{store, util},
|
||||
};
|
||||
|
||||
fn restore_to_file(hash: &Hash, path: &PathBuf) {
|
||||
@@ -23,7 +24,7 @@ fn get_worktree_deleted_files_in_filters(
|
||||
.iter()
|
||||
.filter(|(path, _)| {
|
||||
assert!(path.is_absolute()); //
|
||||
!path.exists() && util::include_in_paths(path, filters)
|
||||
!path.exists() && path.include_in(filters)
|
||||
})
|
||||
.map(|(path, _)| path.clone())
|
||||
.collect() //HashSet自动去重
|
||||
@@ -39,7 +40,7 @@ fn get_index_deleted_files_in_filters(
|
||||
.iter()
|
||||
.filter(|(path, _)| {
|
||||
assert!(path.is_absolute());
|
||||
!index.contains(path) && util::include_in_paths(path, filters)
|
||||
!index.contains(path) && path.include_in(filters)
|
||||
})
|
||||
.map(|(path, _)| path.clone())
|
||||
.collect() //HashSet自动去重
|
||||
@@ -55,15 +56,15 @@ fn preprocess_filters(filters: Option<&Vec<PathBuf>>) -> Vec<PathBuf> {
|
||||
}
|
||||
|
||||
/// 转化为绝对路径(to workdir)的HashMap
|
||||
fn preprocess_blobs(blobs: &Vec<(PathBuf, Hash)>) -> HashMap<PathBuf, Hash> {
|
||||
fn preprocess_blobs(blobs: &[(PathBuf, Hash)]) -> HashMap<PathBuf, Hash> {
|
||||
blobs // 转为绝对路径 //TODO tree改变路径表示方式后,这里需要修改
|
||||
.iter()
|
||||
.map(|(path, hash)| (util::to_workdir_absolute_path(path), hash.clone()))
|
||||
.map(|(path, hash)| (path.to_absolute_workdir(), hash.clone()))
|
||||
.collect() //to HashMap
|
||||
}
|
||||
|
||||
/** 根据filter restore workdir */
|
||||
pub fn restore_worktree(filter: Option<&Vec<PathBuf>>, target_blobs: &Vec<(PathBuf, Hash)>) {
|
||||
pub fn restore_worktree(filter: Option<&Vec<PathBuf>>, target_blobs: &[(PathBuf, Hash)]) {
|
||||
let input_paths = preprocess_filters(filter); //预处理filter 将None转化为workdir
|
||||
let target_blobs = preprocess_blobs(target_blobs); //预处理target_blobs 转化为绝对路径HashMap
|
||||
|
||||
@@ -80,7 +81,7 @@ pub fn restore_worktree(filter: Option<&Vec<PathBuf>>, target_blobs: &Vec<(PathB
|
||||
//文件不存在于workdir
|
||||
if target_blobs.contains_key(path) {
|
||||
//文件存在于target_commit (deleted),需要恢复
|
||||
restore_to_file(&target_blobs[path], &path);
|
||||
restore_to_file(&target_blobs[path], path);
|
||||
} else {
|
||||
//在target_commit和workdir中都不存在(非法路径), 用户输入
|
||||
println!("fatal: pathspec '{}' did not match any files", path.display());
|
||||
@@ -89,16 +90,16 @@ pub fn restore_worktree(filter: Option<&Vec<PathBuf>>, target_blobs: &Vec<(PathB
|
||||
//文件存在,有两种情况:1.修改 2.新文件
|
||||
if target_blobs.contains_key(path) {
|
||||
//文件已修改(modified)
|
||||
let dry_blob = Blob::dry_new(util::read_workfile(&path)); //TODO tree没有存修改时间,所以这里只能用hash判断
|
||||
let dry_blob = Blob::dry_new(util::read_workfile(path)); //TODO tree没有存修改时间,所以这里只能用hash判断
|
||||
if dry_blob.get_hash() != target_blobs[path] {
|
||||
restore_to_file(&target_blobs[path], &path);
|
||||
restore_to_file(&target_blobs[path], path);
|
||||
}
|
||||
} else {
|
||||
//新文件,也分两种情况:1.已跟踪,需要删除 2.未跟踪,保留
|
||||
if index.tracked(path) {
|
||||
//文件已跟踪
|
||||
fs::remove_file(&path).unwrap();
|
||||
util::clear_empty_dir(&path); // 级联删除 清理空目录
|
||||
fs::remove_file(path).unwrap();
|
||||
util::clear_empty_dir(path); // 级联删除 清理空目录
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,12 +107,12 @@ pub fn restore_worktree(filter: Option<&Vec<PathBuf>>, target_blobs: &Vec<(PathB
|
||||
}
|
||||
|
||||
/** 根据filter restore staged */
|
||||
pub fn restore_index(filter: Option<&Vec<PathBuf>>, target_blobs: &Vec<(PathBuf, Hash)>) {
|
||||
pub fn restore_index(filter: Option<&Vec<PathBuf>>, target_blobs: &[(PathBuf, Hash)]) {
|
||||
let input_paths = preprocess_filters(filter); //预处理filter 将None转化为workdir
|
||||
let target_blobs = preprocess_blobs(target_blobs); //预处理target_blobs 转化为绝对路径HashMap
|
||||
|
||||
let index = Index::get_instance();
|
||||
let deleted_files_index = get_index_deleted_files_in_filters(&index, &input_paths, &target_blobs); //统计已删除的文件
|
||||
let deleted_files_index = get_index_deleted_files_in_filters(index, &input_paths, &target_blobs); //统计已删除的文件
|
||||
|
||||
//1.获取index中包含于input_path的文件(使用paths进行过滤)
|
||||
let mut file_paths: HashSet<PathBuf> = util::filter_to_fit_paths(&index.get_tracked_files(), &input_paths);
|
||||
@@ -167,13 +168,13 @@ pub fn restore(paths: Vec<String>, source: Option<String>, worktree: bool, stage
|
||||
if src == "HEAD" {
|
||||
//Default Source
|
||||
head::current_head_commit() // "" if not exist
|
||||
} else if head::list_local_branches().contains(&src) {
|
||||
} else if head::list_local_branches().contains(src) {
|
||||
// Branch Name, e.g. master
|
||||
head::get_branch_head(&src) // "" if not exist
|
||||
head::get_branch_head(src) // "" if not exist
|
||||
} else {
|
||||
// [Commit Hash, e.g. a1b2c3d4] || [Wrong Branch Name]
|
||||
let store = store::Store::new();
|
||||
let commit = store.search(&src);
|
||||
let commit = store.search(src);
|
||||
if commit.is_none() || !util::is_typeof_commit(commit.clone().unwrap()) {
|
||||
println!("fatal: 非法的 commit hash: '{}'", src);
|
||||
return;
|
||||
@@ -221,14 +222,14 @@ pub fn restore(paths: Vec<String>, source: Option<String>, worktree: bool, stage
|
||||
mod test {
|
||||
use std::fs;
|
||||
//TODO 写测试!
|
||||
use crate::{commands as cmd, commands::status, models::Index, utils::test_util};
|
||||
use crate::{commands as cmd, commands::status, models::Index, utils::test};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_restore_stage() {
|
||||
test_util::setup_test_with_empty_workdir();
|
||||
test::setup_with_empty_workdir();
|
||||
let path = PathBuf::from("a.txt");
|
||||
test_util::ensure_no_file(&path);
|
||||
test::ensure_no_file(&path);
|
||||
cmd::add(vec![], true, false); //add -A
|
||||
cmd::restore(vec![".".to_string()], Some("HEAD".to_string()), false, true);
|
||||
let index = Index::get_instance();
|
||||
@@ -237,24 +238,24 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_restore_worktree() {
|
||||
test_util::setup_test_with_empty_workdir();
|
||||
test::setup_with_empty_workdir();
|
||||
let files = vec!["a.txt", "b.txt", "c.txt", "test/in.txt"];
|
||||
test_util::ensure_test_files(&files);
|
||||
test::ensure_files(&files);
|
||||
|
||||
cmd::add(vec![], true, false);
|
||||
assert_eq!(status::changes_to_be_committed().new.iter().count(), 4);
|
||||
assert_eq!(status::changes_to_be_committed().new.len(), 4);
|
||||
|
||||
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);
|
||||
assert_eq!(status::changes_to_be_committed().new.len(), 3);
|
||||
assert_eq!(status::changes_to_be_staged().new.len(), 1);
|
||||
|
||||
fs::remove_file("a.txt").unwrap(); //删除a.txt
|
||||
fs::remove_dir_all("test").unwrap(); //删除test文件夹
|
||||
assert_eq!(status::changes_to_be_staged().deleted.iter().count(), 2);
|
||||
assert_eq!(status::changes_to_be_staged().deleted.len(), 2);
|
||||
|
||||
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);
|
||||
assert_eq!(status::changes_to_be_committed().new.len(), 3);
|
||||
assert_eq!(status::changes_to_be_staged().new.len(), 1);
|
||||
assert_eq!(status::changes_to_be_staged().deleted.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::models::head;
|
||||
use crate::utils::path_ext::PathExt;
|
||||
use crate::{
|
||||
models::{Blob, Commit, Index},
|
||||
utils::{head, util},
|
||||
utils::util,
|
||||
};
|
||||
use colored::Colorize;
|
||||
use std::path::PathBuf;
|
||||
@@ -46,7 +48,7 @@ impl Changes {
|
||||
[&mut change.new, &mut change.modified, &mut change.deleted]
|
||||
.iter_mut()
|
||||
.for_each(|paths| {
|
||||
**paths = util::map(&**paths, |p| util::to_workdir_absolute_path(p));
|
||||
**paths = util::map(&**paths, |p| p.to_absolute_workdir());
|
||||
});
|
||||
change
|
||||
}
|
||||
@@ -58,7 +60,7 @@ impl Changes {
|
||||
[&mut change.new, &mut change.modified, &mut change.deleted]
|
||||
.iter_mut()
|
||||
.for_each(|paths| {
|
||||
**paths = util::map(&**paths, |p| util::get_relative_path(p, &cur_dir));
|
||||
**paths = util::map(&**paths, |p| util::get_relative_path_to_dir(p, &cur_dir));
|
||||
});
|
||||
change
|
||||
}
|
||||
@@ -79,9 +81,9 @@ pub fn changes_to_be_committed() -> Changes {
|
||||
let tracked_files = index
|
||||
.get_tracked_files()
|
||||
.iter()
|
||||
.map(|f| util::to_workdir_relative_path(f))
|
||||
.map(|f| f.to_relative_workdir())
|
||||
.collect::<Vec<PathBuf>>();
|
||||
if head_hash == "" {
|
||||
if head_hash.is_empty() {
|
||||
// 初始提交
|
||||
change.new = tracked_files;
|
||||
return change;
|
||||
@@ -95,7 +97,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 = util::to_workdir_absolute_path(index_file);
|
||||
let index_path = index_file.to_absolute_workdir();
|
||||
if !index.verify_hash(&index_path, blob_hash) {
|
||||
change.modified.push(tree_file.clone());
|
||||
}
|
||||
@@ -118,12 +120,12 @@ pub fn changes_to_be_staged() -> Changes {
|
||||
let index = Index::get_instance();
|
||||
for file in index.get_tracked_files() {
|
||||
if !file.exists() {
|
||||
change.deleted.push(util::to_workdir_relative_path(&file));
|
||||
change.deleted.push(file.to_relative_workdir());
|
||||
} else if index.is_modified(&file) {
|
||||
// 若文件元数据被修改,才需要比较暂存区与文件的hash来判别内容修改
|
||||
let dry_blob = Blob::dry_new(util::read_workfile(&file));
|
||||
if !index.verify_hash(&file, &dry_blob.get_hash()) {
|
||||
change.modified.push(util::to_workdir_relative_path(&file));
|
||||
change.modified.push(file.to_relative_workdir());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,7 +133,7 @@ pub fn changes_to_be_staged() -> Changes {
|
||||
for file in files {
|
||||
if !index.tracked(&file) {
|
||||
//文件未被跟踪
|
||||
change.new.push(util::to_workdir_relative_path(&file));
|
||||
change.new.push(file.to_relative_workdir());
|
||||
}
|
||||
}
|
||||
change
|
||||
@@ -145,7 +147,7 @@ pub fn status() {
|
||||
util::check_repo_exist();
|
||||
match head::current_head() {
|
||||
head::Head::Detached(commit) => {
|
||||
println!("HEAD detached at {}", commit[0..7].to_string());
|
||||
println!("HEAD detached at {}", &commit[0..7]);
|
||||
}
|
||||
head::Head::Branch(branch) => {
|
||||
println!("On branch {}", branch);
|
||||
@@ -202,14 +204,14 @@ pub fn status() {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{commands as cmd, utils::test_util};
|
||||
use crate::{commands as cmd, utils::test};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_changes_to_be_committed() {
|
||||
test_util::setup_test_with_empty_workdir();
|
||||
test::setup_with_empty_workdir();
|
||||
let test_file = "a.txt";
|
||||
test_util::ensure_test_file(Path::new(test_file), None);
|
||||
test::ensure_file(Path::new(test_file), None);
|
||||
|
||||
cmd::commit("test commit".to_string(), true);
|
||||
cmd::add(vec![test_file.to_string()], false, false);
|
||||
@@ -221,7 +223,7 @@ mod tests {
|
||||
println!("{:?}", change.to_absolute());
|
||||
|
||||
cmd::commit("test commit".to_string(), true);
|
||||
test_util::ensure_test_file(Path::new(test_file), Some("new content"));
|
||||
test::ensure_file(Path::new(test_file), Some("new content"));
|
||||
cmd::add(vec![test_file.to_string()], false, false);
|
||||
let change = changes_to_be_committed();
|
||||
assert_eq!(change.new.len(), 0);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use colored::Colorize;
|
||||
|
||||
use crate::{
|
||||
models::{Commit, Hash},
|
||||
utils::{head, store, util},
|
||||
models::{head, Commit, Hash},
|
||||
utils::{store, util},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -89,12 +89,12 @@ mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
commands::{self as cmd},
|
||||
utils::test_util,
|
||||
utils::test,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
#[test]
|
||||
fn test_switch() {
|
||||
test_util::setup_test_with_empty_workdir();
|
||||
test::setup_with_empty_workdir();
|
||||
|
||||
cmd::commit("init".to_string(), true);
|
||||
let test_branch_1 = "test_branch_1".to_string();
|
||||
@@ -102,7 +102,7 @@ mod test {
|
||||
|
||||
/* test 1: NoClean */
|
||||
let test_file_1 = PathBuf::from("test_file_1");
|
||||
test_util::ensure_test_file(&test_file_1, None);
|
||||
test::ensure_file(&test_file_1, None);
|
||||
cmd::add(vec![], true, false); // add all
|
||||
let result = switch_to(test_branch_1.clone(), false);
|
||||
assert!(result.is_err());
|
||||
@@ -123,12 +123,12 @@ mod test {
|
||||
assert!(matches!(result.unwrap_err(), SwitchErr::InvalidObject));
|
||||
|
||||
let tees_file_2 = PathBuf::from("test_file_2");
|
||||
test_util::ensure_test_file(&tees_file_2, None);
|
||||
test::ensure_file(&tees_file_2, None);
|
||||
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
|
||||
|
||||
test_util::ensure_no_file(&test_file_1);
|
||||
test::ensure_no_file(&test_file_1);
|
||||
cmd::add(vec![], true, false); // add all
|
||||
assert!(!test_file_1.exists());
|
||||
cmd::commit("delete file 1".to_string(), false);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::bool_assert_comparison)] // see tree&false directly
|
||||
#![allow(clippy::bool_comparison)] // see tree&false directly
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod models;
|
||||
|
||||
@@ -34,10 +34,10 @@ impl Blob {
|
||||
let mut cmopress_encoder = GzEncoder::new(Vec::new(), Compression::default());
|
||||
cmopress_encoder.write_all(data.as_bytes()).unwrap();
|
||||
let compressed_data = cmopress_encoder.finish().unwrap();
|
||||
base64::engine::general_purpose::STANDARD_NO_PAD.encode(&compressed_data)
|
||||
base64::engine::general_purpose::STANDARD_NO_PAD.encode(compressed_data)
|
||||
}
|
||||
fn decode(encoded: String) -> String {
|
||||
let compressed_data = base64::engine::general_purpose::STANDARD_NO_PAD.decode(&encoded).unwrap();
|
||||
let compressed_data = base64::engine::general_purpose::STANDARD_NO_PAD.decode(encoded).unwrap();
|
||||
let mut decompress_decoder = GzDecoder::new(&compressed_data[..]);
|
||||
let mut data = String::new();
|
||||
decompress_decoder.read_to_string(&mut data).unwrap();
|
||||
@@ -70,11 +70,11 @@ impl Blob {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::utils::test_util;
|
||||
use crate::utils::test;
|
||||
|
||||
#[test]
|
||||
fn test_save_and_load() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let test_data = "hello world";
|
||||
let blob = super::Blob::new(test_data.into());
|
||||
|
||||
|
||||
@@ -83,14 +83,14 @@ impl Commit {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::utils::test_util;
|
||||
use crate::utils::test;
|
||||
|
||||
#[test]
|
||||
fn test_commit() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
|
||||
let index = super::Index::get_instance();
|
||||
let mut commit = super::Commit::new(&index, vec!["123".to_string(), "456".to_string()], "test".to_string());
|
||||
let mut commit = super::Commit::new(index, vec!["123".to_string(), "456".to_string()], "test".to_string());
|
||||
assert_eq!(commit.hash.len(), 0);
|
||||
|
||||
let hash = commit.save();
|
||||
|
||||
@@ -22,7 +22,8 @@ pub fn update_branch(branch_name: &String, commit_hash: &String) {
|
||||
branch.push("refs");
|
||||
branch.push("heads");
|
||||
branch.push(branch_name);
|
||||
std::fs::write(&branch, commit_hash).expect(&format!("无法写入branch in {:?} with {}", branch, commit_hash));
|
||||
std::fs::write(&branch, commit_hash)
|
||||
.unwrap_or_else(|_| panic!("无法写入branch in {:?} with {}", branch, commit_hash));
|
||||
}
|
||||
|
||||
pub fn get_branch_head(branch_name: &String) -> String {
|
||||
@@ -32,8 +33,7 @@ pub fn get_branch_head(branch_name: &String) -> String {
|
||||
branch.push("heads");
|
||||
branch.push(branch_name);
|
||||
if branch.exists() {
|
||||
let commit_hash = std::fs::read_to_string(branch).expect("无法读取branch");
|
||||
commit_hash
|
||||
std::fs::read_to_string(branch).expect("无法读取branch")
|
||||
} else {
|
||||
"".to_string() // 分支不存在或者没有commit
|
||||
}
|
||||
@@ -56,8 +56,7 @@ pub fn current_head_commit() -> String {
|
||||
let head = current_head();
|
||||
match head {
|
||||
Head::Branch(branch_name) => {
|
||||
let commit_hash = get_branch_head(&branch_name);
|
||||
commit_hash
|
||||
get_branch_head(&branch_name)
|
||||
}
|
||||
Head::Detached(commit_hash) => commit_hash,
|
||||
}
|
||||
@@ -113,12 +112,12 @@ pub fn change_head_to_commit(commit_hash: &String) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::utils::test_util;
|
||||
use crate::utils::head;
|
||||
use crate::models::head;
|
||||
use crate::utils::test;
|
||||
|
||||
#[test]
|
||||
fn test_edit_branch() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let branch_name = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
let branch_head = super::get_branch_head(&branch_name);
|
||||
assert!(branch_head.is_empty());
|
||||
@@ -132,7 +131,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_list_local_branches() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let branch_one = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
let branch_two = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
head::update_branch(&branch_one, &"1234567890".to_string());
|
||||
@@ -145,7 +144,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_change_head_to_branch() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let branch_name = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
head::update_branch(&branch_name, &"1234567890".to_string());
|
||||
super::change_head_to_branch(&branch_name);
|
||||
@@ -160,7 +159,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_change_head_to_commit() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let commit_hash = "1234567890".to_string();
|
||||
super::change_head_to_commit(&commit_hash);
|
||||
assert!(
|
||||
@@ -174,7 +173,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_update_branch_head() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let branch_name = "test_branch".to_string() + &rand::random::<u32>().to_string();
|
||||
let commit_hash = "1234567890".to_string();
|
||||
super::update_branch(&branch_name, &commit_hash);
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::utils::path_ext::PathExt;
|
||||
use crate::{models::*, utils::util};
|
||||
use once_cell::unsync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -58,7 +59,7 @@ impl Index {
|
||||
fn new() -> Index {
|
||||
let mut index = Index::default();
|
||||
index.load();
|
||||
return index;
|
||||
index
|
||||
}
|
||||
|
||||
/// 单例模式,线程不安全,但是本程序默认单线程
|
||||
@@ -76,7 +77,7 @@ impl Index {
|
||||
|
||||
/// 预处理路径,统一形式为绝对路径
|
||||
fn preprocess(path: &Path) -> PathBuf {
|
||||
util::get_absolute_path(&path)
|
||||
path.to_absolute()
|
||||
}
|
||||
|
||||
// 添加文件
|
||||
@@ -87,7 +88,7 @@ impl Index {
|
||||
|
||||
// 删除文件
|
||||
pub fn remove(&mut self, path: &Path) {
|
||||
let path = Index::preprocess(&path);
|
||||
let path = Index::preprocess(path);
|
||||
self.entries.remove(&path);
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ impl Index {
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(path, value)| {
|
||||
let relative_path = util::get_relative_path(path, &self.working_dir);
|
||||
let relative_path = util::get_relative_path_to_dir(path, &self.working_dir);
|
||||
(relative_path, value.clone())
|
||||
})
|
||||
.collect();
|
||||
@@ -195,7 +196,7 @@ impl Index {
|
||||
|
||||
/** 获取跟踪的文件列表 */
|
||||
pub fn get_tracked_files(&self) -> Vec<PathBuf> {
|
||||
self.entries.keys().map(|f| f.clone()).collect()
|
||||
self.entries.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn get_tracked_entries(&self) -> HashMap<PathBuf, FileMetaData> {
|
||||
@@ -211,12 +212,12 @@ impl Index {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::test_util;
|
||||
use crate::utils::test;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn test_meta_get() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let metadata = fs::metadata(".mit/HEAD").unwrap();
|
||||
println!("{:?}", util::format_time(&metadata.created().unwrap()));
|
||||
println!("{:?}", util::format_time(&metadata.modified().unwrap()));
|
||||
@@ -225,20 +226,20 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_load() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let index = Index::get_instance();
|
||||
println!("{:?}", index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let index = Index::get_instance();
|
||||
let path = PathBuf::from("../mit_test_storage/.mit/HEAD"); //测试../相对路径的处理
|
||||
index.add(path.clone(), FileMetaData::new(&Blob::new(util::read_workfile(&path)), &path));
|
||||
|
||||
let 中文路径 = "中文路径.txt";
|
||||
test_util::ensure_test_file(Path::new(中文路径), None);
|
||||
test::ensure_file(Path::new(中文路径), None);
|
||||
let path = PathBuf::from(中文路径);
|
||||
index.add(path.clone(), FileMetaData::new(&Blob::new(util::read_workfile(&path)), &path));
|
||||
index.save();
|
||||
@@ -247,7 +248,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_save_load() {
|
||||
test_util::setup_test_with_empty_workdir();
|
||||
test::setup_with_empty_workdir();
|
||||
let index = Index::get_instance();
|
||||
let path = PathBuf::from(".mit/HEAD");
|
||||
index.add(path.clone(), FileMetaData::new(&Blob::new(util::read_workfile(&path)), &path));
|
||||
|
||||
@@ -7,5 +7,7 @@ pub use index::FileMetaData;
|
||||
pub use index::Index;
|
||||
pub mod object;
|
||||
pub use object::Hash;
|
||||
pub mod head;
|
||||
pub mod tree;
|
||||
|
||||
pub use tree::Tree;
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::PathExt;
|
||||
use crate::utils::{store, util};
|
||||
|
||||
use super::{Hash, Index};
|
||||
@@ -29,19 +30,19 @@ 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 {
|
||||
|
||||
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))
|
||||
.map(|file| file.to_relative_workdir())
|
||||
.filter(|path| path.starts_with(¤t_root))
|
||||
.collect();
|
||||
for path in path_entries.iter() {
|
||||
@@ -137,36 +138,36 @@ mod test {
|
||||
|
||||
use crate::{
|
||||
models::*,
|
||||
utils::{test_util, util},
|
||||
utils::{test, util},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let index = Index::get_instance();
|
||||
for test_file in vec!["b.txt", "mit_src/a.txt", "test/test.txt"] {
|
||||
for test_file in ["b.txt", "mit_src/a.txt", "test/test.txt"] {
|
||||
let test_file = PathBuf::from(test_file);
|
||||
test_util::ensure_test_file(&test_file, None);
|
||||
test::ensure_file(&test_file, None);
|
||||
index.add(test_file.clone(), FileMetaData::new(&Blob::new(util::read_workfile(&test_file)), &test_file));
|
||||
}
|
||||
|
||||
let tree = Tree::new(&index);
|
||||
let tree = Tree::new(index);
|
||||
assert!(tree.entries.len() == 3);
|
||||
assert!(tree.hash.len() != 0);
|
||||
assert_eq!(tree.hash.is_empty(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let index = Index::get_instance();
|
||||
let test_files = vec!["b.txt", "mit_src/a.txt"];
|
||||
for test_file in test_files.clone() {
|
||||
let test_file = PathBuf::from(test_file);
|
||||
test_util::ensure_test_file(&test_file, None);
|
||||
test::ensure_file(&test_file, None);
|
||||
index.add(test_file.clone(), FileMetaData::new(&Blob::new(util::read_workfile(&test_file)), &test_file));
|
||||
}
|
||||
|
||||
let tree = Tree::new(&index);
|
||||
let tree = Tree::new(index);
|
||||
let tree_hash = tree.get_hash();
|
||||
|
||||
let loaded_tree = Tree::load(&tree_hash);
|
||||
@@ -177,19 +178,19 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_get_recursive_blobs() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let index = Index::get_instance();
|
||||
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);
|
||||
test_util::ensure_test_file(&test_file, None);
|
||||
test::ensure_file(&test_file, None);
|
||||
let blob = Blob::new(util::read_workfile(&test_file));
|
||||
test_blobs.push(blob.clone());
|
||||
index.add(test_file.clone(), FileMetaData::new(&Blob::new(util::read_workfile(&test_file)), &test_file));
|
||||
}
|
||||
|
||||
let tree = Tree::new(&index);
|
||||
let tree = Tree::new(index);
|
||||
let tree_hash = tree.get_hash();
|
||||
|
||||
let loaded_tree = Tree::load(&tree_hash);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod util;
|
||||
pub mod head;
|
||||
pub mod path_ext;
|
||||
pub use path_ext::PathExt;
|
||||
pub mod store;
|
||||
pub mod test_util;
|
||||
pub mod test;
|
||||
pub mod util;
|
||||
|
||||
56
src/utils/path_ext.rs
Normal file
56
src/utils/path_ext.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use crate::utils::util;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/**
|
||||
Path的扩展 基于util 为了解耦,不要再util中使用PathExt
|
||||
*/
|
||||
pub trait PathExt {
|
||||
fn to_absolute(&self) -> PathBuf;
|
||||
fn to_absolute_workdir(&self) -> PathBuf;
|
||||
fn to_relative(&self) -> PathBuf;
|
||||
fn to_relative_workdir(&self) -> PathBuf;
|
||||
fn is_sub_to(&self, parent: &Path) -> bool;
|
||||
fn include_in<T, U>(&self, paths: U) -> bool
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
U: IntoIterator<Item = T>;
|
||||
}
|
||||
/*
|
||||
在 Rust 中,当你调用一个方法时,Rust 会尝试自动解引用和自动引用(auto-deref and auto-ref)来匹配方法签名。
|
||||
如果有一个为 Path 实现的方法,你可以在 PathBuf、&PathBuf、&&PathBuf 等上调用这个方法,Rust 会自动进行必要的解引用。
|
||||
*/
|
||||
impl PathExt for Path {
|
||||
/// 转换为绝对路径
|
||||
fn to_absolute(&self) -> PathBuf {
|
||||
util::get_absolute_path(self)
|
||||
}
|
||||
|
||||
/// 转换为绝对路径(from workdir相对路径)
|
||||
fn to_absolute_workdir(&self) -> PathBuf {
|
||||
util::to_workdir_absolute_path(self)
|
||||
}
|
||||
|
||||
/// 转换为相对路径(to cur_dir)
|
||||
fn to_relative(&self) -> PathBuf {
|
||||
util::get_relative_path(self)
|
||||
}
|
||||
|
||||
/// 转换为相对路径(to workdir)
|
||||
fn to_relative_workdir(&self) -> PathBuf {
|
||||
util::to_workdir_relative_path(self)
|
||||
}
|
||||
|
||||
/// 从字符串角度判断path是否是parent的子路径(不检测存在性)
|
||||
fn is_sub_to(&self, parent: &Path) -> bool {
|
||||
util::is_sub_path(self, parent)
|
||||
}
|
||||
|
||||
/// 判断是否在paths中(包括子目录),不检查存在
|
||||
fn include_in<T, U>(&self, paths: U) -> bool
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
U: IntoIterator<Item = T>,
|
||||
{
|
||||
util::include_in_paths(self, paths)
|
||||
}
|
||||
}
|
||||
@@ -58,10 +58,7 @@ impl Store {
|
||||
result = Some(object);
|
||||
}
|
||||
}
|
||||
match result {
|
||||
None => None,
|
||||
Some(result) => Some(result),
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn save(&self, content: &String) -> Hash {
|
||||
@@ -83,6 +80,7 @@ impl Store {
|
||||
|
||||
pub fn dry_save(&self, content: &String) -> Hash {
|
||||
/* 不实际保存文件,返回Hash */
|
||||
#[warn(clippy::let_and_return)]
|
||||
let hash = Self::calc_hash(content);
|
||||
// TODO more such as check
|
||||
hash
|
||||
@@ -93,24 +91,24 @@ mod tests {
|
||||
use std::fs;
|
||||
|
||||
use super::*;
|
||||
use crate::utils::test_util;
|
||||
use crate::utils::test;
|
||||
|
||||
#[test]
|
||||
fn test_new_success() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let _ = Store::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_new_fail() {
|
||||
test_util::setup_test_without_mit();
|
||||
test::setup_without_mit();
|
||||
let _ = Store::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_and_load() {
|
||||
let _ = test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let store = Store::new();
|
||||
let content = "hello world".to_string();
|
||||
let hash = store.save(&content);
|
||||
@@ -120,7 +118,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_search() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let hashs = vec!["1234567890".to_string(), "1235467891".to_string(), "4567892".to_string()];
|
||||
for hash in hashs.iter() {
|
||||
let mut path = util::get_storage_path().unwrap();
|
||||
|
||||
@@ -8,33 +8,35 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::models::Index;
|
||||
use crate::utils::PathExt;
|
||||
|
||||
// 执行测试的储存库
|
||||
use super::util;
|
||||
/* tools for test */
|
||||
fn find_cargo_dir() -> PathBuf {
|
||||
let cargo_path = std::env::var("CARGO_MANIFEST_DIR");
|
||||
if cargo_path.is_err() {
|
||||
// vscode DEBUG test没有CARGO_MANIFEST_DIR宏,手动尝试查找cargo.toml
|
||||
let mut path = util::cur_dir();
|
||||
loop {
|
||||
path.push("Cargo.toml");
|
||||
if path.exists() {
|
||||
break;
|
||||
}
|
||||
if !path.pop() {
|
||||
panic!("找不到CARGO_MANIFEST_DIR");
|
||||
match cargo_path {
|
||||
Ok(path) => PathBuf::from(path),
|
||||
Err(_) => {
|
||||
// vscode DEBUG test没有CARGO_MANIFEST_DIR宏,手动尝试查找cargo.toml
|
||||
let mut path = util::cur_dir();
|
||||
loop {
|
||||
path.push("Cargo.toml");
|
||||
if path.exists() {
|
||||
break;
|
||||
}
|
||||
if !path.pop() {
|
||||
panic!("找不到CARGO_MANIFEST_DIR");
|
||||
}
|
||||
}
|
||||
path.pop();
|
||||
path
|
||||
}
|
||||
path.pop();
|
||||
path
|
||||
} else {
|
||||
PathBuf::from(cargo_path.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// 准备测试环境,切换到测试目录
|
||||
fn setup_test_env() {
|
||||
fn setup_env() {
|
||||
color_backtrace::install(); // colorize backtrace
|
||||
|
||||
let mut path = find_cargo_dir();
|
||||
@@ -51,14 +53,14 @@ pub fn init_mit() {
|
||||
}
|
||||
|
||||
/// with 初始化的干净的mit
|
||||
pub fn setup_test_with_clean_mit() {
|
||||
setup_test_without_mit();
|
||||
pub fn setup_with_clean_mit() {
|
||||
setup_without_mit();
|
||||
init_mit();
|
||||
}
|
||||
|
||||
pub fn setup_test_without_mit() {
|
||||
pub fn setup_without_mit() {
|
||||
// 将执行目录切换到测试目录,并清除测试目录下的.mit目录
|
||||
setup_test_env();
|
||||
setup_env();
|
||||
let mut path = util::cur_dir();
|
||||
path.push(util::ROOT_DIR);
|
||||
if path.exists() {
|
||||
@@ -66,9 +68,9 @@ pub fn setup_test_without_mit() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_test_files<T: AsRef<str>>(paths: &Vec<T>) {
|
||||
pub fn ensure_files<T: AsRef<str>>(paths: &Vec<T>) {
|
||||
for path in paths {
|
||||
ensure_test_file(path.as_ref().as_ref(), None);
|
||||
ensure_file(path.as_ref().as_ref(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,22 +87,22 @@ pub fn ensure_empty_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_test_with_empty_workdir() {
|
||||
pub fn setup_with_empty_workdir() {
|
||||
let test_dir = find_cargo_dir().join(TEST_DIR);
|
||||
ensure_empty_dir(&test_dir).unwrap();
|
||||
setup_test_with_clean_mit();
|
||||
ensure_empty_dir(test_dir).unwrap();
|
||||
setup_with_clean_mit();
|
||||
}
|
||||
|
||||
pub fn ensure_test_file(path: &Path, content: Option<&str>) {
|
||||
pub fn ensure_file(path: &Path, content: Option<&str>) {
|
||||
// 以测试目录为根目录,创建文件
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap(); // ensure父目录
|
||||
let mut file = fs::File::create(util::get_working_dir().unwrap().join(path))
|
||||
.expect(format!("无法创建文件:{:?}", path).as_str());
|
||||
.unwrap_or_else(|_| panic!("无法创建文件:{:?}", path));
|
||||
if let Some(content) = content {
|
||||
file.write(content.as_bytes()).unwrap();
|
||||
file.write_all(content.as_bytes()).unwrap();
|
||||
} else {
|
||||
// 写入文件名
|
||||
file.write(path.file_name().unwrap().to_str().unwrap().as_bytes()).unwrap();
|
||||
file.write_all(path.file_name().unwrap().to_str().unwrap().as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,9 +114,9 @@ pub fn ensure_no_file(path: &Path) {
|
||||
}
|
||||
|
||||
/** 列出子文件夹 */
|
||||
pub fn list_subpath(path: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
pub fn list_subdir(path: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
let mut files = Vec::new();
|
||||
let path = util::get_absolute_path(path);
|
||||
let path = path.to_absolute();
|
||||
if path.is_dir() {
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
@@ -125,4 +127,4 @@ pub fn list_subpath(path: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
}
|
||||
}
|
||||
Ok(files)
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,7 @@ pub const ROOT_DIR: &str = ".mit";
|
||||
pub fn storage_exist() -> bool {
|
||||
/*检查是否存在储存库 */
|
||||
let rt = get_storage_path();
|
||||
match rt {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
rt.is_ok()
|
||||
}
|
||||
|
||||
pub fn check_repo_exist() {
|
||||
@@ -47,11 +44,7 @@ pub fn get_storage_path() -> Result<PathBuf, io::Error> {
|
||||
|
||||
/// 获取项目工作区目录, 也就是.mit的父目录
|
||||
pub fn get_working_dir() -> Option<PathBuf> {
|
||||
if let Some(path) = get_storage_path().unwrap().parent() {
|
||||
Some(path.to_path_buf())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
get_storage_path().unwrap().parent().map(|path| path.to_path_buf())
|
||||
}
|
||||
|
||||
/// 检查文件是否在dir内(包括子文件夹), 若不存在则false
|
||||
@@ -112,7 +105,7 @@ where
|
||||
F: Fn(&T) -> T,
|
||||
{
|
||||
//items可以是一个引用
|
||||
items.into_iter().map(|item| func(item)).collect::<O>()
|
||||
items.into_iter().map(func).collect::<O>()
|
||||
}
|
||||
|
||||
/// 过滤列表中的元素,使其在paths中(包括子目录),不检查存在性
|
||||
@@ -132,7 +125,7 @@ pub fn is_inside_repo(file: &Path) -> bool {
|
||||
}
|
||||
|
||||
pub fn format_time(time: &std::time::SystemTime) -> String {
|
||||
let datetime: chrono::DateTime<chrono::Utc> = time.clone().into();
|
||||
let datetime: chrono::DateTime<chrono::Utc> = (*time).into();
|
||||
datetime.format("%Y-%m-%d %H:%M:%S.%3f").to_string()
|
||||
}
|
||||
|
||||
@@ -172,7 +165,7 @@ pub fn include_root_dir(dir: &Path) -> bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
false
|
||||
}
|
||||
|
||||
/// 级联删除空目录,直到遇到 [工作区根目录 | 当前目录]
|
||||
@@ -218,7 +211,7 @@ pub fn list_workdir_files() -> Vec<PathBuf> {
|
||||
}
|
||||
|
||||
/// 获取相对于dir的 规范化 相对路径(不包含../ ./)
|
||||
pub fn get_relative_path(path: &Path, dir: &Path) -> PathBuf {
|
||||
pub fn get_relative_path_to_dir(path: &Path, dir: &Path) -> PathBuf {
|
||||
// 先统一为绝对路径
|
||||
let abs_path = if path.is_relative() {
|
||||
get_absolute_path(path)
|
||||
@@ -253,12 +246,12 @@ pub fn get_common_dir(p1: &Path, p2: &Path) -> PathBuf {
|
||||
|
||||
/// 获取相较于工作区(Working Dir)的相对路径
|
||||
pub fn to_workdir_relative_path(path: &Path) -> PathBuf {
|
||||
get_relative_path(path, &get_working_dir().unwrap())
|
||||
get_relative_path_to_dir(path, &get_working_dir().unwrap())
|
||||
}
|
||||
|
||||
/// 获取相较于当前目录的 规范化 相对路径(不包含../ ./)
|
||||
pub fn to_cur_relative_path(path: &Path) -> PathBuf {
|
||||
get_relative_path(path, &cur_dir())
|
||||
pub fn get_relative_path(path: &Path) -> PathBuf {
|
||||
get_relative_path_to_dir(path, &cur_dir())
|
||||
}
|
||||
|
||||
/// 获取相较于工作区(Working Dir)的绝对路径
|
||||
@@ -333,7 +326,7 @@ pub fn get_absolute_path_to_dir(path: &Path, dir: &Path) -> PathBuf {
|
||||
pub fn integrate_paths(paths: &Vec<PathBuf>) -> HashSet<PathBuf> {
|
||||
let mut abs_paths = HashSet::new();
|
||||
for path in paths {
|
||||
let path = get_absolute_path(&path); // 统一转换为绝对路径
|
||||
let path = get_absolute_path(path); // 统一转换为绝对路径
|
||||
if path.is_dir() {
|
||||
// 包括目录下的所有文件(子文件夹)
|
||||
let files = list_files(&path).unwrap();
|
||||
@@ -392,7 +385,7 @@ mod tests {
|
||||
use crate::{
|
||||
models::{blob::Blob, index::Index},
|
||||
utils::{
|
||||
test_util,
|
||||
test,
|
||||
util::{self, *},
|
||||
},
|
||||
};
|
||||
@@ -404,17 +397,17 @@ mod tests {
|
||||
Ok(path) => println!("{:?}", path),
|
||||
Err(err) => match err.kind() {
|
||||
std::io::ErrorKind::NotFound => println!("Not a git repository"),
|
||||
_ => assert!(false, "Unexpected error"),
|
||||
_ => unreachable!("Unexpected error"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integrate_paths() {
|
||||
let mut paths = Vec::new();
|
||||
paths.push(PathBuf::from("src/utils"));
|
||||
paths.push(PathBuf::from("../test_del.txt"));
|
||||
paths.push(PathBuf::from("src/utils/util.rs"));
|
||||
let paths = ["src/utils", "../test_del.txt", "src/utils/util.rs"]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<PathBuf>>();
|
||||
// paths.push(PathBuf::from("."));
|
||||
let abs_paths = integrate_paths(&paths);
|
||||
for path in abs_paths {
|
||||
@@ -437,9 +430,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_relative_path() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let path = Path::new("../../src\\main.rs");
|
||||
let rel_path = get_relative_path(&path, &cur_dir());
|
||||
let rel_path = get_relative_path_to_dir(path, &cur_dir());
|
||||
println!("{:?}", rel_path);
|
||||
|
||||
assert_eq!(rel_path, path);
|
||||
@@ -447,7 +440,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_to_workdir_absolute_path() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
let path = Path::new("./src/../main.rs");
|
||||
let abs_path = to_workdir_absolute_path(path);
|
||||
println!("{:?}", abs_path);
|
||||
@@ -467,10 +460,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_list_files() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test_util::ensure_test_file(Path::new("test/test.txt"), None);
|
||||
test_util::ensure_test_file(Path::new("a.txt"), None);
|
||||
test_util::ensure_test_file(Path::new("b.txt"), None);
|
||||
test::setup_with_clean_mit();
|
||||
test::ensure_file(Path::new("test/test.txt"), None);
|
||||
test::ensure_file(Path::new("a.txt"), None);
|
||||
test::ensure_file(Path::new("b.txt"), None);
|
||||
let files = list_files(Path::new("./"));
|
||||
match files {
|
||||
Ok(files) => {
|
||||
@@ -486,13 +479,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_check_object_type() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
assert_eq!(check_object_type("123".into()), ObjectType::Invalid);
|
||||
test_util::ensure_test_file(Path::new("test.txt"), Some("test"));
|
||||
test::ensure_file(Path::new("test.txt"), Some("test"));
|
||||
let content = util::read_workfile(get_working_dir().unwrap().join("test.txt").as_path());
|
||||
let hash = Blob::new(content).get_hash();
|
||||
assert_eq!(check_object_type(hash), ObjectType::Blob);
|
||||
let mut commit = Commit::new(&Index::get_instance(), vec![], "test".to_string());
|
||||
let mut commit = Commit::new(Index::get_instance(), vec![], "test".to_string());
|
||||
assert_eq!(check_object_type(commit.get_tree_hash()), ObjectType::Tree);
|
||||
commit.save();
|
||||
assert_eq!(check_object_type(commit.get_hash()), ObjectType::Commit);
|
||||
@@ -500,19 +493,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_check_root_dir() {
|
||||
test_util::setup_test_with_clean_mit();
|
||||
test::setup_with_clean_mit();
|
||||
list_workdir_files().iter().for_each(|f| {
|
||||
fs::remove_file(f).unwrap();
|
||||
});
|
||||
test_util::list_subpath(Path::new("./")).unwrap().iter().for_each(|f| {
|
||||
test::list_subdir(Path::new("./")).unwrap().iter().for_each(|f| {
|
||||
if include_root_dir(f) {
|
||||
fs::remove_dir_all(f).unwrap();
|
||||
}
|
||||
});
|
||||
assert_eq!(include_root_dir(Path::new("./")), true);
|
||||
assert!(include_root_dir(Path::new("./")));
|
||||
fs::create_dir("./src").unwrap_or_default();
|
||||
assert_eq!(include_root_dir(Path::new("./src")), false);
|
||||
fs::create_dir("./src/.mit").unwrap_or_default();
|
||||
assert_eq!(include_root_dir(Path::new("./src")), true);
|
||||
assert!(include_root_dir(Path::new("./src")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user