git_stub_vcs/
materialize.rs1use crate::{MaterializeError, Vcs};
6use atomicwrites::AtomicFile;
7use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
8use fs_err as fs;
9use git_stub::GitStub;
10use std::io::Write;
11
12fn find_non_normal_component(path: &Utf8Path) -> Option<String> {
17 path.components().find_map(|component| match component {
18 Utf8Component::Normal(_) => None,
19 Utf8Component::Prefix(_)
20 | Utf8Component::RootDir
21 | Utf8Component::CurDir
22 | Utf8Component::ParentDir => Some(component.as_str().to_owned()),
23 })
24}
25
26fn check_path(path: &Utf8Path) -> Result<(), MaterializeError> {
28 if let Some(component) = find_non_normal_component(path) {
29 return Err(MaterializeError::InvalidPathComponent {
30 path: path.to_owned(),
31 component,
32 });
33 }
34 Ok(())
35}
36
37#[derive(Debug, Clone)]
42pub struct Materializer {
43 repo_root: Utf8PathBuf,
44 output_dir: Utf8PathBuf,
45 emit_cargo_directives: bool,
46 vcs: Vcs,
47}
48
49impl Materializer {
50 pub fn standard(
61 repo_root: impl Into<Utf8PathBuf>,
62 output_dir: impl Into<Utf8PathBuf>,
63 ) -> Result<Self, MaterializeError> {
64 let repo_root = repo_root.into();
65 let vcs = Vcs::detect(&repo_root)?;
66 Self::check_shallow(&vcs, &repo_root)?;
67 Ok(Materializer {
68 repo_root,
69 output_dir: output_dir.into(),
70 emit_cargo_directives: false,
71 vcs,
72 })
73 }
74
75 pub fn for_build_script(
92 repo_root: impl Into<Utf8PathBuf>,
93 ) -> Result<Self, MaterializeError> {
94 let out_dir = std::env::var("OUT_DIR").expect(
95 "OUT_DIR is set \
96 (must be called from a Cargo build script)",
97 );
98 let out_dir = Utf8PathBuf::from(out_dir).join("git-stub-vcs");
99
100 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect(
101 "CARGO_MANIFEST_DIR is set \
102 (must be called from a Cargo build script)",
103 );
104 let manifest_dir = Utf8PathBuf::from(manifest_dir);
105 let repo_root = manifest_dir.join(repo_root.into());
106
107 let vcs = Vcs::detect(&repo_root)?;
108 Self::check_shallow(&vcs, &repo_root)?;
109 Ok(Materializer {
110 repo_root,
111 output_dir: out_dir,
112 emit_cargo_directives: true,
113 vcs,
114 })
115 }
116
117 pub fn with_vcs(mut self, vcs: Vcs) -> Result<Self, MaterializeError> {
125 Self::check_shallow(&vcs, &self.repo_root)?;
126 self.vcs = vcs;
127 Ok(self)
128 }
129
130 pub fn vcs(&self) -> &Vcs {
132 &self.vcs
133 }
134
135 fn check_shallow(
139 vcs: &Vcs,
140 repo_root: &Utf8Path,
141 ) -> Result<(), MaterializeError> {
142 if vcs.is_shallow_clone(repo_root).map_err(|error| {
143 MaterializeError::ShallowCloneCheck {
144 repo_root: repo_root.to_owned(),
145 error,
146 }
147 })? {
148 return Err(MaterializeError::ShallowClone {
149 vcs: vcs.name(),
150 repo_root: repo_root.to_owned(),
151 });
152 }
153 Ok(())
154 }
155
156 pub fn materialize(
175 &self,
176 git_stub_path: impl AsRef<Utf8Path>,
177 ) -> Result<Utf8PathBuf, MaterializeError> {
178 let git_stub_path = git_stub_path.as_ref();
179
180 check_path(git_stub_path)?;
181
182 if git_stub_path.extension() != Some("gitstub") {
183 return Err(MaterializeError::NotGitStub {
184 path: git_stub_path.to_owned(),
185 });
186 }
187
188 let output_path =
191 self.output_dir.join(git_stub_path.with_extension(""));
192 self.materialize_inner(git_stub_path, &output_path)?;
193 Ok(output_path)
194 }
195
196 pub fn materialize_to(
204 &self,
205 git_stub_path: impl AsRef<Utf8Path>,
206 output_path: impl AsRef<Utf8Path>,
207 ) -> Result<(), MaterializeError> {
208 let git_stub_path = git_stub_path.as_ref();
209 let output_path = output_path.as_ref();
210
211 check_path(git_stub_path)?;
212
213 if git_stub_path.extension() != Some("gitstub") {
214 return Err(MaterializeError::NotGitStub {
215 path: git_stub_path.to_owned(),
216 });
217 }
218
219 let output_path = self.output_dir.join(output_path);
220 self.materialize_inner(git_stub_path, &output_path)
221 }
222
223 fn materialize_inner(
226 &self,
227 git_stub_path: &Utf8Path,
228 output_path: &Utf8Path,
229 ) -> Result<(), MaterializeError> {
230 let full_git_stub_path = self.repo_root.join(git_stub_path);
231
232 if self.emit_cargo_directives {
233 println!("cargo::rerun-if-changed={}", full_git_stub_path);
234 }
235
236 let git_stub_contents = fs::read_to_string(&full_git_stub_path)
237 .map_err(|error| MaterializeError::ReadGitStub {
238 path: full_git_stub_path.clone(),
239 error,
240 })?;
241
242 let git_stub: GitStub = git_stub_contents.parse().map_err(|error| {
243 MaterializeError::InvalidGitStub { path: full_git_stub_path, error }
244 })?;
245
246 let content =
247 self.vcs.read_git_stub_contents(&git_stub, &self.repo_root)?;
248
249 if let Some(parent) = output_path.parent() {
250 fs::create_dir_all(parent).map_err(|error| {
251 MaterializeError::CreateDir { path: parent.to_owned(), error }
252 })?;
253 }
254
255 AtomicFile::new(
256 output_path,
257 atomicwrites::OverwriteBehavior::AllowOverwrite,
258 )
259 .write(|f| f.write_all(&content))
260 .map_err(|error| {
261 use crate::errors::AtomicWriteError;
262 let error = match error {
263 atomicwrites::Error::Internal(e) => AtomicWriteError::Rename(e),
264 atomicwrites::Error::User(e) => AtomicWriteError::Write(e),
265 };
266 MaterializeError::WriteOutput {
267 path: output_path.to_owned(),
268 error,
269 }
270 })?;
271
272 Ok(())
273 }
274}
275
276