package git import ( "bytes" "context" "fmt" "os/exec" "time" ) const worktreeTimeout = 30 * time.Second // CreateWorktreeOpts holds the parameters for CreateWorktree. type CreateWorktreeOpts struct { WorkspaceRoot string WorktreePath string Branch string } // RemoveWorktreeOpts holds the parameters for RemoveWorktree. type RemoveWorktreeOpts struct { WorkspaceRoot string WorktreePath string // Force maps to git worktree remove --force. Use when the worktree may // have uncommitted changes that should be discarded. Force bool } // CreateWorktree runs: // // git -C worktree add -b // // A non-zero exit wraps stderr in the returned error. func CreateWorktree(ctx context.Context, opts CreateWorktreeOpts) error { ctx, cancel := context.WithTimeout(ctx, worktreeTimeout) defer cancel() args := []string{"-C", opts.WorkspaceRoot, "worktree", "add", opts.WorktreePath, "-b", opts.Branch} var stderr bytes.Buffer cmd := exec.CommandContext(ctx, "git", args...) cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return fmt.Errorf("git worktree add failed: %w; stderr: %s", err, stderr.String()) } return nil } // RemoveWorktree runs: // // git -C worktree remove [--force] // // A non-zero exit wraps stderr in the returned error. func RemoveWorktree(ctx context.Context, opts RemoveWorktreeOpts) error { ctx, cancel := context.WithTimeout(ctx, worktreeTimeout) defer cancel() args := []string{"-C", opts.WorkspaceRoot, "worktree", "remove"} if opts.Force { args = append(args, "--force") } args = append(args, opts.WorktreePath) var stderr bytes.Buffer cmd := exec.CommandContext(ctx, "git", args...) cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return fmt.Errorf("git worktree remove failed: %w; stderr: %s", err, stderr.String()) } return nil }