Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func Init(ctx context.Context, cfg *utils.AppConfig, assets embed.FS, info *AppI

registerHealthRoutes(app, humaAPI)
registerProjectRoutes(v1)
registerWorktreeRoutes(v1)
registerWorktreeRoutes(v1, cfg)
registerBranchRoutes(v1)
registerTaskRoutes(v1)
registerNotePadRoutes(v1)
Expand Down
95 changes: 75 additions & 20 deletions api/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"errors"
"net/http"
"path/filepath"
"strings"

"github.com/danielgtaylor/huma/v2"

Expand Down Expand Up @@ -159,11 +161,12 @@ func registerSystemRoutes(group *huma.Group, cfg *utils.AppConfig, terminalManag
huma.Post(group, "/system/ai-assistant-status/update", func(ctx context.Context, input *struct {
Body utils.AIAssistantStatusConfig `json:"body"`
}) (*h.MessageResponse, error) {
// 更新内存中的配置
cfg.Terminal.AIAssistantStatus = input.Body

// 写回配置文件
utils.WriteConfig(cfg)
// 原子更新:在锁内完成修改+写盘
if err := utils.UpdateConfig(cfg, func(c *utils.AppConfig) {
c.Terminal.AIAssistantStatus = input.Body
}); err != nil {
return nil, huma.Error500InternalServerError("failed to save configuration")
}

// 热重载:更新所有现有终端的配置
if terminalManager != nil {
Expand Down Expand Up @@ -194,8 +197,12 @@ func registerSystemRoutes(group *huma.Group, cfg *utils.AppConfig, terminalManag
huma.Post(group, "/system/developer-config/update", func(ctx context.Context, input *struct {
Body utils.DeveloperConfig `json:"body"`
}) (*h.MessageResponse, error) {
cfg.Developer = input.Body
utils.WriteConfig(cfg)
// 原子更新:在锁内完成修改+写盘
if err := utils.UpdateConfig(cfg, func(c *utils.AppConfig) {
c.Developer = input.Body
}); err != nil {
return nil, huma.Error500InternalServerError("failed to save configuration")
}

if terminalManager != nil {
terminalManager.UpdateScrollbackEnabled(input.Body.EnableTerminalScrollback)
Expand Down Expand Up @@ -231,25 +238,29 @@ func registerSystemRoutes(group *huma.Group, cfg *utils.AppConfig, terminalManag
Shell string `json:"shell" doc:"Shell命令,空值表示使用自动选择"`
} `json:"body"`
}) (*h.MessageResponse, error) {
// Validate the shell command if provided
// 验证 Shell 命令有效性
if err := utils.ValidateShellCommand(input.Body.Shell); err != nil {
return nil, huma.Error400BadRequest("Invalid shell command: " + err.Error())
}

// Update config based on current platform
switch utils.GetAvailableShells(cfg.Terminal.Shell).Platform {
case "windows":
cfg.Terminal.Shell.Windows = input.Body.Shell
case "darwin":
cfg.Terminal.Shell.Darwin = input.Body.Shell
default:
cfg.Terminal.Shell.Linux = input.Body.Shell
// 获取当前平台以便更新对应配置
platform := utils.GetAvailableShells(cfg.Terminal.Shell).Platform

// 原子更新:在锁内完成修改+写盘
if err := utils.UpdateConfig(cfg, func(c *utils.AppConfig) {
switch platform {
case "windows":
c.Terminal.Shell.Windows = input.Body.Shell
case "darwin":
c.Terminal.Shell.Darwin = input.Body.Shell
default:
c.Terminal.Shell.Linux = input.Body.Shell
}
}); err != nil {
return nil, huma.Error500InternalServerError("failed to save configuration")
}

// Persist to config file
utils.WriteConfig(cfg)

// Hot-reload: update terminal manager's shell config for new sessions
// 热重载:更新终端管理器的 Shell 配置,新会话生效
if terminalManager != nil {
terminalManager.UpdateShellConfig(cfg.Terminal.Shell)
}
Expand Down Expand Up @@ -295,6 +306,50 @@ func registerSystemRoutes(group *huma.Group, cfg *utils.AppConfig, terminalManag
op.Description = "检查指定的Shell命令是否有效可用"
op.Tags = []string{systemTag}
})

huma.Get(group, "/system/worktree-settings", func(ctx context.Context, input *struct{}) (*h.ItemResponse[utils.WorktreeConfig], error) {
resp := h.NewItemResponse(cfg.Worktree)
resp.Status = http.StatusOK
return resp, nil
}, func(op *huma.Operation) {
op.OperationID = "system-worktree-settings-get"
op.Summary = "获取 Worktree 全局设置"
op.Tags = []string{systemTag}
})

huma.Post(group, "/system/worktree-settings/update", func(ctx context.Context, input *struct {
Body utils.WorktreeConfig `json:"body"`
}) (*h.ItemResponse[utils.WorktreeConfig], error) {
globalBaseDir := strings.TrimSpace(input.Body.GlobalBaseDir)
pattern := strings.TrimSpace(input.Body.GlobalDirNamePattern)
if globalBaseDir != "" && !filepath.IsAbs(globalBaseDir) {
return nil, huma.Error400BadRequest("globalBaseDir must be an absolute path")
}
if pattern == "" {
return nil, huma.Error400BadRequest("globalDirNamePattern is required")
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API endpoint for updating worktree settings validates that the pattern is not empty, but doesn't validate the actual content of the pattern. Consider adding validation to check for invalid patterns (e.g., patterns containing path separators "/" or "\") to provide clearer error messages to users before attempting to use the pattern.

Suggested change
}
}
// Disallow path separators in the directory name pattern to avoid unintended paths.
if strings.ContainsAny(pattern, "/\\") {
return nil, huma.Error400BadRequest("globalDirNamePattern cannot contain path separators ('/' or '\\')")
}

Copilot uses AI. Check for mistakes.

// 安全检查:全局基础目录不能是敏感系统目录
if globalBaseDir != "" && utils.IsSensitiveSystemDir(globalBaseDir) {
return nil, huma.Error400BadRequest("globalBaseDir cannot be a system directory")
}

// 原子更新:在锁内完成修改+写盘
if err := utils.UpdateConfig(cfg, func(c *utils.AppConfig) {
c.Worktree.GlobalBaseDir = globalBaseDir
c.Worktree.GlobalDirNamePattern = pattern
}); err != nil {
return nil, huma.Error500InternalServerError("failed to save configuration")
}

resp := h.NewItemResponse(cfg.Worktree)
resp.Status = http.StatusOK
return resp, nil
}, func(op *huma.Operation) {
op.OperationID = "system-worktree-settings-update"
op.Summary = "更新 Worktree 全局设置"
op.Tags = []string{systemTag}
})
}

func mapSystemError(err error) error {
Expand Down
20 changes: 14 additions & 6 deletions api/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ const worktreeTag = "worktree-工作树"

type createWorktreeInput struct {
Body struct {
BranchName string `json:"branchName" doc:"分支名称" required:"true"`
BaseBranch string `json:"baseBranch" doc:"基础分支" default:""`
CreateBranch bool `json:"createBranch" doc:"是否创建新分支" default:"true"`
BranchName string `json:"branchName" doc:"分支名称" required:"true"`
BaseBranch string `json:"baseBranch" doc:"基础分支" default:""`
CreateBranch bool `json:"createBranch" doc:"是否创建新分支" default:"true"`
Location string `json:"location,omitempty" doc:"创建位置(project/global),为空表示使用项目默认"`
GlobalBaseDirOverride string `json:"globalBaseDirOverride,omitempty" doc:"全局 Worktree 基础目录(仅本次创建,优先级高于全局配置)"`
} `json:"body"`
}

Expand All @@ -30,7 +32,7 @@ type commitWorktreeInput struct {
} `json:"body"`
}

func registerWorktreeRoutes(group *huma.Group) {
func registerWorktreeRoutes(group *huma.Group, cfg *utils.AppConfig) {
worktreeSvc := service.NewWorktreeService()

huma.Post(group, "/projects/{projectId}/worktrees/create", func(
Expand All @@ -44,8 +46,14 @@ func registerWorktreeRoutes(group *huma.Group) {
ctx,
input.ProjectID,
input.Body.BranchName,
input.Body.BaseBranch,
input.Body.CreateBranch,
service.CreateWorktreeOptions{
BaseBranch: input.Body.BaseBranch,
CreateBranch: input.Body.CreateBranch,
Location: input.Body.Location,
GlobalBaseDirOverride: input.Body.GlobalBaseDirOverride,
GlobalBaseDir: cfg.Worktree.GlobalBaseDir,
GlobalDirNamePattern: cfg.Worktree.GlobalDirNamePattern,
},
)
if err != nil {
return nil, mapWorktreeError(err)
Expand Down
126 changes: 68 additions & 58 deletions model/db_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions model/project.sql_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions model/queries/project.sql
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ WHERE id = @id
AND deleted_at IS NULL
RETURNING *;

-- name: ProjectUpdateWorktreeBasePath :one
UPDATE projects
SET
updated_at = @updated_at,
worktree_base_path = CAST(@worktree_base_path AS TEXT)
WHERE id = @id
AND deleted_at IS NULL
RETURNING *;

-- name: ProjectSoftDelete :execrows
UPDATE projects
SET
Expand Down
Loading
Loading