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) =
28            if let Some(project_directory) = &session.project_directory {
29                let parent = project_directory
30                    .parent()
31                    .context("bare project directory has no parent")?;
32                let destination = project_directory
33                    .file_name()
34                    .context("bare project directory cannot be the filesystem root")?;
35                (
36                    parent.to_string_lossy().into_owned(),
37                    "project".to_owned(),
38                    vec![CheckpointRepositorySpec {
39                        id: "project".into(),
40                        relative_destination: PathBuf::from(destination),
41                        // Managed worktrees are retired on Stop, so their
42                        // dirty/untracked state must travel in the archive.
43                        // Capturing from the branch's creation point means the
44                        // bundle also carries the session's own commits. The
45                        // branch and objects remain in the owning Git
46                        // repository as well; no remote origin is required.
47                        // Unmanaged raw checkouts remain in place.
48                        capture: match &session.managed_worktree {
49                            Some(worktree) => {
50                                let base_commit =
51                                    crate::controller::worktree::managed_worktree_base_commit(
52                                        worktree, executor,
53                                    )?;
54                                if worktree.kind == mj_core::state::ManagedCheckoutKind::Clone {
55                                    CheckpointRepositoryCapture::ManagedClone { base_commit }
56                                } else {
57                                    CheckpointRepositoryCapture::DeltaFrom { base_commit }
58                                }
59                            }
60                            None => CheckpointRepositoryCapture::MetadataOnly,
61                        },
62                        origin_override: None,
63                    }],
64                )
65            } else {
66                let bundle = self
67                    .config
68                    .bundles
69                    .get(&session.bundle_id)
70                    .context("session bundle is missing")?;
71                let workspace_root = crate::controller::network_git::workspace_root(
72                    &backend,
73                    session.container_workspace.as_deref(),
74                );
75                let repositories = bundle
76                    .repositories
77                    .iter()
78                    .map(|repository| CheckpointRepositorySpec {
79                        id: repository.id.clone(),
80                        relative_destination: repository.destination.clone(),
81                        capture: CheckpointRepositoryCapture::RemoteWorkspace,
82                        origin_override: None,
83                    })
84                    .collect();
85                (workspace_root, bundle.primary_repo.clone(), repositories)
86            };
87        Ok(SessionExportLayout {
88            backend,
89            workspace_root,
90            primary_repository,
91            repositories,
92            managed_worktree: session.managed_worktree,
93        })
94    }
95}