diff --git a/cnb-builder-shim/internal/devsandbox/vcs/types.go b/cnb-builder-shim/internal/devsandbox/vcs/types.go index b4b0127c3..3c005083a 100644 --- a/cnb-builder-shim/internal/devsandbox/vcs/types.go +++ b/cnb-builder-shim/internal/devsandbox/vcs/types.go @@ -20,6 +20,7 @@ package vcs import ( + "path" "slices" "strings" ) @@ -49,15 +50,8 @@ type File struct { // Files 文件列表 type Files []File -// DirTree 目录树 -type DirTree struct { - Name string `json:"name"` - Dirs []*DirTree `json:"dirs"` - Files Files `json:"files"` -} - // AsTree 转换成目录树形式 -func (files Files) AsTree() DirTree { +func (files Files) AsTree() *DirTree { // 按照文件路径排序 slices.SortFunc(files, func(a, b File) int { return strings.Compare(a.Path, b.Path) @@ -90,5 +84,32 @@ func (files Files) AsTree() DirTree { f.Path = parts[len(parts)-1] cur.Files = append(cur.Files, f) } - return root + + // 压缩目录树,避免过多的嵌套层级 + return root.Compress() +} + +// DirTree 目录树 +type DirTree struct { + Name string `json:"name"` + Dirs []*DirTree `json:"dirs"` + Files Files `json:"files"` +} + +// Compress 压缩 +func (t *DirTree) Compress() *DirTree { + // 根目录不需要压缩,只有子目录需要 + for idx, dt := range t.Dirs { + t.Dirs[idx] = dt.compress(dt.Name) + } + return t +} + +func (t *DirTree) compress(prefix string) *DirTree { + if len(t.Files) == 0 && len(t.Dirs) == 1 { + subDir := t.Dirs[0] + subDir.Name = path.Join(prefix, subDir.Name) + return subDir.compress(subDir.Name) + } + return t } diff --git a/cnb-builder-shim/internal/devsandbox/vcs/types_test.go b/cnb-builder-shim/internal/devsandbox/vcs/types_test.go index 2701a1d8f..9733d4053 100644 --- a/cnb-builder-shim/internal/devsandbox/vcs/types_test.go +++ b/cnb-builder-shim/internal/devsandbox/vcs/types_test.go @@ -38,7 +38,7 @@ var _ = Describe("Test Types", func() { {Action: FileActionModified, Path: "docs/example.txt"}, } - excepted := DirTree{ + excepted := &DirTree{ Name: "/", Dirs: []*DirTree{ { @@ -79,5 +79,69 @@ var _ = Describe("Test Types", func() { } Expect(files.AsTree()).To(Equal(excepted)) }) + + It("AsTree with compress", func() { + files := Files{ + {Action: FileActionAdded, Path: "webfe/static/example.css"}, + {Action: FileActionAdded, Path: "webfe/static/example.js"}, + {Action: FileActionDeleted, Path: "api/main.py"}, + {Action: FileActionDeleted, Path: "api/utils/stringx.py"}, + {Action: FileActionModified, Path: "backend/cmd/main.go"}, + {Action: FileActionModified, Path: "backend/pkg/types.go"}, + {Action: FileActionModified, Path: "docs/common/example.txt"}, + {Action: FileActionDeleted, Path: "mako/templates/mako/about_us.mako"}, + } + + excepted := &DirTree{ + Name: "/", + Dirs: []*DirTree{ + { + Name: "api", + Dirs: []*DirTree{ + { + Name: "utils", Files: Files{ + {Action: FileActionDeleted, Path: "stringx.py"}, + }, + }, + }, + Files: Files{ + {Action: FileActionDeleted, Path: "main.py"}, + }, + }, + { + Name: "backend", Dirs: []*DirTree{ + { + Name: "cmd", Files: Files{ + {Action: FileActionModified, Path: "main.go"}, + }, + }, + { + Name: "pkg", Files: Files{ + {Action: FileActionModified, Path: "types.go"}, + }, + }, + }, + }, + { + Name: "docs/common", Files: Files{ + {Action: FileActionModified, Path: "example.txt"}, + }, + }, + { + Name: "mako/templates/mako", + Files: Files{ + {Action: FileActionDeleted, Path: "about_us.mako"}, + }, + }, + { + Name: "webfe/static", Files: Files{ + {Action: FileActionAdded, Path: "example.css"}, + {Action: FileActionAdded, Path: "example.js"}, + }, + }, + }, + } + Expect(files.AsTree()).To(Equal(excepted)) + }) }) }) diff --git a/cnb-builder-shim/internal/devsandbox/vcs/vcs.go b/cnb-builder-shim/internal/devsandbox/vcs/vcs.go index 416284ff1..3e4a10839 100644 --- a/cnb-builder-shim/internal/devsandbox/vcs/vcs.go +++ b/cnb-builder-shim/internal/devsandbox/vcs/vcs.go @@ -114,6 +114,12 @@ func (v *VersionController) Diff() (Files, error) { return files, nil } +// Commit 提交变更 +func (v *VersionController) Commit(message string) error { + _, err := v.runGitCommand("commit", "-m", message) + return err +} + // 执行 Git 命令 func (v *VersionController) runGitCommand(args ...string) (string, error) { cmd := exec.Command("git", args...) diff --git a/cnb-builder-shim/internal/devsandbox/webserver/server.go b/cnb-builder-shim/internal/devsandbox/webserver/server.go index 8e2dd7b79..f75ffe06c 100644 --- a/cnb-builder-shim/internal/devsandbox/webserver/server.go +++ b/cnb-builder-shim/internal/devsandbox/webserver/server.go @@ -95,6 +95,7 @@ func New(lg *logr.Logger) (*WebServer, error) { r.GET("/processes/status", ProcessStatusHandler()) r.GET("/processes/list", ProcessListHandler()) r.GET("/diffs", DiffsHandler()) + r.GET("/commit", CommitHandler()) return s, nil } @@ -329,6 +330,36 @@ func DiffsHandler() gin.HandlerFunc { } } +// CommitHandler 提交文件变更 +func CommitHandler() gin.HandlerFunc { + return func(c *gin.Context) { + // 由于目前 HTTP 附带文件的源码初始化逻辑不同,暂时不支持 + // TODO 后续重构时需要统一 + if config.G.SourceCode.FetchMethod != config.BK_REPO { + c.JSON( + http.StatusBadRequest, + gin.H{"message": fmt.Sprintf("unsupported fetch method: %s", config.G.SourceCode.FetchMethod)}, + ) + return + } + + commitMsg := c.Query("message") + if commitMsg == "" { + c.JSON(http.StatusBadRequest, gin.H{"message": "commit message is empty"}) + return + } + // 提交变更 + if err := vcs.New().Commit(commitMsg); err != nil { + c.JSON( + http.StatusInternalServerError, + gin.H{"message": fmt.Sprintf("failed to commit files: %s", err)}, + ) + return + } + c.JSON(http.StatusOK, gin.H{"message": "ok"}) + } +} + // HealthzHandler ... func HealthzHandler() gin.HandlerFunc { return func(c *gin.Context) {