newgit_core/
materializer.rs1use camino::{Utf8Path, Utf8PathBuf};
2use serde::{Deserialize, Serialize};
3
4use crate::branch::BranchInstance;
5use crate::error::{NewgitError, Result};
6use crate::source::GitSource;
7
8pub trait Materializer {
13 fn materialize(&self, source: &GitSource, branch: &BranchInstance) -> Result<()>;
14 fn remove(&self, branch: &BranchInstance) -> Result<()>;
15}
16
17#[derive(Debug, Clone, Copy, Default)]
18pub struct RealDirMaterializer;
19
20impl Materializer for RealDirMaterializer {
21 fn materialize(&self, source: &GitSource, branch: &BranchInstance) -> Result<()> {
22 if branch.workspace_path.exists() {
23 return Err(NewgitError::WorkspaceExists(branch.workspace_path.clone()));
24 }
25 if let Some(parent) = branch.workspace_path.parent() {
26 create_dir_all(parent)?;
27 }
28 source.clone_to(&branch.source_ref, &branch.workspace_path)?;
29 write_workspace_marker(source.root(), branch)
30 }
31
32 fn remove(&self, branch: &BranchInstance) -> Result<()> {
33 if branch.workspace_path.exists() {
34 std::fs::remove_dir_all(&branch.workspace_path)
35 .map_err(|source| NewgitError::io(branch.workspace_path.clone(), source))?;
36 }
37 Ok(())
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45pub struct WorkspaceMarker {
46 pub branch: String,
47 pub store_root: Utf8PathBuf,
48}
49
50pub fn workspace_marker_path(workspace: &Utf8Path) -> Utf8PathBuf {
51 workspace.join(".newgit/local/instance.toml")
52}
53
54pub fn exclude_tracker_paths(workspace: &Utf8Path, paths: &[Utf8PathBuf]) -> Result<()> {
69 if paths.is_empty() {
70 return Ok(());
71 }
72 let exclude = workspace.join(".git/info/exclude");
73 if let Some(parent) = exclude.parent() {
74 create_dir_all(parent)?;
75 }
76 let mut contents = std::fs::read_to_string(&exclude).unwrap_or_default();
77 if !contents.is_empty() && !contents.ends_with('\n') {
78 contents.push('\n');
79 }
80 contents.push_str("\n# newgit: tracker-owned paths — these lanes own this content\n");
81 for path in paths {
82 contents.push_str(&format!("/{path}\n"));
83 }
84 std::fs::write(&exclude, contents).map_err(|source| NewgitError::io(exclude, source))
85}
86
87fn write_workspace_marker(store_root: &Utf8Path, branch: &BranchInstance) -> Result<()> {
88 let marker = WorkspaceMarker {
89 branch: branch.name.clone(),
90 store_root: store_root.to_path_buf(),
91 };
92 let path = workspace_marker_path(&branch.workspace_path);
93 if let Some(parent) = path.parent() {
94 create_dir_all(parent)?;
95 let gitignore = parent.join(".gitignore");
98 std::fs::write(&gitignore, "*\n").map_err(|source| NewgitError::io(gitignore, source))?;
99 }
100 let contents = toml::to_string_pretty(&marker).map_err(|source| NewgitError::TomlWrite {
101 label: "workspace marker".to_owned(),
102 source,
103 })?;
104 std::fs::write(&path, contents).map_err(|source| NewgitError::io(path, source))
105}
106
107pub fn create_dir_all(path: &Utf8Path) -> Result<()> {
108 std::fs::create_dir_all(path).map_err(|source| NewgitError::io(path.to_path_buf(), source))
109}