1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum OnboardingRepositoryState {
6 NativeUninitialized,
7 PlainGitUnborn,
8 PlainGitCommitted,
9 NativeInitialized,
10 GitOverlayInitialized,
11}
12
13impl OnboardingRepositoryState {
14 pub const fn as_str(self) -> &'static str {
15 match self {
16 Self::NativeUninitialized => "native_uninitialized",
17 Self::PlainGitUnborn => "plain_git_unborn",
18 Self::PlainGitCommitted => "plain_git_committed",
19 Self::NativeInitialized => "native_initialized",
20 Self::GitOverlayInitialized => "git_overlay_initialized",
21 }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum OnboardingMode {
27 Native,
28 GitOverlay,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum OnboardingAction {
33 Init,
34 None,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct OnboardingFacts {
39 pub git_worktree: bool,
40 pub git_has_commits: bool,
41 pub heddle_mode: Option<OnboardingMode>,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct OnboardingPlan {
46 pub state: OnboardingRepositoryState,
47 pub mode: OnboardingMode,
48 pub action: OnboardingAction,
49}
50
51impl OnboardingPlan {
52 pub fn recommended_command(self) -> Option<&'static str> {
53 match self.action {
54 OnboardingAction::Init => Some("heddle init"),
55 OnboardingAction::None => None,
56 }
57 }
58
59 pub fn storage_summary(self) -> &'static str {
60 match self.mode {
61 OnboardingMode::Native => "Heddle owns source objects, refs, and worktree state",
62 OnboardingMode::GitOverlay => {
63 "Git owns source objects, refs, index, and worktree state; Heddle owns metadata, provenance, and projection mapping"
64 }
65 }
66 }
67}
68
69pub fn plan_repository_onboarding(facts: OnboardingFacts) -> OnboardingPlan {
70 if let Some(mode) = facts.heddle_mode {
71 let state = match mode {
72 OnboardingMode::Native => OnboardingRepositoryState::NativeInitialized,
73 OnboardingMode::GitOverlay => OnboardingRepositoryState::GitOverlayInitialized,
74 };
75 return OnboardingPlan {
76 state,
77 mode,
78 action: OnboardingAction::None,
79 };
80 }
81
82 if facts.git_worktree {
83 return OnboardingPlan {
84 state: if facts.git_has_commits {
85 OnboardingRepositoryState::PlainGitCommitted
86 } else {
87 OnboardingRepositoryState::PlainGitUnborn
88 },
89 mode: OnboardingMode::GitOverlay,
90 action: OnboardingAction::Init,
91 };
92 }
93
94 OnboardingPlan {
95 state: OnboardingRepositoryState::NativeUninitialized,
96 mode: OnboardingMode::Native,
97 action: OnboardingAction::Init,
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn classifies_uninitialized_repositories() {
107 let committed = plan_repository_onboarding(OnboardingFacts {
108 git_worktree: true,
109 git_has_commits: true,
110 heddle_mode: None,
111 });
112 assert_eq!(
113 committed.state,
114 OnboardingRepositoryState::PlainGitCommitted
115 );
116 assert_eq!(committed.mode, OnboardingMode::GitOverlay);
117 assert_eq!(committed.recommended_command(), Some("heddle init"));
118
119 let unborn = plan_repository_onboarding(OnboardingFacts {
120 git_worktree: true,
121 git_has_commits: false,
122 heddle_mode: None,
123 });
124 assert_eq!(unborn.state, OnboardingRepositoryState::PlainGitUnborn);
125 assert_eq!(unborn.mode, OnboardingMode::GitOverlay);
126 assert_eq!(unborn.recommended_command(), Some("heddle init"));
127
128 let native = plan_repository_onboarding(OnboardingFacts {
129 git_worktree: false,
130 git_has_commits: false,
131 heddle_mode: None,
132 });
133 assert_eq!(native.state, OnboardingRepositoryState::NativeUninitialized);
134 assert_eq!(native.mode, OnboardingMode::Native);
135 assert_eq!(native.recommended_command(), Some("heddle init"));
136 }
137
138 #[test]
139 fn initialized_overlay_needs_no_onboarding_action() {
140 let plan = plan_repository_onboarding(OnboardingFacts {
141 git_worktree: true,
142 git_has_commits: true,
143 heddle_mode: Some(OnboardingMode::GitOverlay),
144 });
145 assert_eq!(plan.state, OnboardingRepositoryState::GitOverlayInitialized);
146 assert_eq!(plan.action, OnboardingAction::None);
147 assert_eq!(plan.recommended_command(), None);
148 }
149}