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 locator = session
23            .target
24            .as_ref()
25            .context("session has no live target")?;
26        let backend = backend_locator(locator, &session, &self.config)?;
27        let (workspace_root, primary_repository, repositories) = if let Some(project_directory) =
28            &session.project_directory
29        {
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 &session.managed_worktree {
50                        Some(worktree) => CheckpointRepositoryCapture::DeltaFrom {
51                            base_commit: crate::controller::worktree::managed_worktree_base_commit(
52                                worktree, executor,
53                            )?,
54                        },
55                        None => CheckpointRepositoryCapture::MetadataOnly,
56                    },
57                    origin_override: None,
58                }],
59            )
60        } else {
61            let bundle = self
62                .config
63                .bundles
64                .get(&session.bundle_id)
65                .context("session bundle is missing")?;
66            let workspace_root = crate::controller::network_git::workspace_root(
67                &backend,
68                session.container_workspace.as_deref(),
69            );
70            let repositories = bundle
71                .repositories
72                .iter()
73                .map(|repository| CheckpointRepositorySpec {
74                    id: repository.id.clone(),
75                    relative_destination: repository.destination.clone(),
76                    capture: CheckpointRepositoryCapture::RemoteWorkspace,
77                    origin_override: None,
78                })
79                .collect();
80            (workspace_root, bundle.primary_repo.clone(), repositories)
81        };
82        Ok(SessionExportLayout {
83            backend,
84            workspace_root,
85            primary_repository,
86            repositories,
87            managed_worktree: session.managed_worktree,
88        })
89    }
90}