Skip to main content

newgit_core/
config.rs

1use camino::{Utf8Path, Utf8PathBuf};
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4
5use crate::store::expand_home;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
8pub struct ProjectConfig {
9    pub project: ProjectSection,
10    #[serde(default, skip_serializing_if = "Option::is_none")]
11    pub workspace: Option<WorkspaceSection>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub struct ProjectSection {
16    pub name: String,
17    pub source: SourceSubstrate,
18}
19
20#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
21#[serde(rename_all = "kebab-case")]
22pub enum SourceSubstrate {
23    Git,
24    Jj,
25}
26
27/// Optional overrides; omitted from the generated config so a committed
28/// file never bakes in one user's absolute paths.
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
30pub struct WorkspaceSection {
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub root: Option<Utf8PathBuf>,
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub materializer: Option<String>,
35}
36
37impl ProjectConfig {
38    pub fn new(project_name: impl Into<String>, source: SourceSubstrate) -> Self {
39        Self {
40            project: ProjectSection {
41                name: project_name.into(),
42                source,
43            },
44            workspace: None,
45        }
46    }
47
48    /// Where this project's workspaces live. Defaults to
49    /// `~/.newgit/workspaces/<name>-<hash>/`, where the hash disambiguates
50    /// same-named projects at different paths.
51    pub fn workspace_root(&self, project_root: &Utf8Path) -> Utf8PathBuf {
52        if let Some(root) = self.workspace.as_ref().and_then(|ws| ws.root.as_ref()) {
53            return expand_home(root);
54        }
55        default_workspace_root(&self.project.name, project_root)
56    }
57}
58
59pub fn default_workspace_root(project_name: &str, project_root: &Utf8Path) -> Utf8PathBuf {
60    let digest = Sha256::digest(project_root.as_str().as_bytes());
61    let hash: String = digest[..4]
62        .iter()
63        .map(|byte| format!("{byte:02x}"))
64        .collect();
65    expand_home(Utf8Path::new("~/.newgit/workspaces")).join(format!("{project_name}-{hash}"))
66}
67
68#[cfg(test)]
69mod tests {
70    use camino::Utf8Path;
71
72    use super::{ProjectConfig, SourceSubstrate, default_workspace_root};
73
74    #[test]
75    fn default_root_disambiguates_same_named_projects() {
76        let a = default_workspace_root("api", Utf8Path::new("/home/u/work/api"));
77        let b = default_workspace_root("api", Utf8Path::new("/home/u/other/api"));
78        assert_ne!(a, b);
79    }
80
81    #[test]
82    fn workspace_override_wins() {
83        let mut config = ProjectConfig::new("api", SourceSubstrate::Git);
84        config.workspace = Some(super::WorkspaceSection {
85            root: Some("/data/ws".into()),
86            materializer: None,
87        });
88        assert_eq!(
89            config.workspace_root(Utf8Path::new("/home/u/api")),
90            Utf8Path::new("/data/ws")
91        );
92    }
93}