1use std::fs;
2use std::os::unix::fs::symlink;
3use std::path::{Path, PathBuf};
4
5use crate::commands::Command;
6use crate::error::{Error, Result};
7use crate::manifest::Manifest;
8
9pub struct AddCommand {
10 file_path: PathBuf,
11}
12
13impl AddCommand {
14 pub fn new(file_path: PathBuf) -> Self {
15 Self { file_path }
16 }
17
18 pub fn add_to_manifest(manifest: &mut Manifest, file_path: &Path) -> Result<PathBuf> {
20 let file_name = file_path
21 .file_name()
22 .ok_or_else(|| Error::NotFound(file_path.to_path_buf()))?;
23 let local_path = Path::new(file_name);
24
25 if manifest.contains(local_path) {
26 return Err(Error::AlreadyTracked(local_path.to_path_buf()));
27 }
28
29 fs::rename(file_path, local_path)?;
31
32 let canonical = local_path.canonicalize()?;
34 symlink(&canonical, file_path)?;
35
36 manifest.insert(local_path.to_path_buf(), file_path)?;
38
39 Ok(local_path.to_path_buf())
40 }
41}
42
43impl Command for AddCommand {
44 fn execute(self) -> Result<()> {
45 let mut manifest = Manifest::load()?;
46 let local_path = Self::add_to_manifest(&mut manifest, &self.file_path)?;
47 manifest.save()?;
48
49 println!("{} -> {}", local_path.display(), self.file_path.display());
50 Ok(())
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use tempfile::TempDir;
58
59 fn add_file(source_file: &Path, dest_dir: &Path) -> Result<PathBuf> {
61 let file_name = source_file
62 .file_name()
63 .ok_or_else(|| Error::NotFound(source_file.to_path_buf()))?;
64 let local_path = dest_dir.join(file_name);
65
66 fs::rename(source_file, &local_path)?;
68
69 let canonical = local_path.canonicalize()?;
71 symlink(&canonical, source_file)?;
72
73 Ok(local_path)
74 }
75
76 #[test]
77 fn rejects_already_tracked_file() {
78 let mut manifest = Manifest::empty();
79 manifest
80 .insert("testfile".into(), Path::new("/some/path"))
81 .unwrap();
82
83 assert!(manifest.contains(Path::new("testfile")));
86 }
87
88 #[test]
89 fn moves_file_and_creates_symlink() {
90 let repo = TempDir::new().unwrap();
91 let source_dir = TempDir::new().unwrap();
92 let source_file = source_dir.path().join("myconfig");
93 fs::write(&source_file, "content").unwrap();
94
95 let local_path = add_file(&source_file, repo.path()).unwrap();
96
97 assert!(local_path.exists());
99 assert_eq!(local_path, repo.path().join("myconfig"));
100 assert!(
102 source_file
103 .symlink_metadata()
104 .unwrap()
105 .file_type()
106 .is_symlink()
107 );
108 }
109
110 #[test]
111 fn returns_error_for_invalid_path() {
112 let repo = TempDir::new().unwrap();
113 let result = add_file(Path::new("/"), repo.path());
114 assert!(matches!(result, Err(Error::NotFound(_))));
115 }
116}