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#[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 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}