feat: git pull before worktree creation (#100)
This commit is contained in:
@@ -79,6 +79,7 @@ func (a *lifecycleAdapter) LaunchAgent(ctx context.Context, req *executor.Launch
|
||||
RepositoryPath: req.RepositoryPath,
|
||||
BaseBranch: req.BaseBranch,
|
||||
WorktreeBranchPrefix: req.WorktreeBranchPrefix,
|
||||
PullBeforeWorktree: req.PullBeforeWorktree,
|
||||
}
|
||||
|
||||
// Create the agentctl execution (does NOT start agent process)
|
||||
|
||||
@@ -468,6 +468,7 @@ func (m *Manager) createExecution(ctx context.Context, taskID string, info *Work
|
||||
|
||||
return execution, nil
|
||||
}
|
||||
|
||||
// Start starts the lifecycle manager background tasks
|
||||
func (m *Manager) Start(ctx context.Context) error {
|
||||
if m.runtimeRegistry == nil {
|
||||
@@ -982,6 +983,7 @@ func (m *Manager) getOrCreateWorktree(ctx context.Context, req *LaunchRequest) (
|
||||
RepositoryPath: req.RepositoryPath,
|
||||
BaseBranch: req.BaseBranch,
|
||||
WorktreeBranchPrefix: req.WorktreeBranchPrefix,
|
||||
PullBeforeWorktree: req.PullBeforeWorktree,
|
||||
WorktreeID: worktreeID, // If set, will try to reuse this worktree
|
||||
}
|
||||
|
||||
@@ -1769,9 +1771,10 @@ func (m *Manager) UpdateStatus(executionID string, status v1.AgentStatus) error
|
||||
// - After cancelling an agent turn (to allow new prompts)
|
||||
//
|
||||
// State Machine Transitions:
|
||||
// Starting -> Ready (after initialization)
|
||||
// Running -> Ready (after prompt completion)
|
||||
// Any -> Ready (after cancel)
|
||||
//
|
||||
// Starting -> Ready (after initialization)
|
||||
// Running -> Ready (after prompt completion)
|
||||
// Any -> Ready (after cancel)
|
||||
//
|
||||
// Publishes an AgentReady event to notify subscribers (frontend, orchestrator).
|
||||
//
|
||||
@@ -1812,8 +1815,9 @@ func (m *Manager) MarkReady(executionID string) error {
|
||||
// - errorMessage: Human-readable error description (empty string if no error)
|
||||
//
|
||||
// State Machine:
|
||||
// This is a terminal state transition - no further state changes are expected after this.
|
||||
// Typical flow: Starting -> Running -> Ready -> ... -> Completed/Failed
|
||||
//
|
||||
// This is a terminal state transition - no further state changes are expected after this.
|
||||
// Typical flow: Starting -> Running -> Ready -> ... -> Completed/Failed
|
||||
//
|
||||
// Publishes either AgentCompleted or AgentFailed event depending on final status.
|
||||
//
|
||||
@@ -1855,9 +1859,9 @@ func (m *Manager) MarkCompleted(executionID string, exitCode int, errorMessage s
|
||||
// RemoveExecution removes an execution from tracking.
|
||||
//
|
||||
// ⚠️ WARNING: This is a potentially dangerous operation that should only be called when:
|
||||
// 1. The agent process has been fully stopped (via StopAgent)
|
||||
// 2. All cleanup operations have completed (worktree cleanup, container removal)
|
||||
// 3. The execution is in a terminal state (Completed, Failed, or Cancelled)
|
||||
// 1. The agent process has been fully stopped (via StopAgent)
|
||||
// 2. All cleanup operations have completed (worktree cleanup, container removal)
|
||||
// 3. The execution is in a terminal state (Completed, Failed, or Cancelled)
|
||||
//
|
||||
// This method:
|
||||
// - Removes the execution from the in-memory store
|
||||
@@ -1889,8 +1893,8 @@ func (m *Manager) RemoveExecution(executionID string) {
|
||||
// - When IsAgentRunningForSession returns false but execution exists
|
||||
//
|
||||
// This method performs cleanup:
|
||||
// 1. Closes the agentctl HTTP client connection
|
||||
// 2. Removes the execution from the in-memory tracking store
|
||||
// 1. Closes the agentctl HTTP client connection
|
||||
// 2. Removes the execution from the in-memory tracking store
|
||||
//
|
||||
// What this does NOT do:
|
||||
// - Stop the agent process (assumed already stopped)
|
||||
@@ -2005,7 +2009,7 @@ func (m *Manager) performCleanup(ctx context.Context) {
|
||||
// - pendingID: Unique ID of the permission request (from permission request event)
|
||||
// - optionID: The user-selected option ID (from the permission request's options array)
|
||||
// - cancelled: If true, indicates user cancelled/rejected the permission request.
|
||||
// When cancelled=true, optionID is ignored.
|
||||
// When cancelled=true, optionID is ignored.
|
||||
//
|
||||
// Response Semantics:
|
||||
// - cancelled=false, optionID="approve" → User approved the action
|
||||
|
||||
@@ -28,12 +28,12 @@ type AgentExecution struct {
|
||||
ACPSessionID string // ACP session ID to resume, if available
|
||||
AgentCommand string // Command to start the agent subprocess
|
||||
RuntimeName string // Name of the runtime used (e.g., "docker", "standalone")
|
||||
Status v1.AgentStatus
|
||||
StartedAt time.Time
|
||||
FinishedAt *time.Time
|
||||
ExitCode *int
|
||||
ErrorMessage string
|
||||
Metadata map[string]interface{}
|
||||
Status v1.AgentStatus
|
||||
StartedAt time.Time
|
||||
FinishedAt *time.Time
|
||||
ExitCode *int
|
||||
ErrorMessage string
|
||||
Metadata map[string]interface{}
|
||||
|
||||
// agentctl client for this execution
|
||||
agentctl *agentctl.Client
|
||||
@@ -107,6 +107,7 @@ type LaunchRequest struct {
|
||||
RepositoryPath string // Path to the main repository (for worktree creation)
|
||||
BaseBranch string // Base branch for the worktree (e.g., "main")
|
||||
WorktreeBranchPrefix string // Branch prefix for worktree branches
|
||||
PullBeforeWorktree bool // Whether to pull from remote before creating the worktree
|
||||
}
|
||||
|
||||
// CredentialsManager interface for credential retrieval
|
||||
|
||||
@@ -95,6 +95,7 @@ type LaunchAgentRequest struct {
|
||||
RepositoryPath string // Path to the main repository (for worktree creation)
|
||||
BaseBranch string // Base branch for the worktree (e.g., "main")
|
||||
WorktreeBranchPrefix string // Branch prefix for worktree branches
|
||||
PullBeforeWorktree bool // Whether to pull from remote before creating the worktree
|
||||
}
|
||||
|
||||
// LaunchAgentResponse contains the result of launching an agent
|
||||
@@ -230,6 +231,7 @@ func (e *Executor) ExecuteWithProfile(ctx context.Context, task *v1.Task, agentP
|
||||
var repositoryID string
|
||||
var baseBranch string
|
||||
var worktreeBranchPrefix string
|
||||
var pullBeforeWorktree bool
|
||||
|
||||
// Get the primary repository for this task
|
||||
primaryTaskRepo, err := e.repo.GetPrimaryTaskRepository(ctx, task.ID)
|
||||
@@ -254,6 +256,7 @@ func (e *Executor) ExecuteWithProfile(ctx context.Context, task *v1.Task, agentP
|
||||
}
|
||||
repositoryPath = repository.LocalPath
|
||||
worktreeBranchPrefix = repository.WorktreeBranchPrefix
|
||||
pullBeforeWorktree = repository.PullBeforeWorktree
|
||||
if repositoryPath != "" {
|
||||
req.RepositoryURL = repositoryPath
|
||||
}
|
||||
@@ -276,6 +279,7 @@ func (e *Executor) ExecuteWithProfile(ctx context.Context, task *v1.Task, agentP
|
||||
req.BaseBranch = "main"
|
||||
}
|
||||
req.WorktreeBranchPrefix = worktreeBranchPrefix
|
||||
req.PullBeforeWorktree = pullBeforeWorktree
|
||||
}
|
||||
|
||||
// Resolve agent profile to get model and other settings for snapshot
|
||||
@@ -556,6 +560,8 @@ func (e *Executor) ResumeSession(ctx context.Context, session *models.TaskSessio
|
||||
|
||||
repositoryID := session.RepositoryID
|
||||
var repositoryPath string
|
||||
var worktreeBranchPrefix string
|
||||
var pullBeforeWorktree bool
|
||||
if repositoryID == "" && len(task.Repositories) > 0 {
|
||||
repositoryID = task.Repositories[0].RepositoryID
|
||||
}
|
||||
@@ -569,6 +575,8 @@ func (e *Executor) ResumeSession(ctx context.Context, session *models.TaskSessio
|
||||
return nil, err
|
||||
}
|
||||
repositoryPath = repository.LocalPath
|
||||
worktreeBranchPrefix = repository.WorktreeBranchPrefix
|
||||
pullBeforeWorktree = repository.PullBeforeWorktree
|
||||
if repositoryPath != "" {
|
||||
req.RepositoryURL = repositoryPath
|
||||
}
|
||||
@@ -591,6 +599,8 @@ func (e *Executor) ResumeSession(ctx context.Context, session *models.TaskSessio
|
||||
} else {
|
||||
req.BaseBranch = "main"
|
||||
}
|
||||
req.WorktreeBranchPrefix = worktreeBranchPrefix
|
||||
req.PullBeforeWorktree = pullBeforeWorktree
|
||||
}
|
||||
|
||||
if running, err := e.repo.GetExecutorRunningBySessionID(ctx, session.ID); err == nil && running != nil {
|
||||
|
||||
@@ -51,6 +51,7 @@ func (c *RepositoryController) CreateRepository(ctx context.Context, req dto.Cre
|
||||
ProviderName: req.ProviderName,
|
||||
DefaultBranch: req.DefaultBranch,
|
||||
WorktreeBranchPrefix: req.WorktreeBranchPrefix,
|
||||
PullBeforeWorktree: req.PullBeforeWorktree,
|
||||
SetupScript: req.SetupScript,
|
||||
CleanupScript: req.CleanupScript,
|
||||
DevScript: req.DevScript,
|
||||
@@ -72,6 +73,7 @@ func (c *RepositoryController) UpdateRepository(ctx context.Context, req dto.Upd
|
||||
ProviderName: req.ProviderName,
|
||||
DefaultBranch: req.DefaultBranch,
|
||||
WorktreeBranchPrefix: req.WorktreeBranchPrefix,
|
||||
PullBeforeWorktree: req.PullBeforeWorktree,
|
||||
SetupScript: req.SetupScript,
|
||||
CleanupScript: req.CleanupScript,
|
||||
DevScript: req.DevScript,
|
||||
|
||||
@@ -52,6 +52,7 @@ type RepositoryDTO struct {
|
||||
ProviderName string `json:"provider_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
WorktreeBranchPrefix string `json:"worktree_branch_prefix"`
|
||||
PullBeforeWorktree bool `json:"pull_before_worktree"`
|
||||
SetupScript string `json:"setup_script"`
|
||||
CleanupScript string `json:"cleanup_script"`
|
||||
DevScript string `json:"dev_script"`
|
||||
@@ -305,6 +306,7 @@ func FromRepository(repository *models.Repository) RepositoryDTO {
|
||||
ProviderName: repository.ProviderName,
|
||||
DefaultBranch: repository.DefaultBranch,
|
||||
WorktreeBranchPrefix: repository.WorktreeBranchPrefix,
|
||||
PullBeforeWorktree: repository.PullBeforeWorktree,
|
||||
SetupScript: repository.SetupScript,
|
||||
CleanupScript: repository.CleanupScript,
|
||||
DevScript: repository.DevScript,
|
||||
|
||||
@@ -100,6 +100,7 @@ type CreateRepositoryRequest struct {
|
||||
ProviderName string
|
||||
DefaultBranch string
|
||||
WorktreeBranchPrefix string
|
||||
PullBeforeWorktree *bool
|
||||
SetupScript string
|
||||
CleanupScript string
|
||||
DevScript string
|
||||
@@ -116,6 +117,7 @@ type UpdateRepositoryRequest struct {
|
||||
ProviderName *string
|
||||
DefaultBranch *string
|
||||
WorktreeBranchPrefix *string
|
||||
PullBeforeWorktree *bool
|
||||
SetupScript *string
|
||||
CleanupScript *string
|
||||
DevScript *string
|
||||
|
||||
@@ -144,6 +144,7 @@ type httpCreateRepositoryRequest struct {
|
||||
ProviderName string `json:"provider_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
WorktreeBranchPrefix string `json:"worktree_branch_prefix"`
|
||||
PullBeforeWorktree *bool `json:"pull_before_worktree"`
|
||||
SetupScript string `json:"setup_script"`
|
||||
CleanupScript string `json:"cleanup_script"`
|
||||
DevScript string `json:"dev_script"`
|
||||
@@ -170,6 +171,7 @@ func (h *RepositoryHandlers) httpCreateRepository(c *gin.Context) {
|
||||
ProviderName: body.ProviderName,
|
||||
DefaultBranch: body.DefaultBranch,
|
||||
WorktreeBranchPrefix: body.WorktreeBranchPrefix,
|
||||
PullBeforeWorktree: body.PullBeforeWorktree,
|
||||
SetupScript: body.SetupScript,
|
||||
CleanupScript: body.CleanupScript,
|
||||
DevScript: body.DevScript,
|
||||
@@ -219,6 +221,7 @@ type httpUpdateRepositoryRequest struct {
|
||||
ProviderName *string `json:"provider_name"`
|
||||
DefaultBranch *string `json:"default_branch"`
|
||||
WorktreeBranchPrefix *string `json:"worktree_branch_prefix"`
|
||||
PullBeforeWorktree *bool `json:"pull_before_worktree"`
|
||||
SetupScript *string `json:"setup_script"`
|
||||
CleanupScript *string `json:"cleanup_script"`
|
||||
DevScript *string `json:"dev_script"`
|
||||
@@ -241,6 +244,7 @@ func (h *RepositoryHandlers) httpUpdateRepository(c *gin.Context) {
|
||||
ProviderName: body.ProviderName,
|
||||
DefaultBranch: body.DefaultBranch,
|
||||
WorktreeBranchPrefix: body.WorktreeBranchPrefix,
|
||||
PullBeforeWorktree: body.PullBeforeWorktree,
|
||||
SetupScript: body.SetupScript,
|
||||
CleanupScript: body.CleanupScript,
|
||||
DevScript: body.DevScript,
|
||||
|
||||
@@ -283,6 +283,7 @@ type Repository struct {
|
||||
ProviderName string `json:"provider_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
WorktreeBranchPrefix string `json:"worktree_branch_prefix"`
|
||||
PullBeforeWorktree bool `json:"pull_before_worktree"`
|
||||
SetupScript string `json:"setup_script"`
|
||||
CleanupScript string `json:"cleanup_script"`
|
||||
DevScript string `json:"dev_script"`
|
||||
|
||||
@@ -169,6 +169,7 @@ func (r *sqliteRepository) initSchema() error {
|
||||
provider_name TEXT DEFAULT '',
|
||||
default_branch TEXT DEFAULT '',
|
||||
worktree_branch_prefix TEXT DEFAULT 'feature/',
|
||||
pull_before_worktree INTEGER NOT NULL DEFAULT 1,
|
||||
setup_script TEXT DEFAULT '',
|
||||
cleanup_script TEXT DEFAULT '',
|
||||
dev_script TEXT DEFAULT '',
|
||||
@@ -315,6 +316,9 @@ func (r *sqliteRepository) initSchema() error {
|
||||
if err := r.ensureColumn("repositories", "worktree_branch_prefix", "TEXT DEFAULT 'feature/'"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.ensureColumn("repositories", "pull_before_worktree", "INTEGER NOT NULL DEFAULT 1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.ensureColumn("repositories", "dev_script", "TEXT DEFAULT ''"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1602,7 +1606,6 @@ func (r *sqliteRepository) UpdateMessage(ctx context.Context, message *models.Me
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Repository operations
|
||||
|
||||
// CreateRepository creates a new repository
|
||||
@@ -1617,11 +1620,11 @@ func (r *sqliteRepository) CreateRepository(ctx context.Context, repository *mod
|
||||
_, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO repositories (
|
||||
id, workspace_id, name, source_type, local_path, provider, provider_repo_id, provider_owner,
|
||||
provider_name, default_branch, worktree_branch_prefix, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
provider_name, default_branch, worktree_branch_prefix, pull_before_worktree, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, repository.ID, repository.WorkspaceID, repository.Name, repository.SourceType, repository.LocalPath, repository.Provider,
|
||||
repository.ProviderRepoID, repository.ProviderOwner, repository.ProviderName, repository.DefaultBranch, repository.WorktreeBranchPrefix,
|
||||
repository.SetupScript, repository.CleanupScript, repository.DevScript, repository.CreatedAt, repository.UpdatedAt, repository.DeletedAt)
|
||||
boolToInt(repository.PullBeforeWorktree), repository.SetupScript, repository.CleanupScript, repository.DevScript, repository.CreatedAt, repository.UpdatedAt, repository.DeletedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1632,12 +1635,12 @@ func (r *sqliteRepository) GetRepository(ctx context.Context, id string) (*model
|
||||
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT id, workspace_id, name, source_type, local_path, provider, provider_repo_id, provider_owner,
|
||||
provider_name, default_branch, worktree_branch_prefix, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
provider_name, default_branch, worktree_branch_prefix, pull_before_worktree, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
FROM repositories WHERE id = ? AND deleted_at IS NULL
|
||||
`, id).Scan(
|
||||
&repository.ID, &repository.WorkspaceID, &repository.Name, &repository.SourceType, &repository.LocalPath,
|
||||
&repository.Provider, &repository.ProviderRepoID, &repository.ProviderOwner, &repository.ProviderName,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.PullBeforeWorktree, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -1653,11 +1656,11 @@ func (r *sqliteRepository) UpdateRepository(ctx context.Context, repository *mod
|
||||
result, err := r.db.ExecContext(ctx, `
|
||||
UPDATE repositories SET
|
||||
name = ?, source_type = ?, local_path = ?, provider = ?, provider_repo_id = ?, provider_owner = ?,
|
||||
provider_name = ?, default_branch = ?, worktree_branch_prefix = ?, setup_script = ?, cleanup_script = ?, dev_script = ?, updated_at = ?
|
||||
provider_name = ?, default_branch = ?, worktree_branch_prefix = ?, pull_before_worktree = ?, setup_script = ?, cleanup_script = ?, dev_script = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at IS NULL
|
||||
`, repository.Name, repository.SourceType, repository.LocalPath, repository.Provider, repository.ProviderRepoID,
|
||||
repository.ProviderOwner, repository.ProviderName, repository.DefaultBranch, repository.WorktreeBranchPrefix, repository.SetupScript,
|
||||
repository.CleanupScript, repository.DevScript, repository.UpdatedAt, repository.ID)
|
||||
repository.ProviderOwner, repository.ProviderName, repository.DefaultBranch, repository.WorktreeBranchPrefix, boolToInt(repository.PullBeforeWorktree),
|
||||
repository.SetupScript, repository.CleanupScript, repository.DevScript, repository.UpdatedAt, repository.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1689,7 +1692,7 @@ func (r *sqliteRepository) DeleteRepository(ctx context.Context, id string) erro
|
||||
func (r *sqliteRepository) ListRepositories(ctx context.Context, workspaceID string) ([]*models.Repository, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT id, workspace_id, name, source_type, local_path, provider, provider_repo_id, provider_owner,
|
||||
provider_name, default_branch, worktree_branch_prefix, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
provider_name, default_branch, worktree_branch_prefix, pull_before_worktree, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
FROM repositories WHERE workspace_id = ? AND deleted_at IS NULL ORDER BY created_at DESC
|
||||
`, workspaceID)
|
||||
if err != nil {
|
||||
@@ -1703,7 +1706,7 @@ func (r *sqliteRepository) ListRepositories(ctx context.Context, workspaceID str
|
||||
err := rows.Scan(
|
||||
&repository.ID, &repository.WorkspaceID, &repository.Name, &repository.SourceType, &repository.LocalPath,
|
||||
&repository.Provider, &repository.ProviderRepoID, &repository.ProviderOwner, &repository.ProviderName,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.PullBeforeWorktree, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1797,7 +1800,6 @@ func (r *sqliteRepository) ListRepositoryScripts(ctx context.Context, repository
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
|
||||
// Executor operations
|
||||
|
||||
func (r *sqliteRepository) CreateExecutor(ctx context.Context, executor *models.Executor) error {
|
||||
|
||||
@@ -17,8 +17,6 @@ func NewWithDB(dbConn *sql.DB) (*Repository, error) {
|
||||
return newRepository(dbConn, false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func newRepository(dbConn *sql.DB, ownsDB bool) (*Repository, error) {
|
||||
repo := &Repository{db: dbConn, ownsDB: ownsDB}
|
||||
if err := repo.initSchema(); err != nil {
|
||||
@@ -101,8 +99,6 @@ func (r *Repository) ensureWorkspaceIndexes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
// initSchema creates the database tables if they don't exist
|
||||
func (r *Repository) initSchema() error {
|
||||
schema := `
|
||||
@@ -232,6 +228,7 @@ func (r *Repository) initSchema() error {
|
||||
provider_name TEXT DEFAULT '',
|
||||
default_branch TEXT DEFAULT '',
|
||||
worktree_branch_prefix TEXT DEFAULT 'feature/',
|
||||
pull_before_worktree INTEGER NOT NULL DEFAULT 1,
|
||||
setup_script TEXT DEFAULT '',
|
||||
cleanup_script TEXT DEFAULT '',
|
||||
dev_script TEXT DEFAULT '',
|
||||
@@ -433,6 +430,9 @@ func (r *Repository) initSchema() error {
|
||||
if err := r.ensureColumn("repositories", "worktree_branch_prefix", "TEXT DEFAULT 'feature/'"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.ensureColumn("repositories", "pull_before_worktree", "INTEGER NOT NULL DEFAULT 1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.ensureColumn("repositories", "dev_script", "TEXT DEFAULT ''"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ func (r *Repository) CreateRepository(ctx context.Context, repository *models.Re
|
||||
_, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO repositories (
|
||||
id, workspace_id, name, source_type, local_path, provider, provider_repo_id, provider_owner,
|
||||
provider_name, default_branch, worktree_branch_prefix, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
provider_name, default_branch, worktree_branch_prefix, pull_before_worktree, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, repository.ID, repository.WorkspaceID, repository.Name, repository.SourceType, repository.LocalPath, repository.Provider,
|
||||
repository.ProviderRepoID, repository.ProviderOwner, repository.ProviderName, repository.DefaultBranch, repository.WorktreeBranchPrefix,
|
||||
repository.SetupScript, repository.CleanupScript, repository.DevScript, repository.CreatedAt, repository.UpdatedAt, repository.DeletedAt)
|
||||
boolToInt(repository.PullBeforeWorktree), repository.SetupScript, repository.CleanupScript, repository.DevScript, repository.CreatedAt, repository.UpdatedAt, repository.DeletedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -38,12 +38,12 @@ func (r *Repository) GetRepository(ctx context.Context, id string) (*models.Repo
|
||||
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT id, workspace_id, name, source_type, local_path, provider, provider_repo_id, provider_owner,
|
||||
provider_name, default_branch, worktree_branch_prefix, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
provider_name, default_branch, worktree_branch_prefix, pull_before_worktree, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
FROM repositories WHERE id = ? AND deleted_at IS NULL
|
||||
`, id).Scan(
|
||||
&repository.ID, &repository.WorkspaceID, &repository.Name, &repository.SourceType, &repository.LocalPath,
|
||||
&repository.Provider, &repository.ProviderRepoID, &repository.ProviderOwner, &repository.ProviderName,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.PullBeforeWorktree, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -59,11 +59,11 @@ func (r *Repository) UpdateRepository(ctx context.Context, repository *models.Re
|
||||
result, err := r.db.ExecContext(ctx, `
|
||||
UPDATE repositories SET
|
||||
name = ?, source_type = ?, local_path = ?, provider = ?, provider_repo_id = ?, provider_owner = ?,
|
||||
provider_name = ?, default_branch = ?, worktree_branch_prefix = ?, setup_script = ?, cleanup_script = ?, dev_script = ?, updated_at = ?
|
||||
provider_name = ?, default_branch = ?, worktree_branch_prefix = ?, pull_before_worktree = ?, setup_script = ?, cleanup_script = ?, dev_script = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at IS NULL
|
||||
`, repository.Name, repository.SourceType, repository.LocalPath, repository.Provider, repository.ProviderRepoID,
|
||||
repository.ProviderOwner, repository.ProviderName, repository.DefaultBranch, repository.WorktreeBranchPrefix, repository.SetupScript,
|
||||
repository.CleanupScript, repository.DevScript, repository.UpdatedAt, repository.ID)
|
||||
repository.ProviderOwner, repository.ProviderName, repository.DefaultBranch, repository.WorktreeBranchPrefix, boolToInt(repository.PullBeforeWorktree),
|
||||
repository.SetupScript, repository.CleanupScript, repository.DevScript, repository.UpdatedAt, repository.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (r *Repository) DeleteRepository(ctx context.Context, id string) error {
|
||||
func (r *Repository) ListRepositories(ctx context.Context, workspaceID string) ([]*models.Repository, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT id, workspace_id, name, source_type, local_path, provider, provider_repo_id, provider_owner,
|
||||
provider_name, default_branch, worktree_branch_prefix, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
provider_name, default_branch, worktree_branch_prefix, pull_before_worktree, setup_script, cleanup_script, dev_script, created_at, updated_at, deleted_at
|
||||
FROM repositories WHERE workspace_id = ? AND deleted_at IS NULL ORDER BY created_at DESC
|
||||
`, workspaceID)
|
||||
if err != nil {
|
||||
@@ -109,7 +109,7 @@ func (r *Repository) ListRepositories(ctx context.Context, workspaceID string) (
|
||||
err := rows.Scan(
|
||||
&repository.ID, &repository.WorkspaceID, &repository.Name, &repository.SourceType, &repository.LocalPath,
|
||||
&repository.Provider, &repository.ProviderRepoID, &repository.ProviderOwner, &repository.ProviderName,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
&repository.DefaultBranch, &repository.WorktreeBranchPrefix, &repository.PullBeforeWorktree, &repository.SetupScript, &repository.CleanupScript, &repository.DevScript, &repository.CreatedAt, &repository.UpdatedAt, &repository.DeletedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -136,8 +136,6 @@ func (r *Repository) CreateRepositoryScript(ctx context.Context, script *models.
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GetRepositoryScript retrieves a repository script by ID
|
||||
func (r *Repository) GetRepositoryScript(ctx context.Context, id string) (*models.RepositoryScript, error) {
|
||||
script := &models.RepositoryScript{}
|
||||
|
||||
@@ -199,6 +199,7 @@ type CreateRepositoryRequest struct {
|
||||
ProviderName string `json:"provider_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
WorktreeBranchPrefix string `json:"worktree_branch_prefix"`
|
||||
PullBeforeWorktree *bool `json:"pull_before_worktree"`
|
||||
SetupScript string `json:"setup_script"`
|
||||
CleanupScript string `json:"cleanup_script"`
|
||||
DevScript string `json:"dev_script"`
|
||||
@@ -215,6 +216,7 @@ type UpdateRepositoryRequest struct {
|
||||
ProviderName *string `json:"provider_name,omitempty"`
|
||||
DefaultBranch *string `json:"default_branch,omitempty"`
|
||||
WorktreeBranchPrefix *string `json:"worktree_branch_prefix,omitempty"`
|
||||
PullBeforeWorktree *bool `json:"pull_before_worktree,omitempty"`
|
||||
SetupScript *string `json:"setup_script,omitempty"`
|
||||
CleanupScript *string `json:"cleanup_script,omitempty"`
|
||||
DevScript *string `json:"dev_script,omitempty"`
|
||||
@@ -1032,6 +1034,10 @@ func (s *Service) CreateRepository(ctx context.Context, req *CreateRepositoryReq
|
||||
if prefix == "" {
|
||||
prefix = worktree.DefaultBranchPrefix
|
||||
}
|
||||
pullBeforeWorktree := true
|
||||
if req.PullBeforeWorktree != nil {
|
||||
pullBeforeWorktree = *req.PullBeforeWorktree
|
||||
}
|
||||
repository := &models.Repository{
|
||||
ID: uuid.New().String(),
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
@@ -1044,6 +1050,7 @@ func (s *Service) CreateRepository(ctx context.Context, req *CreateRepositoryReq
|
||||
ProviderName: req.ProviderName,
|
||||
DefaultBranch: req.DefaultBranch,
|
||||
WorktreeBranchPrefix: prefix,
|
||||
PullBeforeWorktree: pullBeforeWorktree,
|
||||
SetupScript: req.SetupScript,
|
||||
CleanupScript: req.CleanupScript,
|
||||
DevScript: req.DevScript,
|
||||
@@ -1099,6 +1106,9 @@ func (s *Service) UpdateRepository(ctx context.Context, id string, req *UpdateRe
|
||||
}
|
||||
repository.WorktreeBranchPrefix = prefix
|
||||
}
|
||||
if req.PullBeforeWorktree != nil {
|
||||
repository.PullBeforeWorktree = *req.PullBeforeWorktree
|
||||
}
|
||||
if req.SetupScript != nil {
|
||||
repository.SetupScript = *req.SetupScript
|
||||
}
|
||||
@@ -2108,6 +2118,7 @@ func (s *Service) publishRepositoryEvent(ctx context.Context, eventType string,
|
||||
"provider_name": repository.ProviderName,
|
||||
"default_branch": repository.DefaultBranch,
|
||||
"worktree_branch_prefix": repository.WorktreeBranchPrefix,
|
||||
"pull_before_worktree": repository.PullBeforeWorktree,
|
||||
"setup_script": repository.SetupScript,
|
||||
"cleanup_script": repository.CleanupScript,
|
||||
"created_at": repository.CreatedAt.Format(time.RFC3339),
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kandev/kandev/internal/worktree"
|
||||
"github.com/kandev/kandev/internal/common/logger"
|
||||
"github.com/kandev/kandev/internal/db"
|
||||
"github.com/kandev/kandev/internal/events/bus"
|
||||
"github.com/kandev/kandev/internal/task/models"
|
||||
"github.com/kandev/kandev/internal/task/repository"
|
||||
"github.com/kandev/kandev/internal/worktree"
|
||||
v1 "github.com/kandev/kandev/pkg/api/v1"
|
||||
)
|
||||
|
||||
@@ -170,6 +170,9 @@ func TestService_CreateRepository_DefaultWorktreeBranchPrefix(t *testing.T) {
|
||||
if created.WorktreeBranchPrefix != worktree.DefaultBranchPrefix {
|
||||
t.Fatalf("expected default prefix %q, got %q", worktree.DefaultBranchPrefix, created.WorktreeBranchPrefix)
|
||||
}
|
||||
if !created.PullBeforeWorktree {
|
||||
t.Fatalf("expected pull_before_worktree to default to true")
|
||||
}
|
||||
|
||||
stored, err := repo.GetRepository(ctx, created.ID)
|
||||
if err != nil {
|
||||
@@ -178,6 +181,37 @@ func TestService_CreateRepository_DefaultWorktreeBranchPrefix(t *testing.T) {
|
||||
if stored.WorktreeBranchPrefix != worktree.DefaultBranchPrefix {
|
||||
t.Fatalf("expected stored prefix %q, got %q", worktree.DefaultBranchPrefix, stored.WorktreeBranchPrefix)
|
||||
}
|
||||
if !stored.PullBeforeWorktree {
|
||||
t.Fatalf("expected stored pull_before_worktree to default to true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_CreateRepository_PullBeforeWorktreeFalse(t *testing.T) {
|
||||
svc, _, repo := createTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_ = repo.CreateWorkspace(ctx, &models.Workspace{ID: "ws-1", Name: "Workspace"})
|
||||
|
||||
pullFalse := false
|
||||
created, err := svc.CreateRepository(ctx, &CreateRepositoryRequest{
|
||||
WorkspaceID: "ws-1",
|
||||
Name: "Test Repo",
|
||||
PullBeforeWorktree: &pullFalse,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateRepository failed: %v", err)
|
||||
}
|
||||
if created.PullBeforeWorktree {
|
||||
t.Fatalf("expected pull_before_worktree to be false when explicitly set")
|
||||
}
|
||||
|
||||
stored, err := repo.GetRepository(ctx, created.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRepository failed: %v", err)
|
||||
}
|
||||
if stored.PullBeforeWorktree {
|
||||
t.Fatalf("expected stored pull_before_worktree to be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_GetTask(t *testing.T) {
|
||||
|
||||
@@ -175,11 +175,6 @@ func (m *Manager) Create(ctx context.Context, req CreateRequest) (*Worktree, err
|
||||
return nil, ErrRepoNotGit
|
||||
}
|
||||
|
||||
// Check base branch exists
|
||||
if !m.branchExists(req.RepositoryPath, req.BaseBranch) {
|
||||
return nil, fmt.Errorf("%w: %s", ErrInvalidBaseBranch, req.BaseBranch)
|
||||
}
|
||||
|
||||
// Check worktree limit
|
||||
count, err := m.countWorktreesForRepo(ctx, req.RepositoryID)
|
||||
if err != nil {
|
||||
@@ -194,11 +189,21 @@ func (m *Manager) Create(ctx context.Context, req CreateRequest) (*Worktree, err
|
||||
repoLock.Lock()
|
||||
defer repoLock.Unlock()
|
||||
|
||||
return m.createWorktree(ctx, req)
|
||||
baseRef := req.BaseBranch
|
||||
if req.PullBeforeWorktree {
|
||||
baseRef = m.pullBaseBranch(req.RepositoryPath, req.BaseBranch)
|
||||
}
|
||||
|
||||
// Check base branch exists
|
||||
if !m.branchExists(req.RepositoryPath, baseRef) {
|
||||
return nil, fmt.Errorf("%w: %s", ErrInvalidBaseBranch, baseRef)
|
||||
}
|
||||
|
||||
return m.createWorktree(ctx, req, baseRef)
|
||||
}
|
||||
|
||||
// createWorktree performs the actual git worktree creation.
|
||||
func (m *Manager) createWorktree(ctx context.Context, req CreateRequest) (*Worktree, error) {
|
||||
func (m *Manager) createWorktree(ctx context.Context, req CreateRequest, baseRef string) (*Worktree, error) {
|
||||
// Generate a unique worktree ID and random suffix
|
||||
worktreeID := uuid.New().String()
|
||||
dirSuffix := worktreeID[:8] // Use first 8 chars of UUID for worktree dir uniqueness
|
||||
@@ -233,7 +238,7 @@ func (m *Manager) createWorktree(ctx context.Context, req CreateRequest) (*Workt
|
||||
cmd := exec.CommandContext(ctx, "git", "worktree", "add",
|
||||
"-b", branchName,
|
||||
worktreePath,
|
||||
req.BaseBranch)
|
||||
baseRef)
|
||||
cmd.Dir = req.RepositoryPath
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
@@ -640,6 +645,77 @@ func (m *Manager) branchExists(repoPath, branch string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *Manager) currentBranch(repoPath string) string {
|
||||
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
cmd.Dir = repoPath
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
// pullBaseBranch fetches the latest changes from origin and returns the best ref to use
|
||||
// for creating a new worktree. The function handles three scenarios:
|
||||
//
|
||||
// 1. baseBranch is already a remote ref (e.g., "origin/main"): fetch and use it directly
|
||||
// 2. baseBranch is a local branch and we're currently on it: pull --ff-only to update
|
||||
// 3. baseBranch is a local branch but we're on a different branch: use origin/<branch> instead
|
||||
//
|
||||
// On fetch/pull failure, errors are logged but the function continues with the best available ref.
|
||||
func (m *Manager) pullBaseBranch(repoPath, baseBranch string) string {
|
||||
// Normalize branch name - strip "origin/" prefix if present
|
||||
localBranch := strings.TrimPrefix(baseBranch, "origin/")
|
||||
isRemoteRef := localBranch != baseBranch
|
||||
|
||||
// Fetch the branch from origin (30s timeout for fetch + potential pull)
|
||||
fetchCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
fetchArgs := []string{"fetch", "origin"}
|
||||
if localBranch != "" {
|
||||
fetchArgs = append(fetchArgs, localBranch)
|
||||
}
|
||||
fetchCmd := exec.CommandContext(fetchCtx, "git", fetchArgs...)
|
||||
fetchCmd.Dir = repoPath
|
||||
if output, err := fetchCmd.CombinedOutput(); err != nil {
|
||||
m.logger.Error("git fetch failed before worktree creation",
|
||||
zap.String("branch", baseBranch),
|
||||
zap.String("output", string(output)),
|
||||
zap.Error(err))
|
||||
return baseBranch
|
||||
}
|
||||
|
||||
// If the original ref was already a remote ref, use it directly
|
||||
if isRemoteRef {
|
||||
return "origin/" + localBranch
|
||||
}
|
||||
|
||||
// For local branches: try to update if we're on that branch, otherwise use origin/<branch>
|
||||
remoteRef := "origin/" + localBranch
|
||||
currentBranch := m.currentBranch(repoPath)
|
||||
|
||||
if currentBranch == baseBranch {
|
||||
// We're on the target branch - try to pull (fast-forward only)
|
||||
pullCmd := exec.CommandContext(fetchCtx, "git", "pull", "--ff-only", "origin", baseBranch)
|
||||
pullCmd.Dir = repoPath
|
||||
if output, err := pullCmd.CombinedOutput(); err != nil {
|
||||
m.logger.Error("git pull failed before worktree creation",
|
||||
zap.String("branch", baseBranch),
|
||||
zap.String("output", string(output)),
|
||||
zap.Error(err))
|
||||
}
|
||||
return baseBranch
|
||||
}
|
||||
|
||||
// Not on the target branch - use the remote ref if it exists
|
||||
if m.branchExists(repoPath, remoteRef) {
|
||||
return remoteRef
|
||||
}
|
||||
|
||||
return baseBranch
|
||||
}
|
||||
|
||||
// countWorktreesForRepo counts active worktrees for a repository.
|
||||
func (m *Manager) countWorktreesForRepo(ctx context.Context, repoID string) (int, error) {
|
||||
if m.store == nil {
|
||||
|
||||
@@ -74,6 +74,9 @@ type CreateRequest struct {
|
||||
// If empty, the default prefix is used.
|
||||
WorktreeBranchPrefix string
|
||||
|
||||
// PullBeforeWorktree indicates whether to pull from remote before creating the worktree.
|
||||
PullBeforeWorktree bool
|
||||
|
||||
// WorktreeID is the ID of an existing worktree to reuse (optional).
|
||||
// If provided and valid, the existing worktree is returned instead of creating a new one.
|
||||
WorktreeID string
|
||||
|
||||
@@ -51,6 +51,7 @@ export type Repository = {
|
||||
provider_name: string;
|
||||
default_branch: string;
|
||||
worktree_branch_prefix: string;
|
||||
pull_before_worktree: boolean;
|
||||
setup_script: string;
|
||||
cleanup_script: string;
|
||||
created_at: string;
|
||||
|
||||
@@ -184,6 +184,7 @@ export async function createRepositoryAction(payload: {
|
||||
provider_name: string;
|
||||
default_branch: string;
|
||||
worktree_branch_prefix: string;
|
||||
pull_before_worktree: boolean;
|
||||
setup_script: string;
|
||||
cleanup_script: string;
|
||||
dev_script: string;
|
||||
@@ -200,6 +201,7 @@ export async function createRepositoryAction(payload: {
|
||||
provider_name: payload.provider_name,
|
||||
default_branch: payload.default_branch,
|
||||
worktree_branch_prefix: payload.worktree_branch_prefix,
|
||||
pull_before_worktree: payload.pull_before_worktree,
|
||||
setup_script: payload.setup_script,
|
||||
cleanup_script: payload.cleanup_script,
|
||||
dev_script: payload.dev_script,
|
||||
|
||||
@@ -97,6 +97,7 @@ export function WorkspaceRepositoriesClient({
|
||||
repo.provider_name !== saved.provider_name ||
|
||||
repo.default_branch !== saved.default_branch ||
|
||||
repo.worktree_branch_prefix !== saved.worktree_branch_prefix ||
|
||||
repo.pull_before_worktree !== saved.pull_before_worktree ||
|
||||
repo.setup_script !== saved.setup_script ||
|
||||
repo.cleanup_script !== saved.cleanup_script ||
|
||||
repo.dev_script !== saved.dev_script
|
||||
@@ -199,6 +200,7 @@ export function WorkspaceRepositoriesClient({
|
||||
provider_name: '',
|
||||
default_branch: selectedRepo?.default_branch || '',
|
||||
worktree_branch_prefix: 'feature/',
|
||||
pull_before_worktree: true,
|
||||
setup_script: '',
|
||||
cleanup_script: '',
|
||||
dev_script: '',
|
||||
@@ -272,6 +274,7 @@ export function WorkspaceRepositoriesClient({
|
||||
provider_name: repo.provider_name,
|
||||
default_branch: repo.default_branch,
|
||||
worktree_branch_prefix: repo.worktree_branch_prefix,
|
||||
pull_before_worktree: repo.pull_before_worktree,
|
||||
setup_script: repo.setup_script,
|
||||
cleanup_script: repo.cleanup_script,
|
||||
dev_script: repo.dev_script,
|
||||
@@ -302,6 +305,7 @@ export function WorkspaceRepositoriesClient({
|
||||
provider_name: repo.provider_name,
|
||||
default_branch: repo.default_branch,
|
||||
worktree_branch_prefix: repo.worktree_branch_prefix,
|
||||
pull_before_worktree: repo.pull_before_worktree,
|
||||
setup_script: repo.setup_script,
|
||||
cleanup_script: repo.cleanup_script,
|
||||
dev_script: repo.dev_script,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { IconEdit, IconGitBranch, IconPlus, IconTrash, IconX } from '@tabler/ico
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Checkbox } from '@kandev/ui/checkbox';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Textarea } from '@kandev/ui/textarea';
|
||||
@@ -57,6 +58,7 @@ export function RepositoryCard({
|
||||
const repositoryName = repository.name ?? '';
|
||||
const repositoryLocalPath = repository.local_path ?? '';
|
||||
const worktreeBranchPrefix = repository.worktree_branch_prefix ?? '';
|
||||
const pullBeforeWorktree = repository.pull_before_worktree ?? true;
|
||||
const setupScript = repository.setup_script ?? '';
|
||||
const cleanupScript = repository.cleanup_script ?? '';
|
||||
const devScript = repository.dev_script ?? '';
|
||||
@@ -167,6 +169,29 @@ export function RepositoryCard({
|
||||
Branches are generated as {'{prefix}{sanitized-title}-{rand}'}.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`repo-pull-before-${repository.id}`}>Worktree Sync</Label>
|
||||
<div className="flex items-start gap-2 pt-2">
|
||||
<Checkbox
|
||||
id={`repo-pull-before-${repository.id}`}
|
||||
checked={pullBeforeWorktree}
|
||||
onCheckedChange={(checked) =>
|
||||
onUpdate(repository.id, { pull_before_worktree: checked === true })
|
||||
}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<Label
|
||||
htmlFor={`repo-pull-before-${repository.id}`}
|
||||
className="text-sm text-muted-foreground cursor-pointer"
|
||||
>
|
||||
Always pull before creating a new worktree
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Keeps the base branch up to date with the remote before spawning new sessions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
|
||||
@@ -63,6 +63,7 @@ export type Repository = {
|
||||
provider_name: string;
|
||||
default_branch: string;
|
||||
worktree_branch_prefix: string;
|
||||
pull_before_worktree: boolean;
|
||||
setup_script: string;
|
||||
cleanup_script: string;
|
||||
dev_script: string;
|
||||
|
||||
Reference in New Issue
Block a user