diff --git a/docs/docs/30-administration/11-forges/10-overview.md b/docs/docs/30-administration/11-forges/10-overview.md index 70305aec2..597c5f689 100644 --- a/docs/docs/30-administration/11-forges/10-overview.md +++ b/docs/docs/30-administration/11-forges/10-overview.md @@ -8,7 +8,7 @@ | Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | | Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Event: Deploy | :white_check_mark: | :x: | :x: | :x: | -| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | [when.path filter](../../20-usage/20-pipeline-syntax.md#path) | :white_check_mark: | :white_check_mark:¹ | :white_check_mark: | :x: | ¹ for pull requests at least Gitea version 1.17 is required diff --git a/server/forge/bitbucket/bitbucket.go b/server/forge/bitbucket/bitbucket.go index 96c73aec8..a8e6ca3a8 100644 --- a/server/forge/bitbucket/bitbucket.go +++ b/server/forge/bitbucket/bitbucket.go @@ -20,17 +20,17 @@ import ( "fmt" "net/http" "net/url" + "path/filepath" "golang.org/x/oauth2" - shared_utils "github.com/woodpecker-ci/woodpecker/shared/utils" - "github.com/woodpecker-ci/woodpecker/server" "github.com/woodpecker-ci/woodpecker/server/forge" "github.com/woodpecker-ci/woodpecker/server/forge/bitbucket/internal" "github.com/woodpecker-ci/woodpecker/server/forge/common" forge_types "github.com/woodpecker-ci/woodpecker/server/forge/types" "github.com/woodpecker-ci/woodpecker/server/model" + shared_utils "github.com/woodpecker-ci/woodpecker/shared/utils" ) // Bitbucket cloud endpoints. @@ -223,8 +223,52 @@ func (c *config) File(ctx context.Context, u *model.User, r *model.Repo, p *mode return []byte(*config), err } -func (c *config) Dir(_ context.Context, _ *model.User, _ *model.Repo, _ *model.Pipeline, _ string) ([]*forge_types.FileMeta, error) { - return nil, forge_types.ErrNotImplemented +// Dir fetches a folder from the bitbucket repository +func (c *config) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model.Pipeline, f string) ([]*forge_types.FileMeta, error) { + var page *string + repoPathFiles := []*forge_types.FileMeta{} + client := c.newClient(ctx, u) + for { + filesResp, err := client.GetRepoFiles(r.Owner, r.Name, p.Commit, f, page) + if err != nil { + return nil, err + } + for _, file := range filesResp.Values { + _, filename := filepath.Split(file.Path) + repoFile := forge_types.FileMeta{ + Name: filename, + } + if file.Type == "commit_file" { + fileData, err := c.newClient(ctx, u).FindSource(r.Owner, r.Name, p.Commit, file.Path) + if err != nil { + return nil, err + } + if fileData != nil { + repoFile.Data = []byte(*fileData) + } + } + repoPathFiles = append(repoPathFiles, &repoFile) + } + + // Check for more results page + if filesResp.Next == nil { + break + } + nextPageURL, err := url.Parse(*filesResp.Next) + if err != nil { + return nil, err + } + params, err := url.ParseQuery(nextPageURL.RawQuery) + if err != nil { + return nil, err + } + nextPage := params.Get("page") + if len(nextPage) == 0 { + break + } + page = &nextPage + } + return repoPathFiles, nil } // Status creates a pipeline status for the Bitbucket commit. diff --git a/server/forge/bitbucket/bitbucket_test.go b/server/forge/bitbucket/bitbucket_test.go index c5a6d59e2..a3d3277fd 100644 --- a/server/forge/bitbucket/bitbucket_test.go +++ b/server/forge/bitbucket/bitbucket_test.go @@ -178,6 +178,20 @@ func Test_bitbucket(t *testing.T) { }) }) + g.Describe("When requesting repo directory contents", func() { + g.It("Should return the details", func() { + files, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "/dir") + g.Assert(err).IsNil() + g.Assert(len(files)).Equal(3) + g.Assert(files[0].Name).Equal("README.md") + g.Assert(string(files[0].Data)).Equal("dummy payload") + }) + g.It("Should handle not found errors", func() { + _, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "/dir_not_found") + g.Assert(err).IsNotNil() + }) + }) + g.Describe("When activating a repository", func() { g.It("Should error when malformed hook", func() { err := c.Activate(ctx, fakeUser, fakeRepo, "%gh&%ij") diff --git a/server/forge/bitbucket/fixtures/handler.go b/server/forge/bitbucket/fixtures/handler.go index 810a41681..90847e821 100644 --- a/server/forge/bitbucket/fixtures/handler.go +++ b/server/forge/bitbucket/fixtures/handler.go @@ -108,6 +108,10 @@ func getRepoHooks(c *gin.Context) { func getRepoFile(c *gin.Context) { switch c.Param("file") { + case "dir": + c.String(200, repoDirPayload) + case "dir_not_found/": + c.String(404, "") case "file_not_found": c.String(404, "") default: @@ -223,6 +227,27 @@ const repoHookPayload = ` const repoFilePayload = "dummy payload" +const repoDirPayload = ` +{ + "pagelen": 10, + "page": 1, + "values": [ + { + "path": "README.md", + "type": "commit_file" + }, + { + "path": "test", + "type": "commit_directory" + }, + { + "path": ".gitignore", + "type": "commit_file" + } + ] +} +` + const userPayload = ` { "username": "superman", diff --git a/server/forge/bitbucket/internal/client.go b/server/forge/bitbucket/internal/client.go index 13fe875ed..35e309f6b 100644 --- a/server/forge/bitbucket/internal/client.go +++ b/server/forge/bitbucket/internal/client.go @@ -48,6 +48,7 @@ const ( pathOrgPerms = "%s/2.0/workspaces/%s/permissions?%s" pathPullRequests = "%s/2.0/repositories/%s/%s/pullrequests" pathBranchCommits = "%s/2.0/repositories/%s/%s/commits/%s" + pathDir = "%s/2.0/repositories/%s/%s/src/%s%s" ) type Client struct { @@ -233,6 +234,16 @@ func (c *Client) GetWorkspace(name string) (*Workspace, error) { return out, err } +func (c *Client) GetRepoFiles(owner, name, revision, path string, page *string) (*DirResp, error) { + out := new(DirResp) + uri := fmt.Sprintf(pathDir, c.base, owner, name, revision, path) + if page != nil { + uri += "?page=" + *page + } + _, err := c.do(uri, get, nil, out) + return out, err +} + func (c *Client) do(rawurl, method string, in, out interface{}) (*string, error) { uri, err := url.Parse(rawurl) if err != nil { diff --git a/server/forge/bitbucket/internal/types.go b/server/forge/bitbucket/internal/types.go index ff2f77f01..8bb4b10dd 100644 --- a/server/forge/bitbucket/internal/types.go +++ b/server/forge/bitbucket/internal/types.go @@ -283,3 +283,16 @@ type CommitsResp struct { type Commit struct { Hash string `json:"hash"` } + +type DirResp struct { + Page uint `json:"page"` + PageLen uint `json:"pagelen"` + Next *string `json:"next"` + Values []*Dir `json:"values"` +} + +type Dir struct { + Path string `json:"path"` + Type string `json:"type"` + Size uint `json:"size"` +}