artifactize_core/
workspace.rs1use std::{
4 io,
5 path::{Path, PathBuf},
6};
7
8pub(crate) const ARTIFACT_MARKERS: [&str; 3] = [
10 crate::config::CONFIG_FILE,
11 "artifactize.json",
12 ".artifactizeignore",
13];
14
15fn has_marker(path: &Path) -> io::Result<bool> {
16 match crate::platform::marker_metadata(path) {
17 Ok(_) => Ok(true),
18 Err(error)
19 if matches!(
20 error.kind(),
21 io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
22 ) =>
23 {
24 Ok(false)
25 }
26 Err(error) => Err(error),
27 }
28}
29
30pub(crate) fn has_artifact_marker(path: &Path) -> io::Result<bool> {
31 for marker in ARTIFACT_MARKERS {
32 if has_marker(&path.join(marker))? {
33 return Ok(true);
34 }
35 }
36 let entries = match std::fs::read_dir(path) {
37 Ok(entries) => entries,
38 Err(error)
39 if matches!(
40 error.kind(),
41 io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
42 ) =>
43 {
44 return Ok(false);
45 }
46 Err(error) if error.kind() == io::ErrorKind::PermissionDenied => {
51 return has_marker(&path.join(".git"));
52 }
53 Err(error) => return Err(error),
54 };
55 for entry in entries {
56 if entry?.file_name().as_encoded_bytes().ends_with(b".artf") {
57 return Ok(true);
58 }
59 }
60 Ok(false)
61}
62
63pub fn canonical_target(path: &Path) -> io::Result<PathBuf> {
64 crate::platform::paths::canonical_target(path)
65}
66
67pub(crate) fn outside_workspace(workspace: &Path, output: &Path) -> io::Result<()> {
68 outside_workspaces(std::iter::once(workspace), output)
69}
70
71pub(crate) fn outside_workspaces<'a>(
74 workspaces: impl IntoIterator<Item = &'a Path>,
75 output: &Path,
76) -> io::Result<()> {
77 let mut workspaces = workspaces.into_iter().peekable();
78 if workspaces.peek().is_none() {
79 return Ok(());
80 }
81 if workspaces.any(|workspace| crate::platform::is_within(output, workspace)) {
82 return Err(io::Error::other(
83 "State and output directories must be outside the reviewed repository.",
84 ));
85 }
86 outside_artifact_workspaces(output)
87}
88
89pub(crate) fn outside_artifact_workspaces(output: &Path) -> io::Result<()> {
90 for ancestor in output.ancestors() {
91 if has_artifact_marker(ancestor)? {
92 return Err(io::Error::other(
93 "State and output directories must be outside artifactize workspaces.",
94 ));
95 }
96 }
97 Ok(())
98}
99
100pub(crate) fn prepare_directory(path: &Path, workspace: &Path) -> io::Result<PathBuf> {
101 let path = canonical_target(path)?;
102 outside_workspace(workspace, &path)?;
103 crate::platform::create_private_dir_all(&path)?;
104 let path = crate::platform::canonicalize(&path)?;
105 outside_workspace(workspace, &path)?;
106 Ok(path)
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn state_and_output_creation_refuse_other_current_or_legacy_workspaces() {
115 for marker in [
116 "index.artf",
117 "artifactize.json",
118 ".artifactizeignore",
119 "file.txt.artf",
120 ] {
121 let root = crate::test_os::tempdir();
122 let reviewed = root.path().join("reviewed");
123 let other = root.path().join("other");
124 std::fs::create_dir(&reviewed).unwrap();
125 std::fs::create_dir(&other).unwrap();
126 std::fs::write(other.join(marker), "marker").unwrap();
127 let state = other.join("state");
128 let error = prepare_directory(&state, &reviewed).unwrap_err();
129 assert!(
130 error.to_string().contains("outside artifactize workspaces"),
131 "{marker}: {error}"
132 );
133 assert!(!state.exists());
134 }
135 }
136}