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 go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/google/cel-go v0.28.0
github.com/google/go-cmp v0.7.0
github.com/google/go-github/scrape v0.0.0-20260403152401-96a365122246
github.com/google/go-github/v84 v84.0.0
github.com/google/go-github/v85 v85.0.0
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
github.com/jenkins-x/go-scm v1.15.17
Expand Down Expand Up @@ -78,7 +79,6 @@ require (
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/google/go-github/v84 v84.0.0 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/rickb777/plural v1.4.10 // indirect
Expand Down
9 changes: 8 additions & 1 deletion pkg/provider/bitbucketdatacenter/bitbucketdatacenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Provider struct {
apiURL string
provenance string
projectKey string
previousHeadCommit string
repo *v1alpha1.Repository
triggerEvent string
cachedChangedFiles *changedfiles.ChangedFiles
Expand Down Expand Up @@ -422,7 +423,13 @@ func (v *Provider) fetchChangedFiles(ctx context.Context, runevent *info.Event)
case triggertype.Push:
opts := &scm.ListOptions{Page: 1, Size: apiResponseLimit}
for {
changes, _, err := v.Client().Git.ListChanges(ctx, orgAndRepo, runevent.SHA, opts)
var changes []*scm.Change
var err error
if v.previousHeadCommit != "" {
changes, _, err = v.getMergeCommitChanges(ctx, runevent.Organization, runevent.Repository, v.previousHeadCommit, runevent.SHA, opts)
} else {
changes, _, err = v.Client().Git.ListChanges(ctx, orgAndRepo, runevent.SHA, opts)
}
if err != nil {
return changedfiles.ChangedFiles{}, fmt.Errorf("failed to list changes for commit %s: %w", runevent.SHA, err)
}
Expand Down
139 changes: 99 additions & 40 deletions pkg/provider/bitbucketdatacenter/bitbucketdatacenter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/settings"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
bbtest "github.com/openshift-pipelines/pipelines-as-code/pkg/provider/bitbucketdatacenter/test"
bbtypes "github.com/openshift-pipelines/pipelines-as-code/pkg/provider/bitbucketdatacenter/types"
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider/status"
"go.opentelemetry.io/otel"

Expand Down Expand Up @@ -707,48 +708,71 @@ func TestGetFiles(t *testing.T) {
PullRequestNumber: 1,
}

pushFiles := []*bbtest.DiffStat{
pushFiles := []*bbtypes.DiffStat{
{
Path: bbtest.DiffPath{ToString: "added.md"},
Path: bbtypes.DiffPath{ToString: "added.md"},
Type: "ADD",
},
{
Path: bbtest.DiffPath{ToString: "modified.txt"},
Path: bbtypes.DiffPath{ToString: "modified.txt"},
Type: "MODIFY",
},
{
Path: bbtest.DiffPath{ToString: "renamed.yaml"},
Path: bbtypes.DiffPath{ToString: "renamed.yaml"},
Type: "MOVE",
},
{
Path: bbtest.DiffPath{ToString: "deleted.go"},
Path: bbtypes.DiffPath{ToString: "deleted.go"},
Type: "DELETE",
},
}

pullRequestFiles := []*bbtest.DiffStat{
pullRequestFiles := []*bbtypes.DiffStat{
{
Path: bbtest.DiffPath{ToString: "added.go"},
Path: bbtypes.DiffPath{ToString: "added.go"},
Type: "ADD",
},
{
Path: bbtest.DiffPath{ToString: "modified.yaml"},
Path: bbtypes.DiffPath{ToString: "modified.yaml"},
Type: "MODIFY",
},
{
Path: bbtest.DiffPath{ToString: "renamed.txt"},
Path: bbtypes.DiffPath{ToString: "renamed.txt"},
Type: "MOVE",
},
{
Path: bbtest.DiffPath{ToString: "deleted.md"},
Path: bbtypes.DiffPath{ToString: "deleted.md"},
Type: "DELETE",
},
}

mergeCommitPushEvent := &info.Event{
SHA: "MERGESHA456",
Organization: "pac",
Repository: "test",
TriggerTarget: triggertype.Push,
}

mergeCommitFiles := []*bbtypes.DiffStat{
{
Path: bbtypes.DiffPath{ToString: "merge-added.go"},
Type: "ADD",
},
{
Path: bbtypes.DiffPath{ToString: "merge-modified.txt"},
Type: "MODIFY",
},
{
Path: bbtypes.DiffPath{ToString: "merge-deleted.md"},
Type: "DELETE",
},
}

tests := []struct {
name string
event *info.Event
changeFiles []*bbtest.DiffStat
changeFiles []*bbtypes.DiffStat
previousHeadCommit string
wantAddedFilesCount int
wantDeletedFilesCount int
wantModifiedFilesCount int
Expand Down Expand Up @@ -794,18 +818,49 @@ func TestGetFiles(t *testing.T) {
wantError: true,
errMsg: "failed to list changes for pull request: No message available",
},
{
name: "good/merge commit push event",
event: mergeCommitPushEvent,
changeFiles: mergeCommitFiles,
previousHeadCommit: "PREVIOUSHEAD789",
wantAddedFilesCount: 1,
wantDeletedFilesCount: 1,
wantModifiedFilesCount: 1,
wantRenamedFilesCount: 0,
},
{
name: "bad/merge commit push event api error",
event: mergeCommitPushEvent,
previousHeadCommit: "PREVIOUSHEAD789",
wantError: true,
errMsg: "failed to list changes for commit MERGESHA456: failed to get merge commit changes: status code: 401",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, _ := rtesting.SetupFakeContext(t)
client, mux, tearDown, tURL := bbtest.SetupBBDataCenterClient()
defer tearDown()

stats := &bbtest.DiffStats{
stats := &bbtypes.DiffStats{
Pagination: bbtypes.Pagination{
LastPage: true,
},
Values: tt.changeFiles,
}

if tt.event.TriggerTarget == triggertype.Push {
if tt.event.TriggerTarget == triggertype.Push && tt.previousHeadCommit != "" {
mux.HandleFunc("/projects/pac/repos/test/changes", func(w http.ResponseWriter, r *http.Request) {
if tt.wantError {
w.WriteHeader(http.StatusUnauthorized)
return
}
assert.Equal(t, r.URL.Query().Get("since"), tt.previousHeadCommit)
assert.Equal(t, r.URL.Query().Get("until"), tt.event.SHA)
b, _ := json.Marshal(stats)
fmt.Fprint(w, string(b))
})
} else if tt.event.TriggerTarget == triggertype.Push {
mux.HandleFunc("/projects/pac/repos/test/commits/IAMSHA123/changes", func(w http.ResponseWriter, _ *http.Request) {
if tt.wantError {
w.WriteHeader(http.StatusUnauthorized)
Expand All @@ -831,7 +886,7 @@ func TestGetFiles(t *testing.T) {
provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
otel.SetMeterProvider(provider)

v := &Provider{client: client, baseURL: tURL, triggerEvent: string(tt.event.TriggerTarget)}
v := &Provider{client: client, baseURL: tURL, triggerEvent: string(tt.event.TriggerTarget), previousHeadCommit: tt.previousHeadCommit}
changedFiles, err := v.GetFiles(ctx, tt.event)
if tt.wantError {
assert.Equal(t, err.Error(), tt.errMsg)
Expand All @@ -855,33 +910,37 @@ func TestGetFiles(t *testing.T) {
}
}

var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
assert.NilError(t, err, "error collecting metrics")

assert.Equal(t, len(rm.ScopeMetrics), 1)
assert.Equal(t, len(rm.ScopeMetrics[0].Metrics), 1)
assert.Equal(t, rm.ScopeMetrics[0].Metrics[0].Name, "pipelines_as_code_git_provider_api_request_count")
count, ok := rm.ScopeMetrics[0].Metrics[0].Data.(metricdata.Sum[int64])
assert.Assert(t, ok)
assert.Equal(t, count.DataPoints[0].Value, int64(1))

_, _ = v.GetFiles(ctx, tt.event)
// recollect the metrics af
err = reader.Collect(ctx, &rm)
assert.NilError(t, err, "error collecting metrics")

assert.Equal(t, len(rm.ScopeMetrics), 1)
assert.Equal(t, len(rm.ScopeMetrics[0].Metrics), 1)
assert.Equal(t, rm.ScopeMetrics[0].Metrics[0].Name, "pipelines_as_code_git_provider_api_request_count")
count, ok = rm.ScopeMetrics[0].Metrics[0].Data.(metricdata.Sum[int64])
assert.Assert(t, ok)
if tt.wantError {
// no caching on error so we expect 2 metrics
assert.Equal(t, count.DataPoints[0].Value, int64(2))
} else {
// caching on success so we expect 1 metric
// getMergeCommitChanges uses v.client.Do directly (not v.Client()),
// so no API usage metrics are recorded for that path.
if tt.previousHeadCommit == "" {
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
assert.NilError(t, err, "error collecting metrics")

assert.Equal(t, len(rm.ScopeMetrics), 1)
assert.Equal(t, len(rm.ScopeMetrics[0].Metrics), 1)
assert.Equal(t, rm.ScopeMetrics[0].Metrics[0].Name, "pipelines_as_code_git_provider_api_request_count")
count, ok := rm.ScopeMetrics[0].Metrics[0].Data.(metricdata.Sum[int64])
assert.Assert(t, ok)
assert.Equal(t, count.DataPoints[0].Value, int64(1))

_, _ = v.GetFiles(ctx, tt.event)
// recollect the metrics af
err = reader.Collect(ctx, &rm)
assert.NilError(t, err, "error collecting metrics")

assert.Equal(t, len(rm.ScopeMetrics), 1)
assert.Equal(t, len(rm.ScopeMetrics[0].Metrics), 1)
assert.Equal(t, rm.ScopeMetrics[0].Metrics[0].Name, "pipelines_as_code_git_provider_api_request_count")
count, ok = rm.ScopeMetrics[0].Metrics[0].Data.(metricdata.Sum[int64])
assert.Assert(t, ok)
if tt.wantError {
// no caching on error so we expect 2 metrics
assert.Equal(t, count.DataPoints[0].Value, int64(2))
} else {
// caching on success so we expect 1 metric
assert.Equal(t, count.DataPoints[0].Value, int64(1))
}
}
})
}
Expand Down
89 changes: 89 additions & 0 deletions pkg/provider/bitbucketdatacenter/diff_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package bitbucketdatacenter

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/jenkins-x/go-scm/scm"
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider/bitbucketdatacenter/types"
)

// getMergeCommitChanges gets the changes between two commits for a merge commit.
// this endpoint is not implemented in the go-scm package, so we need to use the raw HTTP request.
// but we're using the client from the go-scm package to make the request.
func (v *Provider) getMergeCommitChanges(ctx context.Context, org, repo, fromCommit, toCommit string, opts *scm.ListOptions) ([]*scm.Change, *scm.Response, error) {
params := url.Values{}
params.Set("since", fromCommit)
params.Set("until", toCommit)
params.Set("start", fmt.Sprintf("%d", (opts.Page-1)*opts.Size))
params.Set("limit", fmt.Sprintf("%d", opts.Size))
path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/changes?%s",
url.PathEscape(org),
url.PathEscape(repo),
params.Encode())

resp, err := v.Client().Do(ctx, &scm.Request{
Method: "GET",
Path: path,
Header: http.Header{
"Content-Type": {"application/json"},
"x-atlassian-token": {"no-check"},
},
})
if err != nil {
return nil, nil, fmt.Errorf("failed to get merge commit changes: %w", err)
}
defer resp.Body.Close()

if resp.Status > 300 {
Comment thread
zakisk marked this conversation as resolved.
var errResp struct {
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
}
errMsg := fmt.Sprintf("status code: %d", resp.Status)
if decErr := json.NewDecoder(resp.Body).Decode(&errResp); decErr == nil && len(errResp.Errors) > 0 {
errMsg = fmt.Sprintf("%s, message: %s", errMsg, errResp.Errors[0].Message)
}
return nil, nil, fmt.Errorf("failed to get merge commit changes: %s", errMsg)
}

out := new(types.DiffStats)

err = json.NewDecoder(resp.Body).Decode(out)
if err != nil {
return nil, nil, fmt.Errorf("failed to decode merge commit changes: %w", err)
}

if !out.LastPage {
resp.Page.First = 1
resp.Page.Next = opts.Page + 1
}

return convertDiffstats(out), resp, nil
}

func convertDiffstats(from *types.DiffStats) []*scm.Change {
to := make([]*scm.Change, 0, len(from.Values))
for _, v := range from.Values {
to = append(to, convertDiffstat(v))
}
return to
}

func convertDiffstat(from *types.DiffStat) *scm.Change {
to := &scm.Change{
Path: from.Path.ToString,
Added: from.Type == "ADD",
Modified: from.Type == "MODIFY",
Renamed: from.Type == "MOVE",
Deleted: from.Type == "DELETE",
}
if from.SrcPath != nil {
to.PreviousPath = from.SrcPath.ToString
}
return to
}
Loading
Loading