Compare commits
No commits in common. "c5d7135d99071815d22d8f09d7687fd58989dc6b" and "2364cadfd5070c146ade07290652e1ad4f738c54" have entirely different histories.
c5d7135d99
...
2364cadfd5
@ -1,3 +1 @@
|
|||||||
pub mod add;
|
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod status;
|
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
use crate::config::config::Config;
|
|
||||||
use crate::store::{ignorer::Ignorer, nsobject::NsObject};
|
|
||||||
use std::fs;
|
|
||||||
// use glob::glob;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
pub struct AddArgs {
|
|
||||||
/// List of files/dirs to add
|
|
||||||
pub files: Vec<String>,
|
|
||||||
/// Allow ignored files
|
|
||||||
pub force: bool,
|
|
||||||
/// Select all files
|
|
||||||
pub all: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exec(args: AddArgs, config: Config) {
|
|
||||||
// Init ignorer
|
|
||||||
let mut ignorer = Ignorer::new(config.get_root_unsafe());
|
|
||||||
ignorer.active_nsignore(!args.force);
|
|
||||||
|
|
||||||
if args.all {
|
|
||||||
return add_dir(config.get_root_unsafe(), &mut ignorer);
|
|
||||||
}
|
|
||||||
|
|
||||||
for obj_to_add in args.files.iter() {
|
|
||||||
let mut path_to_add = config.execution_path.clone();
|
|
||||||
path_to_add.push(PathBuf::from(obj_to_add));
|
|
||||||
|
|
||||||
if path_to_add.exists() {
|
|
||||||
if path_to_add.is_dir() {
|
|
||||||
add_dir(&path_to_add, &mut ignorer);
|
|
||||||
} else {
|
|
||||||
add_to_index(&path_to_add);
|
|
||||||
}
|
|
||||||
} else if NsObject::from_local_path(&path_to_add).exists() {
|
|
||||||
add_to_index(&path_to_add);
|
|
||||||
} else {
|
|
||||||
// try globbing
|
|
||||||
// todo!()
|
|
||||||
// else panic
|
|
||||||
panic!("fatal: pathspec '{}' did not match any files", obj_to_add);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
for all files
|
|
||||||
if globbing
|
|
||||||
take all match glob nextsyncignore
|
|
||||||
else
|
|
||||||
if dir
|
|
||||||
add dir
|
|
||||||
add all childs with nextsyncignore
|
|
||||||
else
|
|
||||||
add files
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add all files in a directory
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// * `dir`: the directory to add from
|
|
||||||
/// * `force`: true if we should apply the nextsyncignore's rules
|
|
||||||
fn add_dir(dir: &PathBuf, ignorer: &mut Ignorer) {
|
|
||||||
let entries = match fs::read_dir(dir) {
|
|
||||||
Ok(entries) => entries,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Failed to read {} ({err})", dir.display());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read locals files and folders
|
|
||||||
for entry in entries {
|
|
||||||
let entry = match entry {
|
|
||||||
Ok(entry) => entry,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Failed to read entry {err}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ignore object if it is ns config file or nsignored
|
|
||||||
if ignorer.should_ignore(&entry.path()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.path().is_dir() {
|
|
||||||
add_dir(&entry.path(), ignorer);
|
|
||||||
add_to_index(&entry.path());
|
|
||||||
} else {
|
|
||||||
add_to_index(&entry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_to_index(path: &PathBuf) {
|
|
||||||
println!("{}", path.display());
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
use crate::config::config::Config;
|
use crate::config::config::Config;
|
||||||
use std::fs::{DirBuilder, File};
|
use std::fs::{create_dir_all, DirBuilder, File};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub struct InitArgs {}
|
pub struct InitArgs {}
|
||||||
@ -32,7 +32,7 @@ pub fn exec(_: InitArgs, config: Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut binding = DirBuilder::new();
|
let mut binding = DirBuilder::new();
|
||||||
let builder = binding.recursive(true);
|
let mut builder = binding.recursive(true);
|
||||||
|
|
||||||
for dir in &["objects", "refs"] {
|
for dir in &["objects", "refs"] {
|
||||||
path.push(dir);
|
path.push(dir);
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
use crate::config::config::Config;
|
|
||||||
use crate::store::{
|
|
||||||
nsobject::NsObject,
|
|
||||||
object::{Obj, ObjStatus},
|
|
||||||
};
|
|
||||||
use crate::utils::path;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use threadpool::ThreadPool;
|
|
||||||
|
|
||||||
pub struct StatusArgs {
|
|
||||||
pub nostyle: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ObjStatuses {
|
|
||||||
created: Arc<Mutex<HashMap<String, Obj>>>,
|
|
||||||
modified: Arc<Mutex<HashMap<String, Obj>>>,
|
|
||||||
deleted: Arc<Mutex<HashMap<String, Obj>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjStatuses {
|
|
||||||
fn new() -> Self {
|
|
||||||
ObjStatuses {
|
|
||||||
created: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
modified: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
deleted: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_created(&self, key: String, value: Obj) {
|
|
||||||
self.created.lock().unwrap().insert(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_modified(&self, key: String, value: Obj) {
|
|
||||||
self.modified.lock().unwrap().insert(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_deleted(&self, key: String, value: Obj) {
|
|
||||||
self.deleted.lock().unwrap().insert(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exec(args: StatusArgs, config: Config) {
|
|
||||||
// use root of repo if no custom path has been set by the command
|
|
||||||
let root = if config.is_custom_execution_path {
|
|
||||||
config.execution_path.clone()
|
|
||||||
} else {
|
|
||||||
config.get_root_unsafe().to_path_buf()
|
|
||||||
};
|
|
||||||
|
|
||||||
let pool = ThreadPool::new(4);
|
|
||||||
let repo_root = config.get_root_unsafe().clone();
|
|
||||||
let res = Arc::new(ObjStatuses::new());
|
|
||||||
|
|
||||||
let pool_clone = pool.clone();
|
|
||||||
let res_clone = Arc::clone(&res);
|
|
||||||
pool.execute(move || {
|
|
||||||
compare_dir(pool_clone, &repo_root, &root, res_clone);
|
|
||||||
});
|
|
||||||
|
|
||||||
pool.join();
|
|
||||||
|
|
||||||
for entry in res.created.lock().unwrap().iter() {
|
|
||||||
println!("created: {}", entry.0);
|
|
||||||
}
|
|
||||||
for entry in res.modified.lock().unwrap().iter() {
|
|
||||||
println!("modified: {}", entry.0);
|
|
||||||
}
|
|
||||||
for entry in res.deleted.lock().unwrap().iter() {
|
|
||||||
println!("deleted: {}", entry.0);
|
|
||||||
}
|
|
||||||
// find moved and copied
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// * `pool`:
|
|
||||||
/// * `root_path`: path of the repo's root
|
|
||||||
/// * `path`: path we should analyze
|
|
||||||
/// * `res`: the struct in which we should store the response
|
|
||||||
fn compare_dir(pool: ThreadPool, root_path: &PathBuf, path: &PathBuf, res: Arc<ObjStatuses>) {
|
|
||||||
let entries = match fs::read_dir(path) {
|
|
||||||
Ok(entries) => entries,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Failed to read {} ({err})", path.display());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut local_files = HashMap::new();
|
|
||||||
let mut local_dirs = HashMap::new();
|
|
||||||
|
|
||||||
// Read locals files and folders
|
|
||||||
for entry in entries {
|
|
||||||
let entry = match entry {
|
|
||||||
Ok(entry) => entry,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Failed to read entry {err}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let repo_relative_entry = path::to_repo_relative(&entry.path(), root_path);
|
|
||||||
let local_obj = Obj::from_local_path(&repo_relative_entry);
|
|
||||||
|
|
||||||
if entry.path().is_dir() {
|
|
||||||
if entry.path().ends_with(".nextsync") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if local_obj.is_new() {
|
|
||||||
// TODO: opti move files in new directory
|
|
||||||
res.push_created(local_obj.cpy_path(), local_obj);
|
|
||||||
} else {
|
|
||||||
local_dirs.insert(local_obj.cpy_path(), local_obj);
|
|
||||||
|
|
||||||
// Analyze sub folder
|
|
||||||
let pool_clone = pool.clone();
|
|
||||||
let root_path_clone = root_path.clone();
|
|
||||||
let res_clone = Arc::clone(&res);
|
|
||||||
pool.execute(move || {
|
|
||||||
compare_dir(pool_clone, &root_path_clone, &entry.path(), res_clone);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if local_obj.is_new() {
|
|
||||||
res.push_created(local_obj.cpy_path(), local_obj);
|
|
||||||
} else {
|
|
||||||
local_files.insert(local_obj.cpy_path(), local_obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read ns objects to find deleted
|
|
||||||
let entries = NsObject::from_local_path(&path);
|
|
||||||
for entry in entries.iter() {
|
|
||||||
if entry.is_file() {
|
|
||||||
match local_files.get(entry.get_obj_path().to_str().unwrap()) {
|
|
||||||
None => res.push_deleted(path::to_string(entry.get_obj_path()), {
|
|
||||||
let mut obj = entry.get_local_obj();
|
|
||||||
obj.set_status(ObjStatus::Deleted);
|
|
||||||
obj
|
|
||||||
}),
|
|
||||||
Some(local_obj) if local_obj != entry => {
|
|
||||||
res.push_modified(local_obj.cpy_path(), local_obj.clone())
|
|
||||||
}
|
|
||||||
Some(_) => (),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match local_dirs.get(entry.get_obj_path().to_str().unwrap()) {
|
|
||||||
None => res.push_deleted(path::to_string(entry.get_obj_path()), {
|
|
||||||
let mut obj = entry.get_local_obj();
|
|
||||||
obj.set_status(ObjStatus::Deleted);
|
|
||||||
obj
|
|
||||||
}),
|
|
||||||
Some(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +1,20 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// * `execution_path`: path of the command (directory arg) or current path
|
/// * `execution_path`: path of the command (directory arg) or current path
|
||||||
/// * `root`: path of the repo
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub execution_path: PathBuf,
|
pub execution_path: PathBuf,
|
||||||
pub is_custom_execution_path: bool,
|
|
||||||
root: OnceLock<Option<PathBuf>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn new() -> Self {
|
pub fn new(exec_path: Option<&String>) -> Self {
|
||||||
Config {
|
Config {
|
||||||
execution_path: PathBuf::from(env::current_dir().unwrap()),
|
execution_path: match exec_path {
|
||||||
is_custom_execution_path: false,
|
Some(path) => PathBuf::from(path),
|
||||||
root: OnceLock::new(),
|
None => PathBuf::from(env::current_dir().unwrap()),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from(exec_path: Option<&String>) -> Self {
|
|
||||||
match exec_path {
|
|
||||||
Some(path) => Config {
|
|
||||||
execution_path: PathBuf::from(path),
|
|
||||||
is_custom_execution_path: true,
|
|
||||||
root: OnceLock::new(),
|
|
||||||
},
|
},
|
||||||
None => Config::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_root(&self) -> &Option<PathBuf> {
|
|
||||||
self.root.get_or_init(|| {
|
|
||||||
let mut path = self.execution_path.clone();
|
|
||||||
|
|
||||||
let root = loop {
|
|
||||||
path.push(".nextsync");
|
|
||||||
if path.exists() {
|
|
||||||
path.pop();
|
|
||||||
break Some(path);
|
|
||||||
}
|
|
||||||
path.pop();
|
|
||||||
path.pop();
|
|
||||||
if path == Path::new("/") {
|
|
||||||
break None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
root
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_root_unsafe(&self) -> &PathBuf {
|
|
||||||
match self.get_root() {
|
|
||||||
Some(path) => path,
|
|
||||||
None => panic!("fatal: not a nextsync repository (or any parent up to mount point /)"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -2,28 +2,20 @@ use clap::Command;
|
|||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
mod store;
|
|
||||||
mod subcommands;
|
mod subcommands;
|
||||||
mod utils;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let app = Command::new("Nextsync")
|
let app = Command::new("Nextsync")
|
||||||
.version("1.0")
|
.version("1.0")
|
||||||
.author("grimhilt")
|
.author("grimhilt")
|
||||||
.about("A git-line command line tool to interact with nextcloud")
|
.about("A git-line command line tool to interact with nextcloud")
|
||||||
.subcommands([
|
.subcommands([subcommands::init::create()]);
|
||||||
subcommands::init::create(),
|
|
||||||
subcommands::add::create(),
|
|
||||||
subcommands::status::create(),
|
|
||||||
]);
|
|
||||||
// .setting(clap::AppSettings::SubcommandRequiredElseHelp);
|
// .setting(clap::AppSettings::SubcommandRequiredElseHelp);
|
||||||
|
|
||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
Some(("init", args)) => subcommands::init::handler(args),
|
Some(("init", args)) => subcommands::init::handler(args),
|
||||||
Some(("add", args)) => subcommands::add::handler(args),
|
|
||||||
Some(("status", args)) => subcommands::status::handler(args),
|
|
||||||
Some((_, _)) => {}
|
Some((_, _)) => {}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
pub mod ignorer;
|
|
||||||
pub mod nsignore;
|
|
||||||
pub mod object;
|
|
||||||
pub mod nsobject;
|
|
@ -1,57 +0,0 @@
|
|||||||
use crate::store::nsignore::{get_nsignore_file, get_nsignore_rules, is_file_ignored};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
|
||||||
pub struct Ignorer {
|
|
||||||
/// Path of this level
|
|
||||||
path: PathBuf,
|
|
||||||
/// Should it use the nextsyncignore file
|
|
||||||
use_nsignore: bool,
|
|
||||||
/// Nsignore's rules
|
|
||||||
rules: OnceLock<Vec<String>>,
|
|
||||||
childs: Option<Vec<Box<Ignorer>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ignorer {
|
|
||||||
pub fn new(path: &PathBuf) -> Self {
|
|
||||||
Ignorer {
|
|
||||||
path: path.to_path_buf(),
|
|
||||||
use_nsignore: true,
|
|
||||||
rules: OnceLock::new(),
|
|
||||||
childs: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_nsignore(&mut self, active: bool) {
|
|
||||||
self.use_nsignore = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rules(&mut self) -> &Vec<String> {
|
|
||||||
self.rules
|
|
||||||
.get_or_init(|| match get_nsignore_file(&self.path) {
|
|
||||||
Some(nsignore_path) => get_nsignore_rules(&nsignore_path),
|
|
||||||
None => Vec::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_config_file(&self, path: &PathBuf) -> bool {
|
|
||||||
path.ends_with(".nextsync")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether a file/dir is ignored or not
|
|
||||||
///
|
|
||||||
/// * `path`:
|
|
||||||
fn is_ignored(&mut self, path: &PathBuf) -> bool {
|
|
||||||
is_file_ignored(path.to_str().unwrap(), self.get_rules())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether a file/dir should be ignored by the program or not
|
|
||||||
///
|
|
||||||
/// This takes advantage of the `use_nsignore` variable set by
|
|
||||||
/// `active_nsignore`.
|
|
||||||
///
|
|
||||||
/// * `path`:
|
|
||||||
pub fn should_ignore(&mut self, path: &PathBuf) -> bool {
|
|
||||||
self.is_config_file(path) || (self.use_nsignore && self.is_ignored(path))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
use crate::store::object::{Obj, ObjMetadata, ObjType};
|
|
||||||
use crypto::digest::Digest;
|
|
||||||
use crypto::sha1::Sha1;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
|
||||||
type NsObjectChilds = Vec<Box<NsObject>>;
|
|
||||||
|
|
||||||
struct NsObjectPath {
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for NsObjectPath {
|
|
||||||
fn from(hash: &str) -> Self {
|
|
||||||
let (dir, res) = hash.split_at(2);
|
|
||||||
|
|
||||||
let mut ns_obj_path = PathBuf::new();
|
|
||||||
ns_obj_path.push(dir);
|
|
||||||
ns_obj_path.push(res);
|
|
||||||
|
|
||||||
NsObjectPath { path: ns_obj_path }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&PathBuf> for NsObjectPath {
|
|
||||||
fn from(obj_path: &PathBuf) -> Self {
|
|
||||||
let mut hasher = Sha1::new();
|
|
||||||
hasher.input_str(
|
|
||||||
obj_path
|
|
||||||
.to_str()
|
|
||||||
.expect("Cannot contains non UTF-8 char in path"),
|
|
||||||
);
|
|
||||||
NsObjectPath::from(hasher.result_str().as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NsObject {
|
|
||||||
pub obj_type: ObjType,
|
|
||||||
obj_path: OnceLock<PathBuf>,
|
|
||||||
nsobj_path: OnceLock<PathBuf>,
|
|
||||||
childs: OnceLock<NsObjectChilds>,
|
|
||||||
index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NsObject {
|
|
||||||
pub fn from_local_path(path: &PathBuf) -> Self {
|
|
||||||
NsObject {
|
|
||||||
obj_type: ObjType::Obj,
|
|
||||||
obj_path: OnceLock::from(path.to_path_buf()),
|
|
||||||
nsobj_path: OnceLock::new(),
|
|
||||||
childs: OnceLock::new(),
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_hash(hash: &str) -> Self {
|
|
||||||
NsObject {
|
|
||||||
obj_type: ObjType::Obj,
|
|
||||||
obj_path: OnceLock::new(),
|
|
||||||
nsobj_path: OnceLock::from(NsObjectPath::from(hash).path),
|
|
||||||
childs: OnceLock::new(),
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_obj_path(&self) -> &PathBuf {
|
|
||||||
self.obj_path.get_or_init(|| todo!())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_nsobj_path(&self) -> &PathBuf {
|
|
||||||
self.nsobj_path
|
|
||||||
.get_or_init(|| NsObjectPath::from(self.get_obj_path()).path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the existence of the nsobj in the store
|
|
||||||
pub fn exists(&self) -> bool {
|
|
||||||
println!("{}", self.get_nsobj_path().display());
|
|
||||||
self.get_nsobj_path().exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_childs(&self) -> &NsObjectChilds {
|
|
||||||
self.childs.get_or_init(|| {
|
|
||||||
if self.obj_type != ObjType::Tree {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_file(&self) -> bool {
|
|
||||||
self.obj_type == ObjType::Blob
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_dir(&self) -> bool {
|
|
||||||
self.obj_type == ObjType::Tree
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_obj(&self) -> Obj {
|
|
||||||
let mut obj = Obj::from_local_path(self.get_obj_path());
|
|
||||||
obj.set_type(self.obj_type.clone());
|
|
||||||
obj
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Saving the nsobj in the store
|
|
||||||
///
|
|
||||||
/// first line consists of all size fixed datas (obj_type, size, modified)
|
|
||||||
/// next lines are the size variable datas
|
|
||||||
///
|
|
||||||
/// * if it is a Tree obj after an empty line there will be the definition
|
|
||||||
/// of its subobjs (one line by subobj) *
|
|
||||||
/// obj_type + hash
|
|
||||||
pub fn save(&self) -> Result<(), ()> {
|
|
||||||
if !self.get_obj_path().exists {
|
|
||||||
// delete current obj
|
|
||||||
// delete reference on parent
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_metadata(&self) -> Option<ObjMetadata> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> NsObjectIter<'_> {
|
|
||||||
NsObjectIter {
|
|
||||||
nsobject: self,
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NsObjectIter<'a> {
|
|
||||||
nsobject: &'a NsObject,
|
|
||||||
index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for NsObjectIter<'a> {
|
|
||||||
type Item = &'a NsObject;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.index += 1;
|
|
||||||
match self.nsobject.get_childs().get(self.index - 1) {
|
|
||||||
None => None,
|
|
||||||
Some(obj) => Some(&**obj),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
use crate::store::nsobject::NsObject;
|
|
||||||
use crate::utils::path;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
const MAX_SIZE_TO_USE_HASH: u64 = 12 * 1024 * 1024;
|
|
||||||
|
|
||||||
pub struct ObjMetadata {
|
|
||||||
size: u64,
|
|
||||||
modified: Option<SystemTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone)]
|
|
||||||
pub enum ObjType {
|
|
||||||
Obj,
|
|
||||||
Blob,
|
|
||||||
Tree,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone)]
|
|
||||||
pub enum ObjStatus {
|
|
||||||
Undefined,
|
|
||||||
Created,
|
|
||||||
Moved,
|
|
||||||
Copied,
|
|
||||||
Deleted,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Obj {
|
|
||||||
obj_type: ObjType,
|
|
||||||
status: OnceLock<ObjStatus>,
|
|
||||||
/// path of the object from root
|
|
||||||
obj_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Obj {
|
|
||||||
pub fn from_local_path(path: &PathBuf) -> Self {
|
|
||||||
// todo set state
|
|
||||||
Obj {
|
|
||||||
obj_type: ObjType::Obj,
|
|
||||||
status: OnceLock::new(),
|
|
||||||
obj_path: path.to_path_buf(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_status(&self) -> &ObjStatus {
|
|
||||||
self.status.get_or_init(|| {
|
|
||||||
// read path
|
|
||||||
ObjStatus::Created
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_status(&mut self, status: ObjStatus) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_type(&mut self, obj_type: ObjType) {
|
|
||||||
self.obj_type = obj_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_new(&self) -> bool {
|
|
||||||
self.get_status() == &ObjStatus::Created
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cpy_path(&self) -> String {
|
|
||||||
path::to_string(&self.obj_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_metadata(&self) -> Option<ObjMetadata> {
|
|
||||||
let metadata = match fs::metadata(&self.obj_path) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!(
|
|
||||||
"Failed to read metadata of {} ({})",
|
|
||||||
self.obj_path.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(ObjMetadata {
|
|
||||||
size: metadata.len(),
|
|
||||||
modified: metadata.modified().ok(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<NsObject> for Obj {
|
|
||||||
fn eq(&self, other: &NsObject) -> bool {
|
|
||||||
if self.obj_type != other.obj_type {
|
|
||||||
eprintln!("Trying to compare different obj type");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.obj_type == ObjType::Tree {
|
|
||||||
panic!("fatal: cannot comparing folders");
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj_metadata = self.get_metadata();
|
|
||||||
let nsobj_metadata = other.get_metadata();
|
|
||||||
|
|
||||||
// If not metadata exist considere it is not the same
|
|
||||||
if obj_metadata.is_none() || nsobj_metadata.is_none() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj_metadata = obj_metadata.unwrap();
|
|
||||||
let nsobj_metadata = nsobj_metadata.unwrap();
|
|
||||||
|
|
||||||
// if we have modified and it is the same it must be the same file
|
|
||||||
if obj_metadata.modified.is_some() && nsobj_metadata.modified.is_some() {
|
|
||||||
if obj_metadata.modified.unwrap() == nsobj_metadata.modified.unwrap() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if size is same considere comparing hash if file is small
|
|
||||||
if obj_metadata.size == obj_metadata.size {
|
|
||||||
if obj_metadata.size < MAX_SIZE_TO_USE_HASH
|
|
||||||
&& nsobj_metadata.size < MAX_SIZE_TO_USE_HASH
|
|
||||||
{
|
|
||||||
todo!()
|
|
||||||
// return self.get_file_hash() == other.get_file_hash();
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +1 @@
|
|||||||
pub mod add;
|
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod status;
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
|
||||||
|
|
||||||
use crate::commands;
|
|
||||||
use crate::commands::add::AddArgs;
|
|
||||||
use crate::config::config::Config;
|
|
||||||
|
|
||||||
pub fn create() -> Command {
|
|
||||||
Command::new("add")
|
|
||||||
.arg(
|
|
||||||
Arg::new("files")
|
|
||||||
.required_unless_present("all")
|
|
||||||
.conflicts_with("all")
|
|
||||||
.num_args(1..)
|
|
||||||
.value_name("FILE")
|
|
||||||
.help("Files to add"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("force")
|
|
||||||
.short('f')
|
|
||||||
.long("force")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("Allow adding otherwise ignored files."),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("all")
|
|
||||||
.short('A')
|
|
||||||
.long("all")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("This adds, modifies, and removes index entries to match the working tree"),
|
|
||||||
)
|
|
||||||
.about("Add changes to the index")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handler(args: &ArgMatches) {
|
|
||||||
commands::add::exec(
|
|
||||||
AddArgs {
|
|
||||||
files: match args.get_many::<String>("files") {
|
|
||||||
None => vec![],
|
|
||||||
Some(vals) => vals.map(|s| s.to_string()).collect(),
|
|
||||||
},
|
|
||||||
force: *args.get_one::<bool>("force").unwrap(),
|
|
||||||
all: *args.get_one::<bool>("all").unwrap(),
|
|
||||||
},
|
|
||||||
Config::new(),
|
|
||||||
);
|
|
||||||
}
|
|
@ -16,5 +16,5 @@ pub fn create() -> Command {
|
|||||||
|
|
||||||
pub fn handler(args: &ArgMatches) {
|
pub fn handler(args: &ArgMatches) {
|
||||||
let exec_dir = args.get_one::<String>("directory");
|
let exec_dir = args.get_one::<String>("directory");
|
||||||
commands::init::exec(commands::init::InitArgs {}, Config::from(exec_dir));
|
commands::init::exec(commands::init::InitArgs {}, Config::new(exec_dir));
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
use clap::{Arg, ArgMatches, Command};
|
|
||||||
|
|
||||||
use crate::commands;
|
|
||||||
use crate::commands::status::StatusArgs;
|
|
||||||
use crate::config::config::Config;
|
|
||||||
|
|
||||||
pub fn create() -> Command {
|
|
||||||
Command::new("status")
|
|
||||||
.arg(Arg::new("directory").num_args(1).value_name("DIRECTORY"))
|
|
||||||
.arg(
|
|
||||||
Arg::new("nostyle")
|
|
||||||
.long("nostyle")
|
|
||||||
.help("Status with minium information and style"),
|
|
||||||
)
|
|
||||||
.about("Show the working tree status")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handler(args: &ArgMatches) {
|
|
||||||
commands::status::exec(
|
|
||||||
StatusArgs {
|
|
||||||
nostyle: args.contains_id("nostyle"),
|
|
||||||
},
|
|
||||||
Config::from(args.get_one::<String>("directory")),
|
|
||||||
);
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
pub mod path;
|
|
@ -1,9 +0,0 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
pub fn to_repo_relative(path: &PathBuf, root: &PathBuf) -> PathBuf {
|
|
||||||
path.strip_prefix(root).unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string(path: &PathBuf) -> String {
|
|
||||||
path.to_str().unwrap().to_string()
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user