From 19336091b1ff2d16e7a987525803591c38a140f8 Mon Sep 17 00:00:00 2001 From: BillionClaw Date: Tue, 17 Mar 2026 10:06:23 +0800 Subject: [PATCH 1/2] fix(bitbucketdatacenter): resolve tag commit before fetching config In Bitbucket Data Center, when using annotated tags, the webhook's ToHash is the tag object SHA, not the actual commit SHA. This caused tag events to fail when fetching the pipeline config because the config fetcher would try to read the config file using the tag SHA instead of the actual commit SHA. This fix adds a resolveTagCommit step in the Hook handler that resolves the tag SHA to the actual commit SHA BEFORE the config is fetched. This ensures that tag events work correctly regardless of which branch the pipeline config exists in. Fixes #6247 --- .../bitbucketdatacenter.go | 48 +++++++++++++---- .../forge/bitbucketdatacenter/convert_test.go | 52 ++++++++++++++++++- 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go index ea36e8b2485..3104c7422d3 100644 --- a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go +++ b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go @@ -526,12 +526,22 @@ func (c *client) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model switch e := hook.Event.(type) { case *bb.RepositoryPushEvent: pipe, err = c.updatePipelineFromCommits(ctx, user, repo, hook.Pipeline, currCommit, prevCommit) + if err != nil { + return nil, nil, fmt.Errorf("failed to update pipeline: %w", err) + } + // For tag events, we need to resolve the actual commit SHA early. + // In Bitbucket Data Center, when using annotated tags, the webhook's ToHash is the tag object SHA, + // not the actual commit SHA. We need to resolve this before the config is fetched. + if pipe != nil && pipe.Event == model.EventTag { + if err := c.resolveTagCommit(ctx, user, repo, pipe); err != nil { + return nil, nil, fmt.Errorf("failed to resolve tag commit: %w", err) + } + } case *bb.PullRequestEvent: pipe, err = c.updatePipelineFromPullRequest(ctx, user, repo, hook.Pipeline, e.PullRequest.ID) - } - - if err != nil { - return nil, nil, fmt.Errorf("failed to update pipeline: %w", err) + if err != nil { + return nil, nil, fmt.Errorf("failed to update pipeline: %w", err) + } } if pipe == nil { @@ -580,13 +590,6 @@ func (c *client) updatePipelineFromCommits(ctx context.Context, u *model.User, r return nil, fmt.Errorf("unable to read commit: %w", err) } - // In Bitbucket Data Center, when using annotated tags, the webhook's ToHash is the tag object SHA, not the actual commit SHA. - // Update p.Commit so that build statuses are posted to the correct commit SHA. - if p.Event == model.EventTag && commit.ID != "" && commit.ID != p.Commit { - p.Commit = commit.ID - p.ForgeURL = fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", c.url, r.Owner, r.Name, commit.ID) - } - p.Message = commit.Message opts := &bb.CompareChangesOptions{} @@ -613,6 +616,29 @@ func (c *client) updatePipelineFromCommits(ctx context.Context, u *model.User, r return p, nil } +// resolveTagCommit resolves the actual commit SHA for tag events. +// In Bitbucket Data Center, when using annotated tags, the webhook's ToHash is the tag object SHA, +// not the actual commit SHA. This function resolves the tag SHA to the actual commit SHA. +func (c *client) resolveTagCommit(ctx context.Context, u *model.User, r *model.Repo, p *model.Pipeline) error { + bc, err := c.newClient(ctx, u) + if err != nil { + return fmt.Errorf("unable to create bitbucket client: %w", err) + } + + commit, _, err := bc.Projects.GetCommit(ctx, r.Owner, r.Name, p.Commit) + if err != nil { + return fmt.Errorf("unable to resolve tag commit: %w", err) + } + + // Update the commit SHA and ForgeURL to point to the actual commit + if commit.ID != "" && commit.ID != p.Commit { + p.Commit = commit.ID + p.ForgeURL = fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", c.url, r.Owner, r.Name, commit.ID) + } + + return nil +} + func (c *client) updatePipelineFromPullRequest(ctx context.Context, u *model.User, r *model.Repo, p *model.Pipeline, pullRequestID uint64) (*model.Pipeline, error) { bc, err := c.newClient(ctx, u) if err != nil { diff --git a/server/forge/bitbucketdatacenter/convert_test.go b/server/forge/bitbucketdatacenter/convert_test.go index 202889d295f..385dfed09ad 100644 --- a/server/forge/bitbucketdatacenter/convert_test.go +++ b/server/forge/bitbucketdatacenter/convert_test.go @@ -94,14 +94,17 @@ func Test_convertRepo(t *testing.T) { func Test_convertRepositoryPushEvent(t *testing.T) { now := time.Now() tests := []struct { + name string from *bb.RepositoryPushEvent to *model.Pipeline }{ { + name: "empty event", from: &bb.RepositoryPushEvent{}, to: nil, }, { + name: "delete event with zero ToHash", from: &bb.RepositoryPushEvent{ Changes: []bb.RepositoryPushEventChange{ { @@ -113,6 +116,7 @@ func Test_convertRepositoryPushEvent(t *testing.T) { to: nil, }, { + name: "delete event", from: &bb.RepositoryPushEvent{ Changes: []bb.RepositoryPushEventChange{ { @@ -125,6 +129,7 @@ func Test_convertRepositoryPushEvent(t *testing.T) { to: nil, }, { + name: "branch push event", from: &bb.RepositoryPushEvent{ Event: bb.Event{ Date: bb.ISOTime(now), @@ -164,10 +169,53 @@ func Test_convertRepositoryPushEvent(t *testing.T) { Event: model.EventPush, }, }, + { + name: "tag push event", + from: &bb.RepositoryPushEvent{ + Event: bb.Event{ + Date: bb.ISOTime(now), + Actor: bb.User{ + Name: "John Doe", + Email: "john.doe@mail.com", + Slug: "john.doe_mail.com", + }, + }, + Repository: bb.Repository{ + Slug: "REPO", + Project: &bb.Project{ + Key: "PRJ", + }, + }, + Changes: []bb.RepositoryPushEventChange{ + { + Ref: bb.RepositoryPushEventRef{ + ID: "refs/tags/v1.0.0", + DisplayID: "v1.0.0", + }, + RefId: "refs/tags/v1.0.0", + ToHash: "abcdef1234567890", + }, + }, + }, + to: &model.Pipeline{ + Commit: "abcdef1234567890", + Branch: "v1.0.0", + Message: "", + Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", + Author: "John Doe", + Email: "john.doe@mail.com", + Timestamp: now.UTC().Unix(), + Ref: "refs/tags/v1.0.0", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/abcdef1234567890", + Event: model.EventTag, + }, + }, } for _, tt := range tests { - to := convertRepositoryPushEvent(tt.from, "https://base.url") - assert.Equal(t, tt.to, to) + t.Run(tt.name, func(t *testing.T) { + to := convertRepositoryPushEvent(tt.from, "https://base.url") + assert.Equal(t, tt.to, to) + }) } } From 12b7216ff3912013844e7c579a303d78c329db39 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 4 Apr 2026 20:13:59 +0200 Subject: [PATCH 2/2] adopt --- .../forge/bitbucketdatacenter/convert_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/forge/bitbucketdatacenter/convert_test.go b/server/forge/bitbucketdatacenter/convert_test.go index 52ee76d97de..e6bee29a7ee 100644 --- a/server/forge/bitbucketdatacenter/convert_test.go +++ b/server/forge/bitbucketdatacenter/convert_test.go @@ -105,7 +105,7 @@ func Test_convertRepositoryPushEvent(t *testing.T) { }, { name: "delete event with zero ToHash", - from: &bb.RepositoryPushEvent{ + from: &bitbucket.RepositoryPushEvent{ Changes: []bitbucket.RepositoryPushEventChange{ { FromHash: "1234567890abcdef", @@ -171,24 +171,24 @@ func Test_convertRepositoryPushEvent(t *testing.T) { }, { name: "tag push event", - from: &bb.RepositoryPushEvent{ - Event: bb.Event{ - Date: bb.ISOTime(now), - Actor: bb.User{ + from: &bitbucket.RepositoryPushEvent{ + Event: bitbucket.Event{ + Date: bitbucket.ISOTime(now), + Actor: bitbucket.User{ Name: "John Doe", Email: "john.doe@mail.com", Slug: "john.doe_mail.com", }, }, - Repository: bb.Repository{ + Repository: bitbucket.Repository{ Slug: "REPO", - Project: &bb.Project{ + Project: &bitbucket.Project{ Key: "PRJ", }, }, - Changes: []bb.RepositoryPushEventChange{ + Changes: []bitbucket.RepositoryPushEventChange{ { - Ref: bb.RepositoryPushEventRef{ + Ref: bitbucket.RepositoryPushEventRef{ ID: "refs/tags/v1.0.0", DisplayID: "v1.0.0", },