aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs856
1 files changed, 46 insertions, 810 deletions
diff --git a/src/main.rs b/src/main.rs
index 9b17787..b4f951d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@
3// TODO: rewrite all errors so they start with lower case 3// TODO: rewrite all errors so they start with lower case
4 4
5mod depot { 5mod depot {
6 use anyhow::Context;
6 use std::{ 7 use std::{
7 collections::HashSet, 8 collections::HashSet,
8 ffi::{OsStr, OsString}, 9 ffi::{OsStr, OsString},
@@ -26,14 +27,6 @@ mod depot {
26 InvalidPath, 27 InvalidPath,
27 #[error("path must be relative and not empty")] 28 #[error("path must be relative and not empty")]
28 InvalidLinkPath, 29 InvalidLinkPath,
29 #[error("{0}")]
30 LinkCreateError(#[from] LinkCreateError),
31 }
32
33 #[derive(Debug, Error)]
34 enum LinkCreateError {
35 #[error("an ancestor of this path is already linked")]
36 AncestorAlreadyLinked,
37 } 30 }
38 31
39 #[derive(Debug, Clone)] 32 #[derive(Debug, Clone)]
@@ -470,6 +463,17 @@ mod depot {
470 Ok(self.has_links_under_unchecked(path)) 463 Ok(self.has_links_under_unchecked(path))
471 } 464 }
472 465
466 pub fn links_verify_install(&self, link_ids: impl Iterator<Item = LinkID>) -> Result<()> {
467 let mut destination = DepotTree::default();
468 for link_id in link_ids {
469 let link = &self.links[link_id];
470 destination
471 .link_create(&link.destination, link_id)
472 .context("link destinations overlap")?;
473 }
474 Ok(())
475 }
476
473 pub fn link_view(&self, link_id: LinkID) -> LinkView { 477 pub fn link_view(&self, link_id: LinkID) -> LinkView {
474 LinkView { 478 LinkView {
475 link_id, 479 link_id,
@@ -778,6 +782,22 @@ mod depot {
778 } 782 }
779 783
780 #[test] 784 #[test]
785 fn test_depot_links_verify_install() {
786 let mut depot = Depot::default();
787 let f1 = depot.link_create("nvim", ".config/nvim").unwrap();
788 let f2 = depot.link_create("alacritty", ".config/alacritty").unwrap();
789 let f3 = depot.link_create("bash/.bashrc", ".bashrc").unwrap();
790 let f4 = depot.link_create("bash_laptop/.bashrc", ".bashrc").unwrap();
791
792 depot
793 .links_verify_install(vec![f1, f2, f3].into_iter())
794 .unwrap();
795 depot
796 .links_verify_install(vec![f1, f2, f3, f4].into_iter())
797 .unwrap_err();
798 }
799
800 #[test]
781 fn test_depot_read_dir() { 801 fn test_depot_read_dir() {
782 let mut depot = Depot::default(); 802 let mut depot = Depot::default();
783 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); 803 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
@@ -842,792 +862,6 @@ mod depot {
842 } 862 }
843} 863}
844 864
845pub mod depot2 {
846 use std::{
847 collections::HashSet,
848 ffi::{OsStr, OsString},
849 ops::Deref,
850 path::{Path, PathBuf},
851 };
852
853 use slotmap::{Key, SlotMap};
854
855 pub use disk::{read, write};
856
857 slotmap::new_key_type! {pub struct LinkID;}
858 slotmap::new_key_type! {struct NodeID;}
859
860 #[derive(Debug, Clone, PartialEq, Eq)]
861 pub enum SearchResult {
862 Found(LinkID),
863 Ancestor(LinkID),
864 NotFound,
865 }
866
867 #[derive(Debug, Clone, PartialEq, Eq)]
868 pub enum DirNode {
869 Link(LinkID),
870 Directory(PathBuf),
871 }
872
873 #[derive(Debug)]
874 pub struct LinkView<'a> {
875 link_id: LinkID,
876 depot: &'a Depot,
877 }
878
879 impl<'a> LinkView<'a> {
880 pub fn origin(&self) -> &Path {
881 &self.depot.links[self.link_id].origin
882 }
883
884 pub fn destination(&self) -> &Path {
885 &self.depot.links[self.link_id].destination
886 }
887 }
888
889 // wrapper for a path under the depot
890 // this path is relative and does not contain `..` or similar
891 // Deref(Path)
892 struct DepotPath(PathBuf);
893 impl Deref for DepotPath {
894 type Target = Path;
895
896 fn deref(&self) -> &Self::Target {
897 self.0.deref()
898 }
899 }
900
901 #[derive(Debug, Clone)]
902 struct Node {
903 comp: OsString,
904 parent: NodeID,
905 kind: NodeKind,
906 }
907
908 #[derive(Debug, Clone)]
909 enum NodeKind {
910 Link(LinkID),
911 Directory(HashSet<NodeID>),
912 }
913
914 #[derive(Debug, Clone)]
915 struct Link {
916 origin: PathBuf,
917 destination: PathBuf,
918 node_id: NodeID,
919 }
920
921 #[derive(Debug, Clone)]
922 pub struct Depot {
923 links: SlotMap<LinkID, Link>,
924 nodes: SlotMap<NodeID, Node>,
925 root: NodeID,
926 }
927
928 impl Default for Depot {
929 fn default() -> Self {
930 let mut nodes = SlotMap::default();
931 let root = nodes.insert(Node {
932 comp: Default::default(),
933 parent: Default::default(),
934 kind: NodeKind::Directory(Default::default()),
935 });
936 Self {
937 links: Default::default(),
938 nodes,
939 root,
940 }
941 }
942 }
943
944 impl Depot {
945 pub fn link_create(
946 &mut self,
947 origin: impl AsRef<Path>,
948 destination: impl AsRef<Path>,
949 ) -> anyhow::Result<LinkID> {
950 let origin = origin.as_ref();
951 let destination = destination.as_ref();
952 verify_link_path(origin)?;
953 verify_link_path(destination)?;
954
955 // example
956 // origin = fish/config.fish
957 // destination = .config/fish/config.fish
958
959 // search
960 // if ancestor - return error
961 // if found - update destination
962 // if not found - create
963
964 match self.search_unchecked(&origin) {
965 SearchResult::Found(link_id) => {
966 let link = &mut self.links[link_id];
967 link.destination = destination.to_owned();
968 Ok(link_id)
969 }
970 SearchResult::Ancestor(_) => Err(anyhow::anyhow!(
971 "An ancestor of this path is already linked"
972 )),
973 SearchResult::NotFound => {
974 let link_id = self.links.insert(Link {
975 origin: origin.to_owned(),
976 destination: destination.to_owned(),
977 node_id: Default::default(),
978 });
979 let node_id = self.node_create_link(origin, link_id);
980 self.links[link_id].node_id = node_id;
981 Ok(link_id)
982 }
983 }
984 }
985
986 /// moves the link specified by `link_id` to the path at `destination`.
987 /// if the link is already at the destination nothing is done.
988 /// if the destination is another link that that link is removed.
989 /// if the destination is under another link then an error is returned.
990 pub fn link_move(
991 &mut self,
992 link_id: LinkID,
993 destination: impl AsRef<Path>,
994 ) -> anyhow::Result<()> {
995 let destination = destination.as_ref();
996 verify_link_path(destination)?;
997
998 let link_node_id = self.links[link_id].node_id;
999 let link_parent_node_id = self.nodes[link_node_id].parent;
1000 let (node_id, found) = self.node_search(destination);
1001
1002 // the link is already at the destination
1003 if found && node_id == link_node_id {
1004 return Ok(());
1005 }
1006
1007 if found {
1008 let node = &self.nodes[node_id];
1009 match &node.kind {
1010 NodeKind::Link(node_link_id) => {
1011 let node_parent_id = node.parent;
1012 let node_link_id = *node_link_id;
1013 assert_ne!(link_id, node_link_id);
1014 self.node_child_remove(link_parent_node_id, link_node_id);
1015 self.node_child_add(node_parent_id, link_node_id);
1016 self.node_set_parent(link_node_id, node_parent_id);
1017 self.link_remove(node_link_id);
1018 let new_origin = self.node_build_path(link_node_id);
1019 self.links[link_id].origin = new_origin;
1020 Ok(())
1021 }
1022 NodeKind::Directory(..) => Err(anyhow::anyhow!(
1023 "Cannot move link, other links exist under the destination"
1024 )),
1025 }
1026 } else {
1027 let node = &self.nodes[node_id];
1028 match &node.kind {
1029 NodeKind::Link(..) => Err(anyhow::anyhow!(
1030 "Cannot move link, an ancestor is already linked"
1031 )),
1032 NodeKind::Directory(_) => {
1033 let new_node_id = self.node_create_link(destination, link_id);
1034 let new_origin = self.node_build_path(new_node_id);
1035 self.node_remove(link_node_id);
1036 self.links[link_id].node_id = new_node_id;
1037 self.links[link_id].origin = new_origin;
1038 Ok(())
1039 }
1040 }
1041 }
1042 }
1043
1044 pub fn link_remove(&mut self, link_id: LinkID) {
1045 let node_id = self.links[link_id].node_id;
1046 self.node_remove(node_id);
1047 self.links.remove(link_id);
1048 }
1049
1050 pub fn link_view(&self, link_id: LinkID) -> LinkView {
1051 LinkView {
1052 link_id,
1053 depot: self,
1054 }
1055 }
1056
1057 /// searchs for the link at `origin`.
1058 /// returns SearchResult::Found(..) if there is a link at `origin`.
1059 /// returns SearchResult::Ancestor(..) if an ancestor of `origin` is linked.
1060 /// returns SearchResult::NotFound otherwise.
1061 pub fn link_search(&self, origin: impl AsRef<Path>) -> anyhow::Result<SearchResult> {
1062 let origin = origin.as_ref();
1063 verify_path(origin)?;
1064 if origin.components().next().is_none() {
1065 return Ok(SearchResult::NotFound);
1066 }
1067 Ok(self.search_unchecked(&origin))
1068 }
1069
1070 /// finds the link at origin.
1071 pub fn link_find(&self, origin: impl AsRef<Path>) -> anyhow::Result<Option<LinkID>> {
1072 match self.link_search(origin)? {
1073 SearchResult::Found(link_id) => Ok(Some(link_id)),
1074 SearchResult::Ancestor(_) | SearchResult::NotFound => Ok(None),
1075 }
1076 }
1077
1078 /// returns an iterator for all the links at or under the given path.
1079 pub fn links_under(
1080 &self,
1081 path: impl AsRef<Path>,
1082 ) -> anyhow::Result<impl Iterator<Item = LinkID> + '_> {
1083 let path = path.as_ref();
1084 verify_path(path)?;
1085
1086 let mut link_ids = Vec::new();
1087 if let Some(node_id) = self.node_find(path) {
1088 let mut node_ids = vec![node_id];
1089 while let Some(node_id) = node_ids.pop() {
1090 let node = &self.nodes[node_id];
1091 match &node.kind {
1092 NodeKind::Link(link_id) => link_ids.push(*link_id),
1093 NodeKind::Directory(children) => node_ids.extend(children.iter().copied()),
1094 }
1095 }
1096 }
1097 Ok(link_ids.into_iter())
1098 }
1099
1100 pub fn has_links_under(&self, path: impl AsRef<Path>) -> anyhow::Result<bool> {
1101 let path = path.as_ref();
1102 verify_path(path)?;
1103
1104 match self.node_find(path) {
1105 Some(node_id) => match &self.nodes[node_id].kind {
1106 NodeKind::Link(_) => Ok(true),
1107 NodeKind::Directory(children) => Ok(!children.is_empty()),
1108 },
1109 None => Ok(false),
1110 }
1111 }
1112
1113 /// returns true if the `path` is a link or contains an ancestor that is linked.
1114 /// returns false otherwise.
1115 pub fn link_exists(&self, path: impl AsRef<Path>) -> bool {
1116 match self.link_search(path) {
1117 Ok(SearchResult::Found(..)) | Ok(SearchResult::Ancestor(..)) => true,
1118 _ => false,
1119 }
1120 }
1121
1122 pub fn read_dir(
1123 &self,
1124 path: impl AsRef<Path>,
1125 ) -> anyhow::Result<impl Iterator<Item = DirNode> + '_> {
1126 let path = path.as_ref();
1127 verify_path(path)?;
1128
1129 let node_id = match self.node_find(path) {
1130 Some(node_id) => node_id,
1131 None => return Err(anyhow::anyhow!("Directory does not exist")),
1132 };
1133 let node = &self.nodes[node_id];
1134 let children = match &node.kind {
1135 NodeKind::Link(_) => return Err(anyhow::anyhow!("Path is not a directory")),
1136 NodeKind::Directory(children) => children,
1137 };
1138 Ok(children.iter().map(|id| {
1139 let node = &self.nodes[*id];
1140 match &node.kind {
1141 NodeKind::Link(link_id) => DirNode::Link(*link_id),
1142 NodeKind::Directory(_) => DirNode::Directory(self.node_build_path(*id)),
1143 }
1144 }))
1145 }
1146
1147 fn search_unchecked(&self, origin: &Path) -> SearchResult {
1148 debug_assert!(verify_link_path(origin).is_ok());
1149
1150 let mut origin_comps = iter_path_comps(&origin);
1151 let mut curr_node = self.root;
1152 'outer: loop {
1153 let node = &self.nodes[curr_node];
1154 let curr_comp = origin_comps.next();
1155 match &node.kind {
1156 NodeKind::Link(link_id) => match curr_comp {
1157 Some(_) => break SearchResult::Ancestor(*link_id),
1158 None => break SearchResult::Found(*link_id),
1159 },
1160 NodeKind::Directory(children) => match curr_comp {
1161 Some(curr_comp) => {
1162 for &child_id in children.iter() {
1163 let child = &self.nodes[child_id];
1164 if &child.comp == curr_comp {
1165 curr_node = child_id;
1166 continue 'outer;
1167 }
1168 }
1169 break SearchResult::NotFound;
1170 }
1171 None => break SearchResult::NotFound,
1172 },
1173 }
1174 }
1175 }
1176
1177 /// creates a new directory node with no children.
1178 /// the node specified by `parent` must be a directory node.
1179 fn node_create_dir_empty(&mut self, parent: NodeID, comp: OsString) -> NodeID {
1180 let node_id = self.nodes.insert(Node {
1181 comp,
1182 parent,
1183 kind: NodeKind::Directory(Default::default()),
1184 });
1185 self.node_child_add(parent, node_id);
1186 node_id
1187 }
1188
1189 /// all the nodes up to the node to be created have to be directory nodes.
1190 /// `path` must be a verified link path.
1191 fn node_create_link(&mut self, path: &Path, link_id: LinkID) -> NodeID {
1192 assert!(verify_link_path(path).is_ok());
1193 let mut curr_node_id = self.root;
1194 let mut path_comps = iter_path_comps(path).peekable();
1195 // unwrap: a verified link path has atleast 1 component
1196 let mut curr_path_comp = path_comps.next().unwrap();
1197
1198 while path_comps.peek().is_some() {
1199 let next_node = match self.node_children_search(curr_node_id, curr_path_comp) {
1200 Some(child_id) => child_id,
1201 None => self.node_create_dir_empty(curr_node_id, curr_path_comp.to_owned()),
1202 };
1203 curr_node_id = next_node;
1204 // unwrap: we known next is Some beacause of this loop's condition
1205 curr_path_comp = path_comps.next().unwrap();
1206 }
1207
1208 let new_node = self.nodes.insert(Node {
1209 comp: curr_path_comp.to_owned(),
1210 parent: curr_node_id,
1211 kind: NodeKind::Link(link_id),
1212 });
1213 self.node_child_add(curr_node_id, new_node);
1214 new_node
1215 }
1216
1217 /// finds the node at the given path.
1218 /// `path` must be a verified path.
1219 fn node_find(&self, path: &Path) -> Option<NodeID> {
1220 match self.node_search(path) {
1221 (node_id, true) => Some(node_id),
1222 _ => None,
1223 }
1224 }
1225
1226 /// searches for the node at `path`. if that node does not exists then it returns the
1227 /// closest node. return (closest_node, found)
1228 fn node_search(&self, path: &Path) -> (NodeID, bool) {
1229 debug_assert!(verify_path(path).is_ok());
1230
1231 let mut origin_comps = iter_path_comps(&path).peekable();
1232 let mut curr_node = self.root;
1233
1234 if origin_comps.peek().is_none() {
1235 return (self.root, true);
1236 }
1237
1238 'outer: loop {
1239 let node = &self.nodes[curr_node];
1240 match origin_comps.next() {
1241 Some(curr_comp) => match &node.kind {
1242 NodeKind::Link(..) => break (curr_node, false),
1243 NodeKind::Directory(children) => {
1244 for &child_id in children.iter() {
1245 let child = &self.nodes[child_id];
1246 if &child.comp == curr_comp {
1247 curr_node = child_id;
1248 continue 'outer;
1249 }
1250 }
1251 break (curr_node, false);
1252 }
1253 },
1254 None => break (curr_node, true),
1255 }
1256 }
1257 }
1258
1259 /// adds `new_child` to `node_id`'s children.
1260 /// the node specified by `node_id` must be a directory node.
1261 fn node_child_add(&mut self, node_id: NodeID, new_child: NodeID) {
1262 let node = &mut self.nodes[node_id];
1263 match node.kind {
1264 NodeKind::Directory(ref mut children) => {
1265 children.insert(new_child);
1266 }
1267 _ => unreachable!(),
1268 }
1269 }
1270
1271 /// searchs for a child with the given comp and returns its id.
1272 /// the node specified by `node_id` must be a directory node.
1273 fn node_children_search(&self, node_id: NodeID, search_comp: &OsStr) -> Option<NodeID> {
1274 let child_ids = match &self.nodes[node_id].kind {
1275 NodeKind::Directory(c) => c,
1276 _ => unreachable!(),
1277 };
1278 for &child_id in child_ids {
1279 let child = &self.nodes[child_id];
1280 if child.comp == search_comp {
1281 return Some(child_id);
1282 }
1283 }
1284 None
1285 }
1286
1287 /// removes `child` from `node_id`'s children.
1288 /// the node specified by `node_id` must be a directory node and it must contain the node
1289 /// `child`.
1290 fn node_child_remove(&mut self, node_id: NodeID, child: NodeID) {
1291 let node = &mut self.nodes[node_id];
1292 let remove_node = match &mut node.kind {
1293 NodeKind::Directory(children) => {
1294 let contained = children.remove(&child);
1295 assert!(contained);
1296 children.is_empty()
1297 }
1298 _ => unreachable!(),
1299 };
1300 if remove_node && node_id != self.root {
1301 self.node_remove(node_id);
1302 }
1303 }
1304
1305 /// build the path that references this node.
1306 fn node_build_path(&self, node_id: NodeID) -> PathBuf {
1307 fn recursive_helper(nodes: &SlotMap<NodeID, Node>, nid: NodeID, pbuf: &mut PathBuf) {
1308 if nid.is_null() {
1309 return;
1310 }
1311 let parent_id = nodes[nid].parent;
1312 recursive_helper(nodes, parent_id, pbuf);
1313 pbuf.push(&nodes[nid].comp);
1314 }
1315
1316 let mut node_path = PathBuf::default();
1317 recursive_helper(&self.nodes, node_id, &mut node_path);
1318 node_path
1319 }
1320
1321 fn node_set_parent(&mut self, node_id: NodeID, parent: NodeID) {
1322 self.nodes[node_id].parent = parent;
1323 }
1324
1325 fn node_remove(&mut self, node_id: NodeID) {
1326 debug_assert!(node_id != self.root);
1327 debug_assert!(self.nodes.contains_key(node_id));
1328
1329 let node = self.nodes.remove(node_id).unwrap();
1330 match node.kind {
1331 NodeKind::Link(..) => {}
1332 NodeKind::Directory(children) => {
1333 // Right now directory nodes are only removed from inside this function and
1334 // we do not remove directories with children
1335 assert!(children.is_empty());
1336 }
1337 }
1338 let parent_id = node.parent;
1339 self.node_child_remove(parent_id, node_id);
1340 }
1341 }
1342
1343 mod disk {
1344 use std::path::{Path, PathBuf};
1345
1346 use anyhow::Context;
1347 use serde::{Deserialize, Serialize};
1348
1349 use super::Depot;
1350
1351 #[derive(Debug, Serialize, Deserialize)]
1352 struct DiskLink {
1353 origin: PathBuf,
1354 destination: PathBuf,
1355 }
1356
1357 #[derive(Debug, Serialize, Deserialize)]
1358 struct DiskLinks {
1359 links: Vec<DiskLink>,
1360 }
1361
1362 pub fn read(path: &Path) -> anyhow::Result<Depot> {
1363 let contents = std::fs::read_to_string(path).context("Failed to read depot file")?;
1364 let disk_links = toml::from_str::<DiskLinks>(&contents)
1365 .context("Failed to parse depot file")?
1366 .links;
1367 let mut depot = Depot::default();
1368 for disk_link in disk_links {
1369 depot
1370 .link_create(disk_link.origin, disk_link.destination)
1371 .context("Failed to build depot from file. File is in an invalid state")?;
1372 }
1373 Ok(depot)
1374 }
1375
1376 pub fn write(path: &Path, depot: &Depot) -> anyhow::Result<()> {
1377 let mut links = Vec::with_capacity(depot.links.len());
1378 for (_, link) in depot.links.iter() {
1379 links.push(DiskLink {
1380 origin: link.origin.clone(),
1381 destination: link.destination.clone(),
1382 });
1383 }
1384 let contents = toml::to_string_pretty(&DiskLinks { links })
1385 .context("Failed to serialize depot")?;
1386 std::fs::write(path, contents).context("Failed to write depot to file")?;
1387 Ok(())
1388 }
1389 }
1390
1391 /// a verified path is a path that:
1392 /// + is not empty
1393 /// + is relative
1394 /// + does not contain Prefix/RootDir/ParentDir
1395 fn verify_link_path(path: &Path) -> anyhow::Result<()> {
1396 // make sure the path is not empty
1397 if path.components().next().is_none() {
1398 return Err(anyhow::anyhow!("Path cannot be empty"));
1399 }
1400 verify_path(path)
1401 }
1402 /// a verified path is a path that:
1403 /// + is not empty
1404 /// + is relative
1405 /// + does not contain Prefix/RootDir/ParentDir
1406 fn verify_path(path: &Path) -> anyhow::Result<()> {
1407 // make sure the path is relative
1408 // make sure the path does not contain '.' or '..'
1409 for component in path.components() {
1410 match component {
1411 std::path::Component::Prefix(_) => {
1412 return Err(anyhow::anyhow!("Path cannot have prefix"))
1413 }
1414 std::path::Component::RootDir => {
1415 return Err(anyhow::anyhow!("Path must be relative"))
1416 }
1417 std::path::Component::CurDir | std::path::Component::ParentDir => {
1418 return Err(anyhow::anyhow!("Path cannot contain '.' or '..'"))
1419 }
1420 std::path::Component::Normal(_) => {}
1421 }
1422 }
1423 Ok(())
1424 }
1425
1426 /// Iterate over the components of a path.
1427 /// # Pre
1428 /// The path can only have "Normal" components.
1429 fn iter_path_comps(path: &Path) -> impl Iterator<Item = &OsStr> {
1430 debug_assert!(verify_path(path).is_ok());
1431 path.components().map(|component| match component {
1432 std::path::Component::Normal(comp) => comp,
1433 _ => unreachable!(),
1434 })
1435 }
1436
1437 #[cfg(test)]
1438 mod tests {
1439 use super::*;
1440
1441 #[test]
1442 fn test_verify_path() {
1443 verify_path(Path::new("")).unwrap();
1444 verify_path(Path::new("f1")).unwrap();
1445 verify_path(Path::new("d1/f1")).unwrap();
1446 verify_path(Path::new("d1/f1.txt")).unwrap();
1447 verify_path(Path::new("d1/./f1.txt")).unwrap();
1448
1449 verify_path(Path::new("/")).unwrap_err();
1450 verify_path(Path::new("./f1")).unwrap_err();
1451 verify_path(Path::new("/d1/f1")).unwrap_err();
1452 verify_path(Path::new("d1/../f1.txt")).unwrap_err();
1453 verify_path(Path::new("/d1/../f1.txt")).unwrap_err();
1454 }
1455
1456 #[test]
1457 fn test_verify_link_path() {
1458 verify_link_path(Path::new("f1")).unwrap();
1459 verify_link_path(Path::new("d1/f1")).unwrap();
1460 verify_link_path(Path::new("d1/f1.txt")).unwrap();
1461 verify_link_path(Path::new("d1/./f1.txt")).unwrap();
1462
1463 verify_link_path(Path::new("")).unwrap_err();
1464 verify_link_path(Path::new("/")).unwrap_err();
1465 verify_link_path(Path::new("./f1")).unwrap_err();
1466 verify_link_path(Path::new("/d1/f1")).unwrap_err();
1467 verify_link_path(Path::new("d1/../f1.txt")).unwrap_err();
1468 verify_link_path(Path::new("/d1/../f1.txt")).unwrap_err();
1469 }
1470
1471 #[test]
1472 fn test_depot_link_create() {
1473 let mut depot = Depot::default();
1474 depot.link_create("", "dest1.txt").unwrap_err();
1475 depot.link_create("comp1.txt", "").unwrap_err();
1476 depot.link_create("", "").unwrap_err();
1477
1478 depot.link_create("comp1.txt", "dest1.txt").unwrap();
1479 depot.link_create("comp1.txt", "dest1_updated.txt").unwrap();
1480 depot
1481 .link_create("./comp1.txt", "dest1_updated.txt")
1482 .unwrap_err();
1483 depot.link_create("/comp1.txt", "dest1.txt").unwrap_err();
1484 depot.link_create("dir1/", "destdir1/").unwrap();
1485 depot
1486 .link_create("dir1/file1.txt", "destfile1.txt")
1487 .unwrap_err();
1488 }
1489
1490 #[test]
1491 fn test_depot_link_move() {
1492 let mut depot = Depot::default();
1493 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
1494 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
1495
1496 depot.link_move(f1, "").unwrap_err();
1497 depot.link_move(f1, "d1/f2/f1").unwrap_err();
1498
1499 depot.link_move(f1, "d1/f2").unwrap();
1500 depot.link_move(f1, "f1").unwrap();
1501 assert_eq!(depot.link_view(f1).origin(), Path::new("f1"));
1502 depot.link_move(f1, "f2").unwrap();
1503 assert_eq!(depot.link_view(f1).origin(), Path::new("f2"));
1504 }
1505
1506 #[test]
1507 fn test_depot_links_under() {
1508 let mut depot = Depot::default();
1509 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
1510 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
1511 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
1512 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
1513 let d3 = depot.link_create("d3", "d3").unwrap();
1514
1515 let under_f1 = depot.links_under("d1/f1").unwrap().collect::<Vec<_>>();
1516 assert_eq!(under_f1, vec![f1]);
1517
1518 let under_d1 = depot.links_under("d1").unwrap().collect::<Vec<_>>();
1519 let expected_under_d1 = vec![f1, f2, f3, f4];
1520 assert!(
1521 under_d1.len() == expected_under_d1.len()
1522 && expected_under_d1.iter().all(|x| under_d1.contains(x))
1523 );
1524
1525 let under_d2 = depot.links_under("d2").unwrap().collect::<Vec<_>>();
1526 assert_eq!(under_d2, vec![]);
1527
1528 let under_d3 = depot.links_under("d3").unwrap().collect::<Vec<_>>();
1529 assert_eq!(under_d3, vec![d3]);
1530
1531 let under_root = depot.links_under("").unwrap().collect::<Vec<_>>();
1532 let expected_under_root = vec![f1, f2, f3, f4, d3];
1533 assert!(
1534 under_root.len() == expected_under_root.len()
1535 && expected_under_root.iter().all(|x| under_root.contains(x))
1536 );
1537 }
1538
1539 #[test]
1540 fn test_depot_has_links_under() {
1541 let mut depot = Depot::default();
1542 let _f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
1543 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
1544 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
1545 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
1546 let _d3 = depot.link_create("d3", "d3").unwrap();
1547
1548 assert!(depot.has_links_under("").unwrap());
1549 assert!(depot.has_links_under("d1").unwrap());
1550 assert!(depot.has_links_under("d3").unwrap());
1551 assert!(depot.has_links_under("d1/f1").unwrap());
1552 assert!(depot.has_links_under("d1/d2").unwrap());
1553 assert!(depot.has_links_under("d1/d2/f4").unwrap());
1554
1555 assert!(!depot.has_links_under("d2").unwrap());
1556 assert!(!depot.has_links_under("d4").unwrap());
1557 assert!(!depot.has_links_under("d1/d2/f4/f5").unwrap());
1558 }
1559
1560 #[test]
1561 fn test_depot_link_remove() {
1562 let mut depot = Depot::default();
1563 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
1564 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::Found(f1));
1565 depot.link_remove(f1);
1566 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::NotFound);
1567 }
1568
1569 #[test]
1570 fn test_depot_link_search() {
1571 let mut depot = Depot::default();
1572 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
1573 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
1574 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
1575 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
1576 let d3 = depot.link_create("d3", "d3").unwrap();
1577
1578 assert_eq!(depot.link_search("d1").unwrap(), SearchResult::NotFound,);
1579 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::Found(f1),);
1580 assert_eq!(depot.link_search("d1/f2").unwrap(), SearchResult::Found(f2),);
1581 assert_eq!(depot.link_search("d1/f3").unwrap(), SearchResult::Found(f3),);
1582 assert_eq!(
1583 depot.link_search("d1/d2/f4").unwrap(),
1584 SearchResult::Found(f4),
1585 );
1586 assert_eq!(
1587 depot.link_search("d1/d2/f5").unwrap(),
1588 SearchResult::NotFound,
1589 );
1590 assert_eq!(
1591 depot.link_search("d3/f6").unwrap(),
1592 SearchResult::Ancestor(d3),
1593 );
1594 }
1595
1596 #[test]
1597 fn test_depot_read_dir() {
1598 let mut depot = Depot::default();
1599 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
1600 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
1601 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
1602 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
1603 let _d3 = depot.link_create("d3", "d3").unwrap();
1604
1605 let read_dir = depot.read_dir("d1").unwrap().collect::<Vec<_>>();
1606 let expected_read_dir = vec![
1607 DirNode::Link(f1),
1608 DirNode::Link(f2),
1609 DirNode::Link(f3),
1610 DirNode::Directory(PathBuf::from("d1/d2")),
1611 ];
1612 assert!(
1613 read_dir.len() == expected_read_dir.len()
1614 && expected_read_dir.iter().all(|x| read_dir.contains(x))
1615 );
1616 }
1617
1618 #[test]
1619 fn test_iter_path_comps() {
1620 let path = Path::new("comp1/comp2/./comp3/file.txt");
1621 let mut iter = iter_path_comps(path);
1622 assert_eq!(iter.next(), Some(OsStr::new("comp1")));
1623 assert_eq!(iter.next(), Some(OsStr::new("comp2")));
1624 assert_eq!(iter.next(), Some(OsStr::new("comp3")));
1625 assert_eq!(iter.next(), Some(OsStr::new("file.txt")));
1626 assert_eq!(iter.next(), None);
1627 }
1628 }
1629}
1630
1631pub mod dotup { 865pub mod dotup {
1632 use std::{ 866 use std::{
1633 cmp::Ordering, 867 cmp::Ordering,
@@ -1793,22 +1027,20 @@ pub mod dotup {
1793 } 1027 }
1794 1028
1795 pub fn install(&self, paths: impl Iterator<Item = impl AsRef<Path>>) { 1029 pub fn install(&self, paths: impl Iterator<Item = impl AsRef<Path>>) {
1796 let mut already_linked: HashSet<LinkID> = Default::default(); 1030 let install_result: anyhow::Result<()> = try {
1797 for origin in paths { 1031 let mut link_ids = HashSet::<LinkID>::default();
1798 let install_result: anyhow::Result<()> = try { 1032 for path in paths {
1799 let origin = self.prepare_relative_path(origin.as_ref())?; 1033 let path = self.prepare_relative_path(path.as_ref())?;
1800 let canonical_pairs = self.canonical_pairs_under(&origin)?; 1034 link_ids.extend(self.depot.links_under(&path)?);
1801 for pair in canonical_pairs { 1035 }
1802 if already_linked.contains(&pair.link_id) { 1036 self.depot.links_verify_install(link_ids.iter().copied())?;
1803 continue; 1037
1804 } 1038 for link_id in link_ids {
1805 self.symlink_install(&pair.origin, &pair.destination)?; 1039 self.symlink_install_by_link_id(link_id)?;
1806 already_linked.insert(pair.link_id);
1807 }
1808 };
1809 if let Err(e) = install_result {
1810 println!("error while installing {} : {e}", origin.as_ref().display());
1811 } 1040 }
1041 };
1042 if let Err(e) = install_result {
1043 println!("error while installing : {e}");
1812 } 1044 }
1813 } 1045 }
1814 1046
@@ -2447,7 +1679,11 @@ fn command_link(global_flags: Flags, args: LinkArgs) -> anyhow::Result<()> {
2447 let origins = if args.directory { 1679 let origins = if args.directory {
2448 vec![args.origin] 1680 vec![args.origin]
2449 } else { 1681 } else {
2450 utils::collect_files_in_dir_recursive(args.origin)? 1682 if args.origin.is_dir() {
1683 utils::collect_files_in_dir_recursive(args.origin)?
1684 } else {
1685 vec![args.origin]
1686 }
2451 }; 1687 };
2452 for origin in origins { 1688 for origin in origins {
2453 dotup.link(origin, &args.destination); 1689 dotup.link(origin, &args.destination);