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