Skip to content
Merged
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
28 changes: 22 additions & 6 deletions experiments/tibuild-v2/internal/service/impl/hotfix_tidbx.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (s *hotfixsrvc) BumpTagForTidbx(ctx context.Context, p *hotfix.BumpTagForTi
// Parse repository owner and name
parts := strings.SplitN(p.Repo, "/", 2)
if len(parts) != 2 {
l.Warn().Msg("Invalid repository format, expected 'owner/repo'")
return nil, &hotfix.HTTPError{
Code: http.StatusBadRequest,
Message: "invalid repository format, expected 'owner/repo'",
Expand All @@ -32,6 +33,7 @@ func (s *hotfixsrvc) BumpTagForTidbx(ctx context.Context, p *hotfix.BumpTagForTi

// Validate that at least one of branch or commit is provided
if p.Branch == nil && p.Commit == nil {
l.Warn().Msg("At least one of 'branch' or 'commit' must be provided")
return nil, &hotfix.HTTPError{
Code: http.StatusBadRequest,
Message: "at least one of 'branch' or 'commit' must be provided",
Expand All @@ -41,39 +43,42 @@ func (s *hotfixsrvc) BumpTagForTidbx(ctx context.Context, p *hotfix.BumpTagForTi
// Step 1: Verify the branch or commit exists
commitSHA, err := s.verifyAndGetCommit(ctx, owner, repo, p.Branch, p.Commit)
if err != nil {
l.Err(err).Msg("Failed to verify commit")
return nil, err
}

l = l.With().Str("commit", commitSHA).Logger()
l.Info().Msg("Verified commit exists")

// Step 2: Compute the tag name
tagName, err := s.computeNewTagNameForTidbx(ctx, owner, repo)
// Step 2: Compute the tag name (and fail if commit already has a tidbx-style tag)
tagName, err := s.computeNewTagNameForTidbx(ctx, owner, repo, commitSHA)
if err != nil {
l.Err(err).Msg("Failed to compute tag name")
return nil, err
}

l = l.With().Str("tag", tagName).Logger()
l.Info().Msg("Computed tag name")

// Step 3: Create the tag with author information
tagMessage := fmt.Sprintf("Hot fix tag created by %s", p.Author)
tagMessage := fmt.Sprintf("Created hot fix tag on behalf of %s", p.Author)
if err := s.createTag(ctx, owner, repo, tagName, commitSHA, tagMessage); err != nil {
l.Err(err).Msg("Failed to create tag")
return nil, err
}

l.Info().Msg("Successfully created tag")

return &hotfix.HotfixTagResult{
Repo: p.Repo,
Commit: commitSHA,
Tag: tagName,
}, nil
}

// computeNewTagNameForTidbx computes the next tag name based on existing tags.
// computeNewTagNameForTidbx computes the next tag name based on existing tags,
// and fails if the provided commit already has a tidbx-style tag.
// Tags follow the pattern vX.Y.Z-nextgen.YYYYMM.N
func (s *hotfixsrvc) computeNewTagNameForTidbx(ctx context.Context, owner, repo string) (string, error) {
func (s *hotfixsrvc) computeNewTagNameForTidbx(ctx context.Context, owner, repo, commitSHA string) (string, error) {
// Get all tags from the repository
var allTags []*github.RepositoryTag
opts := &github.ListOptions{PerPage: 100}
Expand All @@ -99,6 +104,17 @@ func (s *hotfixsrvc) computeNewTagNameForTidbx(ctx context.Context, owner, repo

for _, tag := range allTags {
name := tag.GetName()

// If the tidbx-style tag points to the provided commit, fail fast
if pattern.MatchString(name) {
if tag.Commit != nil && tag.Commit.SHA != nil && *tag.Commit.SHA == commitSHA {
return "", &hotfix.HTTPError{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("commit %s already has existing tidbx-style tag: %s", commitSHA, name),
}
}
}

matches := pattern.FindStringSubmatch(name)
if len(matches) == 4 {
seq, err := strconv.Atoi(matches[3])
Expand Down
85 changes: 79 additions & 6 deletions experiments/tibuild-v2/internal/service/impl/hotfix_tidbx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"io"
"net/http"
"strings"
"testing"

"github.com/google/go-github/v69/github"
Expand All @@ -23,13 +24,15 @@ func newServiceWithClient(client *github.Client) *hotfixsrvc {

func TestComputeNewTagNameForTidbx(t *testing.T) {
type testCase struct {
name string
pages [][]string
expected string
expectErr bool
errCode int
name string
pages [][]string
expected string
taggedCommitSHA string
expectErr bool
errCode int
}

testCommitSHA := "a9814602ed087838d71095efd35bd221ab0bf5a9"
cases := []testCase{
{
name: "LastMonthIncrement",
Expand Down Expand Up @@ -85,6 +88,18 @@ func TestComputeNewTagNameForTidbx(t *testing.T) {
},
expected: "v9.0.0-nextgen.202601.1",
},
{
name: "CommitAlreadyTagged",
pages: [][]string{
{
"v8.5.4-nextgen.202510.1",
"v8.5.4-nextgen.202510.2",
},
},
taggedCommitSHA: testCommitSHA,
expectErr: true,
errCode: http.StatusBadRequest,
},
}

for _, tc := range cases {
Expand All @@ -96,6 +111,10 @@ func TestComputeNewTagNameForTidbx(t *testing.T) {
tags := make([]*github.RepositoryTag, len(names))
for j, name := range names {
tags[j] = &github.RepositoryTag{Name: github.Ptr(name)}
// For the CommitAlreadyTagged case, attach the commit SHA to the first tidbx-style tag
if j == 0 && tc.taggedCommitSHA != "" && strings.HasPrefix(name, "v") && strings.Contains(name, "-nextgen.") {
tags[j].Commit = &github.Commit{SHA: &tc.taggedCommitSHA}
}
}
tagPages = append(tagPages, tags)
}
Expand All @@ -104,7 +123,7 @@ func TestComputeNewTagNameForTidbx(t *testing.T) {
ghClient := github.NewClient(mock.NewMockedHTTPClient(resp))
svc := newServiceWithClient(ghClient)

tag, err := svc.computeNewTagNameForTidbx(context.Background(), "owner", "repo")
tag, err := svc.computeNewTagNameForTidbx(context.Background(), "owner", "repo", testCommitSHA)
if tc.expectErr {
if err == nil {
t.Fatalf("expected error, got nil")
Expand Down Expand Up @@ -264,3 +283,57 @@ func TestBumpTagForTidbx_PaginationFlow(t *testing.T) {
})
}
}

func TestBumpTagForTidbx_FailWhenCommitAlreadyTagged(t *testing.T) {
fullRepo := "owner/repo"
branch := "main"
commit := "abc123"

// Tags response includes a tidbx-style tag that points to the same commit
respTags := mock.WithRequestMatchPages(
mock.GetReposTagsByOwnerByRepo,
[]*github.RepositoryTag{
{Name: github.Ptr("v8.5.4-nextgen.202510.1"), Commit: &github.Commit{SHA: github.Ptr(commit)}},
{Name: github.Ptr("v8.5.4-nextgen.202510.2")},
},
)

httpClient := mock.NewMockedHTTPClient(
respTags,
mock.WithRequestMatch(
mock.GetReposCommitsByOwnerByRepoByRef,
&github.RepositoryCommit{
SHA: github.Ptr(commit),
},
),
mock.WithRequestMatch(
mock.GetReposBranchesByOwnerByRepoByBranch,
&github.Branch{
Name: github.Ptr(branch),
Commit: &github.RepositoryCommit{
SHA: github.Ptr(commit),
},
},
),
)
svc := newServiceWithClient(github.NewClient(httpClient))

apiCallPayload := &hotfix.BumpTagForTidbxPayload{
Repo: fullRepo,
Author: "tester",
Branch: &branch,
Commit: &commit,
}

_, err := svc.BumpTagForTidbx(context.Background(), apiCallPayload)
if err == nil {
t.Fatalf("expected error due to existing tidbx-style tag on commit, got nil")
}
httpErr, ok := err.(*hotfix.HTTPError)
if !ok {
t.Fatalf("expected *hotfix.HTTPError, got %T", err)
}
if httpErr.Code != http.StatusBadRequest {
t.Fatalf("expected status %d, got %d", http.StatusBadRequest, httpErr.Code)
}
}
Loading