use std::io::Write;
use std::path::{Path, PathBuf};
use canonical_path::CanonicalPathBuf;
use clap::{Parser, ValueHint};
use libpijul::{MutTxnT, MutTxnTExt, TxnTExt};
use log::{debug, info};
use pijul_repository::Repository;
#[derive(Parser, Debug)]
pub struct Move {
#[clap(hide = true, long = "salt")]
salt: Option<u64>,
#[clap(value_hint = ValueHint::AnyPath)]
paths: Vec<PathBuf>,
}
impl Move {
pub fn run(mut self) -> Result<(), anyhow::Error> {
let repo = Repository::find_root(None)?;
let to = if let Some(to) = self.paths.pop() {
to
} else {
return Ok(());
};
let is_dir = if let Ok(m) = std::fs::metadata(&to) {
m.is_dir()
} else {
false
};
if !is_dir && self.paths.len() > 1 {
return Ok(());
}
let mut txn = repo.pristine.mut_txn_begin()?;
let repo_path = CanonicalPathBuf::canonicalize(&repo.path)?;
for p in self.paths {
debug!("p = {:?}", p);
let source = std::fs::canonicalize(&p.clone())?;
debug!("source = {:?}", source);
let target = if is_dir {
to.join(source.file_name().unwrap())
} else {
to.clone()
};
debug!("target = {:?}", target);
let r = Rename {
source: &source,
target: &target,
};
std::fs::rename(r.source, r.target)?;
let target = std::fs::canonicalize(r.target)?;
debug!("target = {:?}", target);
{
let source = source.strip_prefix(&repo_path)?;
use path_slash::PathExt;
let source = source.to_slash_lossy();
let target = target.strip_prefix(&repo_path)?;
let target = target.to_slash_lossy();
debug!("moving {:?} -> {:?}", source, target);
txn.move_file(&source, &target, self.salt.unwrap_or(0))?;
}
std::mem::forget(r);
}
txn.commit()?;
Ok(())
}
}
struct Rename<'a> {
source: &'a Path,
target: &'a Path,
}
impl<'a> Drop for Rename<'a> {
fn drop(&mut self) {
std::fs::rename(self.target, self.source).unwrap_or(())
}
}
#[derive(Parser, Debug)]
pub struct List {
#[clap(long = "repository")]
repo_path: Option<PathBuf>,
}
impl List {
pub fn run(self) -> Result<(), anyhow::Error> {
let repo = Repository::find_root(self.repo_path)?;
let txn = repo.pristine.txn_begin()?;
let mut stdout = std::io::stdout();
let mut working_copy = txn.iter_working_copy().peekable();
if working_copy.peek().is_none() {
writeln!(stdout, "No tracked files")?;
return Ok(());
}
for p in working_copy {
let p = p?.1;
writeln!(stdout, "{}", p)?;
}
Ok(())
}
}
#[derive(Parser, Debug)]
pub struct Add {
#[clap(short = 'r', long = "recursive")]
recursive: bool,
#[clap(short = 'f', long = "force")]
force: bool,
#[clap(hide = true, long = "salt")]
salt: Option<u64>,
paths: Vec<PathBuf>,
}
impl Add {
pub fn run(self) -> Result<(), anyhow::Error> {
let repo = Repository::find_root(None)?;
let txn = repo.pristine.arc_txn_begin()?;
let threads = std::thread::available_parallelism()?.get();
let repo_path = CanonicalPathBuf::canonicalize(&repo.path)?;
let mut stderr = std::io::stderr();
for path in self.paths.iter() {
info!("Adding {:?}", path);
let path = CanonicalPathBuf::canonicalize(&path)?;
debug!("{:?}", path);
let meta = std::fs::metadata(&path)?;
debug!("{:?}", meta);
if !self.force
&& !libpijul::working_copy::filesystem::filter_ignore(
repo_path.as_ref(),
path.as_ref(),
meta.is_dir(),
)
{
continue;
}
if self.recursive {
use libpijul::working_copy::filesystem::*;
let (full, _) = get_prefix(Some(repo_path.as_ref()), path.as_path())?;
let full = CanonicalPathBuf::new(&full)?;
repo.working_copy.add_prefix_rec(
&txn,
repo_path.clone(),
full.clone(),
self.force,
threads,
self.salt.unwrap_or(0),
)?
} else {
let mut txn = txn.write();
let path = if let Ok(path) = path.as_path().strip_prefix(&repo_path.as_path()) {
path
} else {
continue;
};
use path_slash::PathExt;
let path_str = path.to_slash_lossy();
if path.is_dir() || path_str == "" {
let display_str = if path.is_dir() {
path_str.clone()
} else {
".".to_string()
};
writeln!(stderr, "The directory `{}` has been recorded, but its contents will not be tracked (use --recursive to override)", display_str)?;
}
if !txn.is_tracked(&path_str)? {
if let Err(e) = txn.add(&path_str, meta.is_dir(), self.salt.unwrap_or(0)) {
writeln!(stderr, "{}", e)?;
}
}
}
}
txn.commit()?;
writeln!(stderr, "Tracked {} path(s)", self.paths.iter().count())?;
Ok(())
}
}
#[derive(Parser, Debug)]
pub struct Remove {
paths: Vec<PathBuf>,
}
impl Remove {
pub fn run(self) -> Result<(), anyhow::Error> {
let repo = Repository::find_root(None)?;
let mut txn = repo.pristine.mut_txn_begin()?;
let repo_path = CanonicalPathBuf::canonicalize(&repo.path)?;
for path in self.paths.iter() {
debug!("{:?}", path);
if let Some(p) = path.file_name() {
if let Some(p) = p.to_str() {
if p.ends_with('~') || (p.starts_with('#') && p.ends_with('#')) {
continue;
}
}
}
let path = path.canonicalize()?;
let path = if let Ok(path) = path.strip_prefix(&repo_path.as_path()) {
path
} else {
continue;
};
use path_slash::PathExt;
let path_str = path.to_slash_lossy();
if txn.is_tracked(&path_str)? {
txn.remove_file(&path_str)?;
}
}
txn.commit()?;
Ok(())
}
}