use std::path::Path;
use std::sync::Arc;
use tracing::info;
use super::lifecycle::SpawnParams;
use crate::core::remote_mode::LOCAL_ONLY_SKIP;
use crate::daemon::state::DaemonState;
use crate::runtime::RuntimeKind;
use crate::session_manager::{ManagedSessionId, SessionRecord};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct SessionSource {
pub(super) source_id: Option<String>,
pub(super) repo_url: Option<String>,
pub(super) name: String,
}
impl SessionSource {
pub(super) fn github(owner: &str, repo: &str) -> Self {
Self {
source_id: Some(format!("{owner}/{repo}")),
repo_url: Some(format!("https://github.com/{owner}/{repo}")),
name: repo.to_string(),
}
}
pub(super) fn local_only(dir: &Path) -> Self {
let name = dir
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "local".to_string());
Self {
source_id: None,
repo_url: None,
name,
}
}
}
pub(super) async fn spawn_managed_local_only(
state: &Arc<DaemonState>,
session_id: &ManagedSessionId,
params: &SpawnParams,
runtime: RuntimeKind,
local_path: &Path,
config: &crate::core::trusty_tools_config::TrustyToolsConfig,
) -> Result<SessionRecord, String> {
info!(
id = %session_id,
path = %local_path.display(),
"spawn_managed: {LOCAL_ONLY_SKIP}; worktrees and merges stay local and gh is disabled (#8934)"
);
let source = SessionSource::local_only(local_path);
if !params.worktree {
if let Some(live) = live_session_on(state, params.force_new, local_path).await {
return Ok(live);
}
return super::launch_on_main::spawn_managed_on_main(
state, session_id, params, runtime, local_path, &source,
)
.await;
}
crate::core::worktree_naming::ensure_worktrees_gitignored(local_path)?;
let (worktree, reserved_name) = super::lifecycle::reserve_inproject_worktree(
state,
session_id,
params,
local_path,
local_path,
&source.name,
config,
)
.await?;
super::lifecycle::spawn_managed_inproject(
state,
session_id,
params,
runtime,
worktree,
source,
reserved_name,
)
.await
}
async fn live_session_on(
state: &Arc<DaemonState>,
force_new: bool,
dir: &Path,
) -> Option<SessionRecord> {
if force_new {
return None;
}
let mgr = state.session_manager().await;
let existing = mgr.list().await;
let tmux = mgr.tmux_driver();
super::launch_on_main::has_concurrent_main_checkout_session(&existing, dir)
.filter(|r| tmux.session_exists(&r.tmux_name))
.cloned()
}
pub(super) fn spawn_managed_local(session_id: &ManagedSessionId, params: &SpawnParams) -> String {
let local_dir = std::path::PathBuf::from(¶ms.repo_url);
let origin_url = match super::inproject::get_origin_url(&local_dir) {
Err(e) => return e,
Ok(None) => {
return format!(
"spawn failed: '{}' is not a git repository root. Pass the directory \
holding `.git`; a repository with no origin remote runs local-only \
(#8934). Use `tm connect` to run in a directory that is not a repository.",
local_dir.display()
);
}
Ok(Some(url)) => url,
};
let shown = crate::core::remote_url_redact::redact_stored_url(&origin_url);
if trusty_common::github_path::parse_github_path(&origin_url).is_none() {
return format!(
"spawn failed: could not parse a GitHub owner/repo from origin remote \
'{shown}' for '{}'. \
Use `tm connect` to run in the live checkout instead.",
local_dir.display()
);
}
format!(
"spawn failed for session {session_id}: '{}' resolves to the repository at \
'{shown}' but is not that repository's root, so there is no checkout to run \
the session in. trusty-mpm no longer provisions one for you (ADR-0055): pass the \
repository ROOT — the directory holding `.git` — instead.",
local_dir.display()
)
}
#[cfg(test)]
#[path = "local_only_spawn_tests.rs"]
mod tests;