1 use std::io::Write; 2 use std::path::{Path, PathBuf}; 3 4 use canonical_path::CanonicalPathBuf; 5 use clap::{ArgSettings, Parser}; 6 use libpijul::{MutTxnT, MutTxnTExt, TxnTExt}; 7 use log::{debug, info}; 8 9 use crate::repository::Repository; 10 11 #[derive(Parser, Debug)] 12 pub struct Move { 13 #[clap(setting = ArgSettings::Hidden, long = "salt")] 14 salt: Option<u64>, 15 /// Paths which need to be moved 16 /// 17 /// The last argument to this option is considered the 18 /// destination 19 paths: Vec<PathBuf>, 20 } 21 22 impl Move { run(mut self) -> Result<(), anyhow::Error>23 pub fn run(mut self) -> Result<(), anyhow::Error> { 24 let repo = Repository::find_root(None)?; 25 let to = if let Some(to) = self.paths.pop() { 26 to 27 } else { 28 return Ok(()); 29 }; 30 let is_dir = if let Ok(m) = std::fs::metadata(&to) { 31 m.is_dir() 32 } else { 33 false 34 }; 35 if !is_dir && self.paths.len() > 1 { 36 return Ok(()); 37 } 38 39 let mut txn = repo.pristine.mut_txn_begin()?; 40 let repo_path = CanonicalPathBuf::canonicalize(&repo.path)?; 41 for p in self.paths { 42 debug!("p = {:?}", p); 43 let source = std::fs::canonicalize(&p.clone())?; 44 debug!("source = {:?}", source); 45 let target = if is_dir { 46 to.join(source.file_name().unwrap()) 47 } else { 48 to.clone() 49 }; 50 debug!("target = {:?}", target); 51 52 let r = Rename { 53 source: &source, 54 target: &target, 55 }; 56 std::fs::rename(r.source, r.target)?; 57 let target = std::fs::canonicalize(r.target)?; 58 debug!("target = {:?}", target); 59 { 60 let source = source.strip_prefix(&repo_path)?; 61 use path_slash::PathExt; 62 let source = source.to_slash_lossy(); 63 let target = target.strip_prefix(&repo_path)?; 64 let target = target.to_slash_lossy(); 65 debug!("moving {:?} -> {:?}", source, target); 66 txn.move_file(&source, &target, self.salt.unwrap_or(0))?; 67 } 68 std::mem::forget(r); 69 } 70 txn.commit()?; 71 Ok(()) 72 } 73 } 74 75 struct Rename<'a> { 76 source: &'a Path, 77 target: &'a Path, 78 } 79 80 impl<'a> Drop for Rename<'a> { drop(&mut self)81 fn drop(&mut self) { 82 std::fs::rename(self.target, self.source).unwrap_or(()) 83 } 84 } 85 86 #[derive(Parser, Debug)] 87 pub struct List { 88 /// Set the repository where this command should run. Defaults to the first ancestor of the current directory that contains a `.pijul` directory. 89 #[clap(long = "repository")] 90 repo_path: Option<PathBuf>, 91 } 92 93 impl List { run(self) -> Result<(), anyhow::Error>94 pub fn run(self) -> Result<(), anyhow::Error> { 95 let repo = Repository::find_root(self.repo_path)?; 96 let txn = repo.pristine.txn_begin()?; 97 let mut stdout = std::io::stdout(); 98 for p in txn.iter_working_copy() { 99 let p = p?.1; 100 writeln!(stdout, "{}", p)?; 101 } 102 Ok(()) 103 } 104 } 105 106 #[derive(Parser, Debug)] 107 pub struct Add { 108 #[clap(short = 'r', long = "recursive")] 109 recursive: bool, 110 #[clap(short = 'f', long = "force")] 111 force: bool, 112 #[clap(setting = ArgSettings::Hidden, long = "salt")] 113 salt: Option<u64>, 114 /// Paths to add to the internal tree. 115 paths: Vec<PathBuf>, 116 } 117 118 impl Add { run(self) -> Result<(), anyhow::Error>119 pub fn run(self) -> Result<(), anyhow::Error> { 120 let repo = Repository::find_root(None)?; 121 let txn = repo.pristine.arc_txn_begin()?; 122 let threads = num_cpus::get(); 123 let repo_path = CanonicalPathBuf::canonicalize(&repo.path)?; 124 let mut stderr = std::io::stderr(); 125 for path in self.paths.iter() { 126 info!("Adding {:?}", path); 127 let path = CanonicalPathBuf::canonicalize(&path)?; 128 debug!("{:?}", path); 129 let meta = std::fs::metadata(&path)?; 130 debug!("{:?}", meta); 131 if !self.force 132 && !libpijul::working_copy::filesystem::filter_ignore( 133 repo_path.as_ref(), 134 path.as_ref(), 135 meta.is_dir(), 136 ) 137 { 138 continue; 139 } 140 if self.recursive { 141 use libpijul::working_copy::filesystem::*; 142 let (full, _) = get_prefix(Some(repo_path.as_ref()), path.as_path())?; 143 repo.working_copy.add_prefix_rec( 144 &txn, 145 repo_path.clone(), 146 full.clone(), 147 threads, 148 self.salt.unwrap_or(0), 149 )? 150 } else { 151 let mut txn = txn.write(); 152 let path = if let Ok(path) = path.as_path().strip_prefix(&repo_path.as_path()) { 153 path 154 } else { 155 continue; 156 }; 157 use path_slash::PathExt; 158 let path_str = path.to_slash_lossy(); 159 if !txn.is_tracked(&path_str)? { 160 if let Err(e) = txn.add(&path_str, meta.is_dir(), self.salt.unwrap_or(0)) { 161 writeln!(stderr, "{}", e)?; 162 } 163 } 164 } 165 } 166 txn.commit()?; 167 Ok(()) 168 } 169 } 170 171 #[derive(Parser, Debug)] 172 pub struct Remove { 173 /// The paths need to be removed 174 paths: Vec<PathBuf>, 175 } 176 177 impl Remove { run(self) -> Result<(), anyhow::Error>178 pub fn run(self) -> Result<(), anyhow::Error> { 179 let repo = Repository::find_root(None)?; 180 let mut txn = repo.pristine.mut_txn_begin()?; 181 let repo_path = CanonicalPathBuf::canonicalize(&repo.path)?; 182 for path in self.paths.iter() { 183 debug!("{:?}", path); 184 185 if let Some(p) = path.file_name() { 186 if let Some(p) = p.to_str() { 187 if p.ends_with('~') || (p.starts_with('#') && p.ends_with('#')) { 188 continue; 189 } 190 } 191 } 192 193 let path = path.canonicalize()?; 194 let path = if let Ok(path) = path.strip_prefix(&repo_path.as_path()) { 195 path 196 } else { 197 continue; 198 }; 199 use path_slash::PathExt; 200 let path_str = path.to_slash_lossy(); 201 if txn.is_tracked(&path_str)? { 202 txn.remove_file(&path_str)?; 203 } 204 } 205 txn.commit()?; 206 Ok(()) 207 } 208 } 209