Skip to content

Commit f9fd3f3

Browse files
zakiskchmouel
authored andcommitted
fix: issue in on-path-change on pr merge in bitbucket data center
fixed an issue where push pipeline run was not triggering on pr merge when pipeline run definition is having on-path-cahnge annotation. this was happening due to bitbucket data center api behavior that it creates merge commit on pr merge that is set as HEAD commit of the branch and when changes for that commit is retrieved, they're being received empty because merge commit in bitbucket data center doesn't contain code changes of its parent. https://issues.redhat.com/browse/SRVKP-7432 Signed-off-by: Zaki Shaikh <[email protected]>
1 parent 6575e4d commit f9fd3f3

File tree

7 files changed

+166
-18
lines changed

7 files changed

+166
-18
lines changed

pkg/provider/bitbucketdatacenter/parse_payload.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func sanitizeOwner(owner string) string {
8888
}
8989

9090
// ParsePayload parses the payload from the event.
91-
func (v *Provider) ParsePayload(_ context.Context, _ *params.Run, request *http.Request,
91+
func (v *Provider) ParsePayload(_ context.Context, run *params.Run, request *http.Request,
9292
payload string,
9393
) (*info.Event, error) {
9494
processedEvent := info.NewEvent()
@@ -169,6 +169,27 @@ func (v *Provider) ParsePayload(_ context.Context, _ *params.Run, request *http.
169169
}
170170

171171
processedEvent.SHA = e.Changes[0].ToHash
172+
173+
// In Bitbucket Data Center, when a pull request is merged, it creates two commits in the repository:
174+
// 1. A merge commit, which is represented by `changes[0].ToHash`.
175+
// 2. The actual commit containing the changes from the source branch.
176+
//
177+
// However, the merge commit often does not contain any file changes itself,
178+
// which can cause issues when determining whether file modifications should trigger PipelineRuns.
179+
//
180+
// Typically, a regular (non-merge) commit has a single parent, but a merge commit has two parents:
181+
// - The first parent is the previous HEAD of the destination branch (the branch into which the PR was merged).
182+
// - The second parent is the HEAD of the source branch (the branch being merged).
183+
//
184+
// To correctly identify the actual commit that contains the changes (i.e., the source branch's HEAD),
185+
// we inspect `e.Commits[0]`, and if it has more than one parent, we take the second parent.
186+
// This helps ensure we reference the correct commit.
187+
if len(e.Commits) > 1 && len(e.Commits[0].Parents) > 1 {
188+
processedEvent.SHA = e.Commits[0].Parents[1].ID
189+
run.Clients.Log.Infof("Detected a merge commit as HEAD; "+
190+
"using second parent commit SHA %s (source branch HEAD) to target push event", e.Commits[0].Parents[1].ID)
191+
}
192+
172193
processedEvent.URL = e.Repository.Links.Self[0].Href
173194
processedEvent.BaseBranch = e.Changes[0].RefID
174195
processedEvent.HeadBranch = e.Changes[0].RefID

pkg/provider/bitbucketdatacenter/parse_payload_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
1010
bbv1test "github.com/openshift-pipelines/pipelines-as-code/pkg/provider/bitbucketdatacenter/test"
1111
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider/bitbucketdatacenter/types"
12+
"github.com/openshift-pipelines/pipelines-as-code/pkg/test/logger"
1213
"gotest.tools/v3/assert"
1314
rtesting "knative.dev/pkg/reconciler/testing"
1415
)
@@ -570,6 +571,7 @@ func TestParsePayload(t *testing.T) {
570571
payloadEvent any
571572
expEvent *info.Event
572573
eventType string
574+
wantSHA string
573575
wantErrSubstr string
574576
rawStr string
575577
targetPipelinerun string
@@ -607,12 +609,14 @@ func TestParsePayload(t *testing.T) {
607609
eventType: "pr:opened",
608610
payloadEvent: bbv1test.MakePREvent(ev1, ""),
609611
expEvent: ev1,
612+
wantSHA: "abcd",
610613
},
611614
{
612615
name: "good/push",
613616
eventType: "repo:refs_changed",
614617
payloadEvent: bbv1test.MakePushEvent(ev1, []types.PushRequestEventChange{{ToHash: ev1.SHA, RefID: "base"}}, []types.Commit{{ID: ev1.SHA}}),
615618
expEvent: ev1,
619+
wantSHA: "abcd",
616620
},
617621
{
618622
name: "bad/changes are empty in push",
@@ -628,37 +632,82 @@ func TestParsePayload(t *testing.T) {
628632
expEvent: ev1,
629633
wantErrSubstr: "push event contains no commits; cannot proceed",
630634
},
635+
{
636+
name: "good/changes are empty in push",
637+
eventType: "repo:refs_changed",
638+
payloadEvent: bbv1test.MakePushEvent(ev1, []types.PushRequestEventChange{
639+
{
640+
Ref: types.Ref{ID: "refs/heads/main"},
641+
ToHash: "abcd",
642+
},
643+
}, []types.Commit{
644+
{
645+
Parents: []struct {
646+
ID string `json:"id"`
647+
DisplayID string `json:"displayId"`
648+
}{
649+
{
650+
ID: "efghabcd",
651+
DisplayID: "efgh",
652+
},
653+
{
654+
ID: "abcdefgh",
655+
DisplayID: "abcd",
656+
},
657+
},
658+
},
659+
{
660+
ID: "abcdefgh",
661+
Parents: []struct {
662+
ID string `json:"id"`
663+
DisplayID string `json:"displayId"`
664+
}{
665+
{
666+
ID: "weroiusf",
667+
DisplayID: "wero",
668+
},
669+
},
670+
},
671+
}),
672+
expEvent: ev1,
673+
wantSHA: "abcdefgh",
674+
},
631675
{
632676
name: "good/comment ok-to-test",
633677
eventType: "pr:comment:added",
634678
payloadEvent: bbv1test.MakePREvent(ev1, "/ok-to-test"),
635679
expEvent: ev1,
680+
wantSHA: "abcd",
636681
},
637682
{
638683
name: "good/comment test",
639684
eventType: "pr:comment:added",
640685
payloadEvent: bbv1test.MakePREvent(ev1, "/test"),
641686
expEvent: ev1,
687+
wantSHA: "abcd",
642688
},
643689
{
644690
name: "good/comment retest a pr",
645691
eventType: "pr:comment:added",
646692
payloadEvent: bbv1test.MakePREvent(ev1, "/retest dummy"),
647693
expEvent: ev1,
648694
targetPipelinerun: "dummy",
695+
wantSHA: "abcd",
649696
},
650697
{
651698
name: "good/comment cancel a pr",
652699
eventType: "pr:comment:added",
653700
payloadEvent: bbv1test.MakePREvent(ev1, "/cancel dummy"),
654701
expEvent: ev1,
655702
canceltargetPipelinerun: "dummy",
703+
wantSHA: "abcd",
656704
},
657705
{
658706
name: "good/comment cancel all",
659707
eventType: "pr:comment:added",
660708
payloadEvent: bbv1test.MakePREvent(ev1, "/cancel"),
661709
expEvent: ev1,
710+
wantSHA: "abcd",
662711
},
663712
}
664713
for _, tt := range tests {
@@ -672,6 +721,7 @@ func TestParsePayload(t *testing.T) {
672721
run := &params.Run{
673722
Info: info.Info{},
674723
}
724+
run.Clients.Log, _ = logger.GetLogger()
675725
_b, err := json.Marshal(tt.payloadEvent)
676726
assert.NilError(t, err)
677727
payload := string(_b)
@@ -686,6 +736,9 @@ func TestParsePayload(t *testing.T) {
686736
}
687737
assert.NilError(t, err)
688738

739+
// assert SHA ID
740+
assert.Equal(t, tt.wantSHA, got.SHA)
741+
689742
assert.Equal(t, got.AccountID, tt.expEvent.AccountID)
690743

691744
// test that we got slashed

test/bitbucket_datacenter_pull_request_test.go

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ import (
99
"os"
1010
"testing"
1111

12+
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
1213
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
1314
tbbdc "github.com/openshift-pipelines/pipelines-as-code/test/pkg/bitbucketdatacenter"
15+
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/options"
16+
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/payload"
1417
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/wait"
1518

19+
"github.com/jenkins-x/go-scm/scm"
1620
"github.com/tektoncd/pipeline/pkg/names"
1721
"gotest.tools/v3/assert"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1823
)
1924

2025
func TestBitbucketDataCenterPullRequest(t *testing.T) {
@@ -35,9 +40,11 @@ func TestBitbucketDataCenterPullRequest(t *testing.T) {
3540
files[fmt.Sprintf(".tekton/pipelinerun-%d.yaml", i)] = "testdata/pipelinerun.yaml"
3641
}
3742

43+
files, err = payload.GetEntries(files, targetNS, options.MainBranch, triggertype.PullRequest.String(), map[string]string{})
44+
assert.NilError(t, err)
45+
3846
pr := tbbdc.CreatePR(ctx, t, client, runcnx, opts, repo, files, bitbucketWSOwner, targetNS)
39-
runcnx.Clients.Log.Infof("Pull Request with title '%s' is created", pr.Title)
40-
defer tbbdc.TearDown(ctx, t, runcnx, client, pr.Number, bitbucketWSOwner, targetNS)
47+
defer tbbdc.TearDown(ctx, t, runcnx, client, pr, bitbucketWSOwner, targetNS)
4148

4249
successOpts := wait.SuccessOpt{
4350
TargetNS: targetNS,
@@ -64,9 +71,11 @@ func TestBitbucketDataCenterCELPathChangeInPullRequest(t *testing.T) {
6471
".tekton/pipelinerun.yaml": "testdata/pipelinerun-cel-path-changed.yaml",
6572
}
6673

74+
files, err = payload.GetEntries(files, targetNS, options.MainBranch, triggertype.PullRequest.String(), map[string]string{})
75+
assert.NilError(t, err)
76+
6777
pr := tbbdc.CreatePR(ctx, t, client, runcnx, opts, repo, files, bitbucketWSOwner, targetNS)
68-
runcnx.Clients.Log.Infof("Pull Request with title '%s' is created", pr.Title)
69-
defer tbbdc.TearDown(ctx, t, runcnx, client, pr.Number, bitbucketWSOwner, targetNS)
78+
defer tbbdc.TearDown(ctx, t, runcnx, client, pr, bitbucketWSOwner, targetNS)
7079

7180
successOpts := wait.SuccessOpt{
7281
TargetNS: targetNS,
@@ -76,3 +85,64 @@ func TestBitbucketDataCenterCELPathChangeInPullRequest(t *testing.T) {
7685
}
7786
wait.Succeeded(ctx, t, runcnx, opts, successOpts)
7887
}
88+
89+
func TestBitbucketDataCenterOnPathChangeAnnotationOnPRMerge(t *testing.T) {
90+
targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns")
91+
// this would be a temporary base branch for the pull request we're going to raise
92+
// we need this because we're going to merge the pull request so that after test
93+
// we can delete the temporary base branch and our main branch should not be affected
94+
// by this merge because we run the E2E frequently.
95+
tempBaseBranch := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns")
96+
97+
ctx := context.Background()
98+
bitbucketWSOwner := os.Getenv("TEST_BITBUCKET_SERVER_E2E_REPOSITORY")
99+
100+
ctx, runcnx, opts, client, err := tbbdc.Setup(ctx)
101+
assert.NilError(t, err)
102+
103+
repo := tbbdc.CreateCRD(ctx, t, client, runcnx, bitbucketWSOwner, targetNS)
104+
runcnx.Clients.Log.Infof("Repository %s has been created", repo.Name)
105+
defer tbbdc.TearDownNs(ctx, t, runcnx, targetNS)
106+
107+
branch, resp, err := client.Git.CreateRef(ctx, bitbucketWSOwner, tempBaseBranch, repo.Branch)
108+
assert.NilError(t, err, "error creating branch: http status code: %d : %v", resp.Status, err)
109+
runcnx.Clients.Log.Infof("Base branch %s has been created", branch.Name)
110+
111+
opts.BaseBranch = branch.Name
112+
113+
if os.Getenv("TEST_NOCLEANUP") != "true" {
114+
defer func() {
115+
_, err := client.Git.DeleteRef(ctx, bitbucketWSOwner, tempBaseBranch)
116+
assert.NilError(t, err, "error deleting branch: http status code: %d : %v", resp.Status, err)
117+
}()
118+
}
119+
120+
files := map[string]string{
121+
".tekton/pr.yaml": "testdata/pipelinerun-on-path-change.yaml",
122+
"doc/foo/bar/README.md": "README.md",
123+
}
124+
125+
files, err = payload.GetEntries(files, targetNS, tempBaseBranch, triggertype.Push.String(), map[string]string{})
126+
assert.NilError(t, err)
127+
128+
pr := tbbdc.CreatePR(ctx, t, client, runcnx, opts, repo, files, bitbucketWSOwner, targetNS)
129+
defer tbbdc.TearDown(ctx, t, runcnx, client, nil, bitbucketWSOwner, targetNS)
130+
131+
// merge the pull request so that we can get push event.
132+
_, err = client.PullRequests.Merge(ctx, bitbucketWSOwner, pr.Number, &scm.PullRequestMergeOptions{})
133+
assert.NilError(t, err)
134+
135+
successOpts := wait.SuccessOpt{
136+
TargetNS: targetNS,
137+
OnEvent: triggertype.Push.String(),
138+
NumberofPRMatch: 1,
139+
MinNumberStatus: 1,
140+
}
141+
wait.Succeeded(ctx, t, runcnx, opts, successOpts)
142+
143+
pipelineRuns, err := runcnx.Clients.Tekton.TektonV1().PipelineRuns(targetNS).List(ctx, metav1.ListOptions{})
144+
assert.NilError(t, err)
145+
assert.Equal(t, len(pipelineRuns.Items), 1)
146+
// check that pipeline run contains on-path-change annotation.
147+
assert.Equal(t, pipelineRuns.Items[0].GetAnnotations()[keys.OnPathChange], "[doc/***.md]")
148+
}

test/bitbucket_datacenter_push_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func TestBitbucketDataCenterCELPathChangeOnPush(t *testing.T) {
3939
branch, resp, err := client.Git.CreateRef(ctx, bitbucketWSOwner, targetNS, mainBranchRef)
4040
assert.NilError(t, err, "error creating branch: http status code: %d : %v", resp.Status, err)
4141
runcnx.Clients.Log.Infof("Branch %s has been created", branch.Name)
42-
defer tbbs.TearDown(ctx, t, runcnx, client, -1, bitbucketWSOwner, branch.Name)
42+
defer tbbs.TearDown(ctx, t, runcnx, client, nil, bitbucketWSOwner, branch.Name)
4343

4444
files, err = payload.GetEntries(files, targetNS, branch.Name, triggertype.Push.String(), map[string]string{})
4545
assert.NilError(t, err)

test/pkg/bitbucketdatacenter/pr.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,32 @@ import (
66
"testing"
77

88
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
9-
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
109
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/options"
11-
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/payload"
1210
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/scm"
1311

1412
goscm "github.com/jenkins-x/go-scm/scm"
1513
"gotest.tools/v3/assert"
1614
)
1715

1816
func CreatePR(ctx context.Context, t *testing.T, client *goscm.Client, runcnx *params.Run, opts options.E2E, repo *goscm.Repository, files map[string]string, orgAndRepo, targetNS string) *goscm.PullRequest {
19-
mainBranchRef := "refs/heads/main"
20-
branch, resp, err := client.Git.CreateRef(ctx, orgAndRepo, targetNS, mainBranchRef)
17+
baseBranchRef := repo.Branch
18+
if opts.BaseBranch != "" {
19+
baseBranchRef = opts.BaseBranch
20+
}
21+
22+
branch, resp, err := client.Git.CreateRef(ctx, orgAndRepo, targetNS, baseBranchRef)
2123
assert.NilError(t, err, "error creating branch: http status code: %d : %v", resp.Status, err)
2224
runcnx.Clients.Log.Infof("Branch %s has been created", branch.Name)
2325

24-
files, err = payload.GetEntries(files, targetNS, options.MainBranch, triggertype.PullRequest.String(), map[string]string{})
25-
assert.NilError(t, err)
2626
gitCloneURL, err := scm.MakeGitCloneURL(repo.Clone, opts.UserName, opts.Password)
2727
assert.NilError(t, err)
28+
2829
scmOpts := &scm.Opts{
2930
GitURL: gitCloneURL,
3031
Log: runcnx.Clients.Log,
3132
WebURL: repo.Clone,
3233
TargetRefName: targetNS,
33-
BaseRefName: repo.Branch,
34+
BaseRefName: baseBranchRef,
3435
CommitTitle: fmt.Sprintf("commit %s", targetNS),
3536
}
3637
scm.PushFilesToRefGit(t, scmOpts, files)
@@ -41,9 +42,10 @@ func CreatePR(ctx context.Context, t *testing.T, client *goscm.Client, runcnx *p
4142
Title: title,
4243
Body: "Test PAC on bitbucket data center",
4344
Head: targetNS,
44-
Base: "main",
45+
Base: baseBranchRef,
4546
}
4647
pr, resp, err := client.PullRequests.Create(ctx, orgAndRepo, prOpts)
4748
assert.NilError(t, err, "error creating pull request: http status code: %d : %v", resp.Status, err)
49+
runcnx.Clients.Log.Infof("Created Pull Request with Title '%s'. Head branch '%s' ⮕ Base Branch '%s'", pr.Title, pr.Head.Ref, pr.Base.Ref)
4850
return pr
4951
}

test/pkg/bitbucketdatacenter/setup.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,16 @@ func TearDownNs(ctx context.Context, t *testing.T, runcnx *params.Run, targetNS
8282
repository.NSTearDown(ctx, t, runcnx, targetNS)
8383
}
8484

85-
func TearDown(ctx context.Context, t *testing.T, runcnx *params.Run, client *scm.Client, prID int, orgAndRepo, ref string) {
85+
func TearDown(ctx context.Context, t *testing.T, runcnx *params.Run, client *scm.Client, pr *scm.PullRequest, orgAndRepo, ref string) {
8686
if os.Getenv("TEST_NOCLEANUP") == "true" {
8787
runcnx.Clients.Log.Infof("Not cleaning up and closing PR since TEST_NOCLEANUP is set")
8888
return
8989
}
9090

91-
if prID != -1 {
92-
runcnx.Clients.Log.Infof("Deleting PR #%d", prID)
93-
_, err := client.PullRequests.DeletePullRequest(ctx, orgAndRepo, prID)
91+
// in Bitbucket Data Center, merged pull requests cannot be deleted.
92+
if pr != nil && !pr.Merged {
93+
runcnx.Clients.Log.Infof("Deleting PR #%d", pr.Number)
94+
_, err := client.PullRequests.DeletePullRequest(ctx, orgAndRepo, pr.Number)
9495
assert.NilError(t, err)
9596
}
9697

test/pkg/options/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascod
44

55
type E2E struct {
66
Repo, Organization string
7+
BaseBranch string
78
DirectWebhook bool
89
ProjectID int
910
ControllerURL string

0 commit comments

Comments
 (0)