new worktree on attempt create

This commit is contained in:
Louis Knight-Webb
2025-06-16 17:15:51 -04:00
parent f84e3bfd20
commit 7f7592e5bf
4 changed files with 85 additions and 3 deletions

View File

@@ -68,3 +68,8 @@ When working on any task that involves changes to the backend and the frontend,
# Testing your work
Try to build the Typescript project after any frontend changes `npm run build`
# Backend data models
SQLX queries should be located in backend/src/models/\*
Use getters and setters instead of raw SQL queries where possible.

View File

@@ -26,6 +26,7 @@ bcrypt = "0.15"
jsonwebtoken = "9.2"
ts-rs = { version = "9.0", features = ["uuid-impl", "chrono-impl"] }
dirs = "5.0"
git2 = "0.18"
[build-dependencies]
ts-rs = { version = "9.0", features = ["uuid-impl", "chrono-impl"] }

View File

@@ -58,6 +58,18 @@ impl Task {
.await
}
pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
sqlx::query_as!(
Task,
r#"SELECT id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at
FROM tasks
WHERE id = $1"#,
id
)
.fetch_optional(pool)
.await
}
pub async fn find_by_id_and_project_id(pool: &PgPool, id: Uuid, project_id: Uuid) -> Result<Option<Self>, sqlx::Error> {
sqlx::query_as!(
Task,

View File

@@ -3,6 +3,44 @@ use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Type, PgPool};
use ts_rs::TS;
use uuid::Uuid;
use git2::{Repository, Error as GitError};
use std::path::Path;
use super::task::Task;
use super::project::Project;
#[derive(Debug)]
pub enum TaskAttemptError {
Database(sqlx::Error),
Git(GitError),
TaskNotFound,
ProjectNotFound,
}
impl std::fmt::Display for TaskAttemptError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TaskAttemptError::Database(e) => write!(f, "Database error: {}", e),
TaskAttemptError::Git(e) => write!(f, "Git error: {}", e),
TaskAttemptError::TaskNotFound => write!(f, "Task not found"),
TaskAttemptError::ProjectNotFound => write!(f, "Project not found"),
}
}
}
impl std::error::Error for TaskAttemptError {}
impl From<sqlx::Error> for TaskAttemptError {
fn from(err: sqlx::Error) -> Self {
TaskAttemptError::Database(err)
}
}
impl From<GitError> for TaskAttemptError {
fn from(err: GitError) -> Self {
TaskAttemptError::Git(err)
}
}
#[derive(Debug, Clone, Type, Serialize, Deserialize, PartialEq, TS)]
#[sqlx(type_name = "task_attempt_status", rename_all = "lowercase")]
@@ -57,10 +95,34 @@ impl TaskAttempt {
.await
}
pub async fn create(pool: &PgPool, data: &CreateTaskAttempt, attempt_id: Uuid) -> Result<Self, sqlx::Error> {
pub async fn create(pool: &PgPool, data: &CreateTaskAttempt, attempt_id: Uuid) -> Result<Self, TaskAttemptError> {
let now = Utc::now();
sqlx::query_as!(
// First, get the task to get the project_id
let task = Task::find_by_id(pool, data.task_id)
.await?
.ok_or(TaskAttemptError::TaskNotFound)?;
// Then get the project using the project_id
let project = Project::find_by_id(pool, task.project_id)
.await?
.ok_or(TaskAttemptError::ProjectNotFound)?;
// Create the worktree using git2
let repo = Repository::open(&project.git_repo_path)?;
let worktree_path = Path::new(&data.worktree_path);
// Create the worktree directory if it doesn't exist
if let Some(parent) = worktree_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| TaskAttemptError::Git(GitError::from_str(&e.to_string())))?;
}
// Create the worktree at the specified path
let branch_name = format!("attempt-{}", attempt_id);
repo.worktree(&branch_name, worktree_path, None)?;
// Insert the record into the database
let task_attempt = sqlx::query_as!(
TaskAttempt,
r#"INSERT INTO task_attempts (id, task_id, worktree_path, base_commit, merge_commit, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
@@ -74,7 +136,9 @@ impl TaskAttempt {
now
)
.fetch_one(pool)
.await
.await?;
Ok(task_attempt)
}
pub async fn exists_for_task(pool: &PgPool, attempt_id: Uuid, task_id: Uuid, project_id: Uuid) -> Result<bool, sqlx::Error> {