use crate::*;
use carryctx::application;
use carryctx::application::runtime::InvocationContext;
use carryctx::error::ExitCode;
use clap::Parser;
#[derive(Parser, Debug)]
pub enum WorktreeCommand {
Create {
task_ref: String,
#[arg(long)]
path: Option<String>,
#[arg(long)]
branch: Option<String>,
#[arg(long)]
base: Option<String>,
},
Bind {
path: String,
#[arg(long)]
task: Option<String>,
},
List,
Show { worktree_ref: String },
Status,
Unbind { worktree_ref: String },
}
#[derive(Parser, Debug)]
pub struct WorktreeArgs {
#[command(subcommand)]
pub command: WorktreeCommand,
}
pub fn handle_worktree(
args: &WorktreeArgs,
ctx: &InvocationContext,
is_json: bool,
) -> Result<ExitCode, ExitCode> {
if let Some(result) = check_dry_run(ctx, &format!("worktree {:?}", args.command)) {
return result;
}
let mut runtime = try_open_runtime(ctx)?;
let project_id = &runtime.config.project.id;
let conn = runtime.database.connection_mut();
let now = chrono::Utc::now().to_rfc3339();
let worktree_repo = SqliteWorktreeRepository::new(conn);
let task_repo = SqliteTaskRepository::new(conn);
let event_repo = SqliteEventRepository::new(conn);
let git_cli = GitCli::new();
match &args.command {
WorktreeCommand::Create {
task_ref,
path,
branch,
base,
} => {
let branch_name = branch.clone().unwrap_or_else(|| {
format!("carryctx/{}", task_ref.replace('/', "-").to_lowercase())
});
let worktree_path = path.clone().unwrap_or_else(|| {
format!(".worktrees/{}", task_ref.replace('/', "-").to_lowercase())
});
let input = application::worktree::CreateWorktreeInput {
project_id: project_id.to_string(),
repository_root: runtime
.git_project
.repository_root
.to_string_lossy()
.to_string(),
path: worktree_path,
branch: branch_name,
base: base.clone(),
task_id: Some(task_ref.clone()),
};
let result = application::worktree::create_worktree(
&worktree_repo,
&task_repo,
&event_repo,
&git_cli,
&runtime.xdg,
&input,
&now,
);
render_and_print("worktree.create", result, is_json, ctx.quiet)
}
WorktreeCommand::Bind { path, task } => {
let input = application::worktree::BindWorktreeInput {
project_id: project_id.to_string(),
path: path.clone(),
task_id: task.clone(),
};
let result = application::worktree::bind_worktree(
&worktree_repo,
&task_repo,
&event_repo,
&git_cli,
&input,
&now,
);
render_and_print("worktree.bind", result, is_json, ctx.quiet)
}
WorktreeCommand::List => {
let result = application::worktree::list_worktrees(
&worktree_repo,
&git_cli,
project_id,
Some(&runtime.git_project.repository_root.to_string_lossy()),
);
if ctx.format == carryctx::application::runtime::OutputFormat::Markdown {
let md = match &result {
Ok(trees) => {
let mut out = String::from("# Worktrees\n\n");
out.push_str("| Path | Branch | Task |\n");
out.push_str("|---|---|---|\n");
for w in trees {
let path = w.path.split('/').next_back().unwrap_or(&w.path);
let task = w.task_id.as_deref().unwrap_or("-");
let task_s = if task.len() > 8 { &task[..8] } else { task };
let branch = w.branch.as_deref().unwrap_or("-");
out.push_str(&format!("| {} | {} | {} |\n", path, branch, task_s));
}
out
}
Err(e) => format!("Error: {e}"),
};
if !ctx.quiet {
print!("{md}");
}
return Ok(ExitCode::Success);
}
render_and_print("worktree.list", result, is_json, ctx.quiet)
}
WorktreeCommand::Show { worktree_ref } => {
let result = application::worktree::show_worktree(
&worktree_repo,
&git_cli,
project_id,
worktree_ref,
);
render_and_print("worktree.show", result, is_json, ctx.quiet)
}
WorktreeCommand::Status => {
let worktrees = worktree_repo.list(project_id).map_err(|e| e.exit_code)?;
let git_trees = git_cli
.list_worktrees(&runtime.git_project.repository_root)
.ok()
.unwrap_or_default();
let data = serde_json::json!({
"registered": worktrees,
"gitWorktrees": git_trees,
});
render_and_print("worktree.status", Ok(data), is_json, ctx.quiet)
}
WorktreeCommand::Unbind { worktree_ref } => {
let result = application::worktree::unbind_worktree(
&worktree_repo,
&event_repo,
project_id,
worktree_ref,
&now,
);
render_and_print("worktree.unbind", result, is_json, ctx.quiet)
}
}
}