1use super::*;
2use crate::pristine::{ArcTxn, GraphTxnT, InodeMetadata, TreeErr, TreeTxnT, TxnErr};
3use canonical_path::{CanonicalPath, CanonicalPathBuf};
4use ignore::WalkBuilder;
5use std::borrow::Cow;
6use std::path::{Path, PathBuf};
7
8#[derive(Clone)]
9pub struct FileSystem {
10 root: PathBuf,
11}
12
13pub fn filter_ignore(root_: &CanonicalPath, path: &CanonicalPath, is_dir: bool) -> bool {
15 debug!("path = {:?} root = {:?}", path, root_);
16 if let Ok(suffix) = path.as_path().strip_prefix(root_.as_path()) {
17 debug!("suffix = {:?}", suffix);
18 let mut root = root_.as_path().to_path_buf();
19 let mut ignore = ignore::gitignore::GitignoreBuilder::new(&root);
20 let mut add_root = |root: &mut PathBuf| {
21 ignore.add_line(None, crate::DOT_DIR).unwrap();
22 root.push(".ignore");
23 ignore.add(&root);
24 root.pop();
25 root.push(".gitignore");
26 ignore.add(&root);
27 root.pop();
28 };
29 add_root(&mut root);
30 for c in suffix.components() {
31 root.push(c);
32 add_root(&mut root);
33 }
34 if let Ok(ig) = ignore.build() {
35 let m = ig.matched(suffix, is_dir);
36 debug!("m = {:?}", m);
37 return !m.is_ignore();
38 }
39 }
40 false
41}
42
43pub fn get_prefix(
46 repo_path: Option<&Path>,
47 prefix: &Path,
48) -> Result<(PathBuf, String), std::io::Error> {
49 let mut p = String::new();
50 let repo = if let Some(repo) = repo_path {
51 Cow::Borrowed(repo)
52 } else {
53 Cow::Owned(std::env::current_dir()?)
54 };
55 debug!("get prefix {:?} {:?}", repo, prefix);
56 let repo_prefix = &repo.join(prefix);
57 let prefix_ = if let Ok(x) = repo_prefix.canonicalize() {
58 x
59 } else {
60 let mut p = PathBuf::new();
61 for c in repo_prefix.components() {
62 use std::path::Component;
63 match c {
64 Component::Prefix(_) => p.push(c.as_os_str()),
65 Component::RootDir => p.push(c.as_os_str()),
66 Component::CurDir => {}
67 Component::ParentDir => {
68 p.pop();
69 }
70 Component::Normal(x) => p.push(x),
71 }
72 }
73 p
74 };
75 debug!("get prefix {:?}", prefix_);
76 if let Ok(prefix) = prefix_.as_path().strip_prefix(repo) {
77 for c in prefix.components() {
78 if !p.is_empty() {
79 p.push('/');
80 }
81 let c: &std::path::Path = c.as_ref();
82 p.push_str(&c.to_string_lossy())
83 }
84 }
85 Ok((prefix_, p))
86}
87
88#[derive(Error)]
89pub enum AddError<T: GraphTxnT + TreeTxnT> {
90 #[error(transparent)]
91 Ignore(#[from] ignore::Error),
92 #[error(transparent)]
93 Io(#[from] std::io::Error),
94 #[error(transparent)]
95 Fs(#[from] crate::fs::FsError<T>),
96}
97
98impl<T: GraphTxnT + TreeTxnT> std::fmt::Debug for AddError<T> {
99 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
100 match self {
101 AddError::Ignore(e) => std::fmt::Debug::fmt(e, fmt),
102 AddError::Io(e) => std::fmt::Debug::fmt(e, fmt),
103 AddError::Fs(e) => std::fmt::Debug::fmt(e, fmt),
104 }
105 }
106}
107
108#[derive(Error)]
109pub enum Error<C: std::error::Error + 'static, T: GraphTxnT + TreeTxnT> {
110 #[error(transparent)]
111 Add(#[from] AddError<T>),
112 #[error(transparent)]
113 Record(#[from] crate::record::RecordError<C, std::io::Error, T>),
114 #[error(transparent)]
115 Txn(#[from] TxnErr<T::GraphError>),
116 #[error(transparent)]
117 Tree(#[from] TreeErr<T::TreeError>),
118}
119
120impl<C: std::error::Error + 'static, T: GraphTxnT + TreeTxnT> std::fmt::Debug for Error<C, T> {
121 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
122 match self {
123 Error::Add(e) => std::fmt::Debug::fmt(e, fmt),
124 Error::Record(e) => std::fmt::Debug::fmt(e, fmt),
125 Error::Txn(e) => std::fmt::Debug::fmt(e, fmt),
126 Error::Tree(e) => std::fmt::Debug::fmt(e, fmt),
127 }
128 }
129}
130
131pub struct Untracked {
132 join: Option<std::thread::JoinHandle<Result<(), std::io::Error>>>,
133 receiver: std::sync::mpsc::Receiver<(PathBuf, bool)>,
134}
135
136impl Iterator for Untracked {
137 type Item = Result<(PathBuf, bool), std::io::Error>;
138 fn next(&mut self) -> Option<Self::Item> {
139 if let Ok(x) = self.receiver.recv() {
140 return Some(Ok(x));
141 } else {
142 if let Some(j) = self.join.take()
143 && let Ok(Err(e)) = j.join()
144 {
145 return Some(Err(e));
146 }
147 }
148 None
149 }
150}
151
152impl FileSystem {
153 pub fn from_root<P: AsRef<Path>>(root: P) -> Self {
154 FileSystem {
155 root: root.as_ref().to_path_buf(),
156 }
157 }
158
159 #[allow(clippy::too_many_arguments)]
160 pub fn record_prefixes<
161 T: crate::MutTxnTExt + crate::TxnTExt + Send + Sync + 'static,
162 C: crate::changestore::ChangeStore + Clone + Send + 'static,
163 P: AsRef<Path>,
164 >(
165 &self,
166 txn: ArcTxn<T>,
167 algo: crate::Algorithm,
168 channel: crate::pristine::ChannelRef<T>,
169 changes: &C,
170 state: &mut crate::RecordBuilder,
171 repo_path: CanonicalPathBuf,
172 prefixes: &[P],
173 force: bool,
174 threads: usize,
175 salt: u64,
176 ) -> Result<(), Error<C::Error, T>>
177 where
178 T::Channel: Send + Sync,
179 {
180 for prefix in prefixes.iter() {
181 self.clone().record_prefix(
182 txn.clone(),
183 algo,
184 channel.clone(),
185 changes,
186 state,
187 repo_path.clone(),
188 prefix.as_ref(),
189 force,
190 threads,
191 salt,
192 )?
193 }
194 if prefixes.is_empty() {
195 self.record_prefix(
196 txn,
197 algo,
198 channel,
199 changes,
200 state,
201 repo_path.clone(),
202 Path::new(""),
203 force,
204 threads,
205 salt,
206 )?
207 }
208 Ok(())
209 }
210
211 pub fn add_prefix_rec<T: crate::MutTxnTExt + crate::TxnTExt>(
212 &self,
213 txn: &ArcTxn<T>,
214 repo_path: CanonicalPathBuf,
215 full: CanonicalPathBuf,
216 force: bool,
217 threads: usize,
218 salt: u64,
219 ) -> Result<(), AddError<T>> {
220 let mut txn = txn.write();
221 for p in
222 self.iterate_prefix_rec(repo_path.clone(), full.clone(), force, threads, |_, _| true)?
223 {
224 let (path, is_dir) = p?;
225 info!("Adding {:?}", path);
226 use path_slash::PathExt;
227 let path_str = path.to_slash_lossy();
228 if path_str.is_empty() || path_str == "." {
229 continue;
230 }
231 match txn.add(&path_str, is_dir, salt) {
232 Ok(_) => {}
233 Err(crate::fs::FsError::AlreadyInRepo(_)) => {}
234 Err(e) => return Err(e.into()),
235 }
236 }
237 Ok(())
238 }
239
240 pub fn iterate_prefix_rec<F: Fn(&Path, bool) -> bool + Send + Sync + 'static>(
241 &self,
242 repo_path: CanonicalPathBuf,
243 full: CanonicalPathBuf,
244 force: bool,
245 threads: usize,
246 follow: F,
247 ) -> Result<Untracked, std::io::Error> {
248 debug!("full = {:?}", full);
249 let meta = std::fs::metadata(&full)?;
250 debug!("meta = {:?}", meta);
251 let (sender, receiver) = std::sync::mpsc::sync_channel(100);
252
253 debug!("{:?}", full.as_path().strip_prefix(repo_path.as_path()));
254 debug!("force = {:?}", force);
255 if !force
256 && !filter_ignore(
257 repo_path.as_canonical_path(),
258 full.as_canonical_path(),
259 meta.is_dir(),
260 )
261 {
262 return Ok(Untracked {
263 join: None,
264 receiver,
265 });
266 }
267 let follow = std::sync::Arc::new(follow);
268 let t = std::thread::spawn(move || -> Result<(), std::io::Error> {
269 let follow = follow.clone();
270 if meta.is_dir() {
271 let mut walk = WalkBuilder::new(&full);
272 walk.ignore(!force)
273 .git_ignore(!force)
274 .hidden(false)
275 .filter_entry(|p| {
276 debug!("p.file_name = {:?}", p.file_name());
277 p.file_name() != crate::DOT_DIR
278 })
279 .threads((threads - 1).max(1));
280 walk.build_parallel().run(move || {
281 let repo_path = repo_path.clone();
282 let sender = sender.clone();
283 let follow = follow.clone();
284 Box::new(move |entry| {
285 let entry: ignore::DirEntry = if let Ok(entry) = entry {
286 entry
287 } else {
288 return ignore::WalkState::Quit;
289 };
290 let p = entry.path();
291 if let Some(p) = p.file_name()
292 && let Some(p) = p.to_str()
293 && (p.ends_with("~") || (p.starts_with("#") && p.ends_with("#")))
294 {
295 return ignore::WalkState::Skip;
296 }
297 debug!("entry path = {:?} {:?}", entry.path(), repo_path);
298 if let Ok(path) = entry.path().strip_prefix(&repo_path) {
299 let is_dir = entry.file_type().unwrap().is_dir();
300 if sender.send((path.to_path_buf(), is_dir)).is_err() {
301 return ignore::WalkState::Quit;
302 }
303 if !follow(path, is_dir) {
304 return ignore::WalkState::Skip;
305 }
306 } else {
307 debug!("entry = {:?}", entry.path());
308 }
309 ignore::WalkState::Continue
310 })
311 })
312 } else {
313 debug!("filter_ignore ok");
314 let path = full.as_path().strip_prefix(repo_path.as_path()).unwrap();
315 sender.send((path.to_path_buf(), false)).unwrap();
316 }
317 Ok(())
318 });
319 Ok(Untracked {
320 join: Some(t),
321 receiver,
322 })
323 }
324
325 #[allow(clippy::too_many_arguments)]
326 pub fn record_prefix<
327 T: crate::MutTxnTExt + crate::TxnTExt + Send + Sync + 'static,
328 C: crate::changestore::ChangeStore + Clone + Send + 'static,
329 >(
330 &self,
331 txn: ArcTxn<T>,
332 algorithm: crate::Algorithm,
333 channel: crate::pristine::ChannelRef<T>,
334 changes: &C,
335 state: &mut crate::RecordBuilder,
336 repo_path: CanonicalPathBuf,
337 prefix: &Path,
338 force: bool,
339 threads: usize,
340 salt: u64,
341 ) -> Result<(), Error<C::Error, T>>
342 where
343 T::Channel: Send + Sync,
344 {
345 let (full, prefix) = get_prefix(Some(repo_path.as_ref()), prefix).map_err(AddError::Io)?;
346 if let Ok(full) = CanonicalPathBuf::canonicalize(&full)
347 && let Ok(path) = full.as_path().strip_prefix(repo_path.as_path())
348 {
349 use path_slash::PathExt;
350 let path_str = path.to_slash_lossy();
351 if !crate::fs::is_tracked(&*txn.read(), &path_str)? {
352 self.add_prefix_rec(&txn, repo_path, full, force, threads, salt)?;
353 }
354 }
355 debug!("recording from prefix {:?}", prefix);
356 state.record(
357 txn.clone(),
358 algorithm,
359 false,
360 &crate::diff::DEFAULT_SEPARATOR,
361 channel,
362 self,
363 changes,
364 &prefix,
365 threads,
366 )?;
367 debug!("recorded");
368 Ok(())
369 }
370
371 fn path(&self, file: &str) -> PathBuf {
372 let mut path = self.root.clone();
373 path.extend(crate::path::components(file));
374 path
375 }
376}
377
378impl WorkingCopyRead for FileSystem {
379 type Error = std::io::Error;
380 fn file_metadata(&self, file: &str) -> Result<InodeMetadata, Self::Error> {
381 debug!("metadata {:?}", file);
382 let attr = std::fs::metadata(self.path(file))?;
383 let permissions = permissions(&attr).unwrap_or(0o700);
384 debug!("permissions = {:?}", permissions);
385 Ok(InodeMetadata::new(permissions & 0o100, attr.is_dir()))
386 }
387 fn read_file(&self, file: &str, buffer: &mut Vec<u8>) -> Result<(), Self::Error> {
388 use std::io::Read;
389 debug!("read_file {:?}", file);
390 let mut f = std::fs::File::open(self.path(file))?;
391 f.read_to_end(buffer)?;
392 Ok(())
393 }
394
395 #[cfg(not(unix))]
396 fn modified_time(&self, file: &str) -> Result<std::time::SystemTime, Self::Error> {
397 debug!("modified_time {:?}", file);
398 let attr = std::fs::metadata(&self.path(file))?;
399 Ok(attr.modified()?)
400 }
401
402 #[cfg(unix)]
403 fn modified_time(&self, file: &str) -> Result<std::time::SystemTime, Self::Error> {
404 debug!("modified_time {:?}", file);
405 use std::os::unix::fs::MetadataExt;
406 let attr = std::fs::metadata(self.path(file))?;
407 let ctime = std::time::SystemTime::UNIX_EPOCH
408 + std::time::Duration::from_millis(
409 attr.ctime() as u64 * 1000 + attr.ctime_nsec() as u64 / 1_000_000,
410 );
411 Ok(attr.modified()?.max(ctime))
412 }
413}
414
415impl WorkingCopy for FileSystem {
416 fn create_dir_all(&self, file: &str) -> Result<(), Self::Error> {
417 debug!("create_dir_all {:?}", file);
418 std::fs::create_dir_all(self.path(file))
419 }
420
421 fn remove_path(&self, path: &str, rec: bool) -> Result<(), Self::Error> {
422 debug!("remove_path {:?}", path);
423 let path = self.path(path);
424 if let Ok(meta) = std::fs::metadata(&path)
425 && let Err(e) = if meta.is_dir() {
426 if rec {
427 std::fs::remove_dir_all(&path)
428 } else {
429 std::fs::remove_dir(&path)
430 }
431 } else {
432 std::fs::remove_file(&path)
433 }
434 {
435 info!("while deleting {:?}: {:?}", path, e);
436 }
437 Ok(())
438 }
439 fn rename(&self, former: &str, new: &str) -> Result<(), Self::Error> {
440 debug!("rename {:?} {:?}", former, new);
441 let former = self.path(former);
442 let new = self.path(new);
443 if let Some(p) = new.parent() {
444 std::fs::create_dir_all(p)?
445 }
446 std::fs::rename(&former, &new)?;
447 Ok(())
448 }
449 #[cfg(not(windows))]
450 fn set_permissions(&self, name: &str, permissions: u16) -> Result<(), Self::Error> {
451 use std::os::unix::fs::PermissionsExt;
452 let name = self.path(name);
453 debug!("set_permissions: {:?}", name);
454 let metadata = std::fs::metadata(&name)?;
455 let mut current = metadata.permissions();
456 debug!(
457 "setting mode for {:?} to {:?} (currently {:?})",
458 name, permissions, current
459 );
460 if permissions & 0o100 != 0 {
461 current.set_mode(current.mode() | 0o100);
462 } else {
463 current.set_mode(current.mode() & ((!0o777) | 0o666));
464 }
465 debug!("setting {:?}", current);
466 std::fs::set_permissions(name, current)?;
467 debug!("set");
468 Ok(())
469 }
470 #[cfg(windows)]
471 fn set_permissions(&self, _name: &str, _permissions: u16) -> Result<(), Self::Error> {
472 Ok(())
473 }
474
475 type Writer = std::io::BufWriter<std::fs::File>;
476 fn write_file(&self, file: &str, _: Inode) -> Result<Self::Writer, Self::Error> {
477 let path = self.path(file);
478 debug!("path = {:?}", path);
479 if let Some(p) = path.parent() {
480 std::fs::create_dir_all(p).unwrap_or(())
481 }
482 debug!("write_file: dir created");
483 std::fs::remove_file(&path).unwrap_or(());
484 let file = std::io::BufWriter::new(std::fs::File::create(&path)?);
485 debug!("file");
486 Ok(file)
487 }
488
489 fn touch(&self, file: &str, time: std::time::SystemTime) -> Result<(), Self::Error> {
490 let p = self.path(file);
491 let dest = std::fs::File::open(&p)?;
493 let times = std::fs::FileTimes::new().set_modified(time);
494 dest.set_times(times)?;
495 Ok(())
496 }
497}
498
499#[cfg(not(windows))]
500fn permissions(attr: &std::fs::Metadata) -> Option<usize> {
501 use std::os::unix::fs::PermissionsExt;
502 Some(attr.permissions().mode() as usize)
503}
504#[cfg(windows)]
505fn permissions(_: &std::fs::Metadata) -> Option<usize> {
506 None
507}