Skip to main content

mj_controller/controller/checkpoint/
layout.rs

1use super::*;
2
3impl Controller {
4    /// Where a session's repositories live on its target, and what each one
5    /// contributes to an export.
6    ///
7    /// A checkpoint and a diff, a file read or a branch push all need the same
8    /// answers - which target, which directory, which repository is the
9    /// primary - so they are derived once here rather than restated wherever a
10    /// caller reaches the target.
11    pub fn session_export_layout(
12        &self,
13        session_id: &str,
14        executor: &(impl CommandExecutor + Sync),
15    ) -> Result<SessionExportLayout> {
16        let session = self
17            .state
18            .sessions
19            .get(session_id)
20            .with_context(|| format!("unknown session {session_id}"))?
21            .clone();
22        let checkout = self.state.checkout(session_id)?;
23        let locator = session
24            .target
25            .as_ref()
26            .context("session has no live target")?;
27        let backend = backend_locator(locator, &session, &self.config)?;
28        let (workspace_root, primary_repository, repositories) =
29            if let Some(project_directory) = checkout.project_directory() {
30                let parent = project_directory
31                    .parent()
32                    .context("bare project directory has no parent")?;
33                let destination = project_directory
34                    .file_name()
35                    .context("bare project directory cannot be the filesystem root")?;
36                (
37                    parent.to_string_lossy().into_owned(),
38                    "project".to_owned(),
39                    vec![CheckpointRepositorySpec {
40                        id: "project".into(),
41                        relative_destination: PathBuf::from(destination),
42                        // Managed worktrees are retired on Stop, so their
43                        // dirty/untracked state must travel in the archive.
44                        // Capturing from the branch's creation point means the
45                        // bundle also carries the session's own commits. The
46                        // branch and objects remain in the owning Git
47                        // repository as well; no remote origin is required.
48                        // Unmanaged raw checkouts remain in place.
49                        capture: match &checkout {
50                            mj_core::state::Checkout::ManagedWorktree { worktree, .. } => {
51                                let base_commit =
52                                    crate::controller::worktree::managed_worktree_base_commit(
53                                        worktree, executor,
54                                    )?;
55                                if worktree.kind == mj_core::state::ManagedCheckoutKind::Clone {
56                                    CheckpointRepositoryCapture::ManagedClone { base_commit }
57                                } else {
58                                    CheckpointRepositoryCapture::DeltaFrom { base_commit }
59                                }
60                            }
61                            _ => CheckpointRepositoryCapture::MetadataOnly,
62                        },
63                        origin_override: None,
64                    }],
65                )
66            } else {
67                let bundle = session
68                    .project_bundle(&self.config)
69                    .context("session bundle is missing")?;
70                let workspace_root = crate::controller::network_git::workspace_root(
71                    &backend,
72                    session.container_workspace.as_deref(),
73                );
74                let repositories = bundle
75                    .repositories
76                    .iter()
77                    .map(|repository| CheckpointRepositorySpec {
78                        id: repository.id.clone(),
79                        relative_destination: repository.destination.clone(),
80                        capture: CheckpointRepositoryCapture::RemoteWorkspace,
81                        origin_override: None,
82                    })
83                    .collect();
84                (workspace_root, bundle.primary_repo.clone(), repositories)
85            };
86        Ok(SessionExportLayout {
87            backend,
88            workspace_root,
89            primary_repository,
90            repositories,
91            managed_worktree: match checkout {
92                mj_core::state::Checkout::ManagedWorktree { worktree, .. } => {
93                    Some(worktree.clone())
94                }
95                _ => None,
96            },
97        })
98    }
99}