mirror of https://github.com/go-gitea/gitea.git
Merge 614cff0e45 into 01351cc6c7
commit
ae366e10c0
@ -0,0 +1,346 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package release
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// GenerateReleaseNotesOptions describes how to build release notes content.
|
||||
type GenerateReleaseNotesOptions struct {
|
||||
TagName string
|
||||
Target string
|
||||
PreviousTag string
|
||||
}
|
||||
|
||||
// GenerateReleaseNotesResult holds the rendered notes and the base tag used.
|
||||
type GenerateReleaseNotesResult struct {
|
||||
Content string
|
||||
PreviousTag string
|
||||
}
|
||||
|
||||
func newErrReleaseNotesTagNotFound(tagName string) error {
|
||||
return util.ErrorWrapTranslatable(util.NewNotExistErrorf("tag %q not found", tagName), "repo.release.generate_notes_tag_not_found", tagName)
|
||||
}
|
||||
|
||||
func newErrReleaseNotesTargetNotFound(ref string) error {
|
||||
return util.ErrorWrapTranslatable(util.NewNotExistErrorf("release target %q not found", ref), "repo.release.generate_notes_target_not_found", ref)
|
||||
}
|
||||
|
||||
// GenerateReleaseNotes builds the markdown snippet for release notes.
|
||||
func GenerateReleaseNotes(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts GenerateReleaseNotesOptions) (*GenerateReleaseNotesResult, error) {
|
||||
tagName := strings.TrimSpace(opts.TagName)
|
||||
if tagName == "" {
|
||||
return nil, util.NewInvalidArgumentErrorf("empty target tag name for release notes")
|
||||
}
|
||||
|
||||
headCommit, err := resolveHeadCommit(repo, gitRepo, tagName, opts.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseSelection, err := resolveBaseTag(ctx, repo, gitRepo, headCommit, tagName, opts.PreviousTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commits, err := gitRepo.CommitsBetweenIDs(headCommit.ID.String(), baseSelection.Commit.ID.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CommitsBetweenIDs: %w", err)
|
||||
}
|
||||
|
||||
prs, err := collectPullRequestsFromCommits(ctx, repo.ID, commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contributors, newContributors, err := collectContributors(ctx, repo.ID, prs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := buildReleaseNotesContent(ctx, repo, tagName, baseSelection.CompareBase, prs, contributors, newContributors)
|
||||
return &GenerateReleaseNotesResult{
|
||||
Content: content,
|
||||
PreviousTag: baseSelection.PreviousTag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resolveHeadCommit(repo *repo_model.Repository, gitRepo *git.Repository, tagName, target string) (*git.Commit, error) {
|
||||
ref := tagName
|
||||
if !gitRepo.IsTagExist(tagName) {
|
||||
ref = strings.TrimSpace(target)
|
||||
if ref == "" {
|
||||
ref = repo.DefaultBranch
|
||||
}
|
||||
}
|
||||
|
||||
commit, err := gitRepo.GetCommit(ref)
|
||||
if err != nil {
|
||||
return nil, newErrReleaseNotesTargetNotFound(ref)
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
type baseSelection struct {
|
||||
CompareBase string
|
||||
PreviousTag string
|
||||
Commit *git.Commit
|
||||
}
|
||||
|
||||
func resolveBaseTag(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, headCommit *git.Commit, tagName, requestedBase string) (*baseSelection, error) {
|
||||
requestedBase = strings.TrimSpace(requestedBase)
|
||||
if requestedBase != "" {
|
||||
return buildBaseSelectionForTag(gitRepo, requestedBase)
|
||||
}
|
||||
|
||||
candidate, err := autoPreviousReleaseTag(ctx, repo, tagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if candidate != "" {
|
||||
return buildBaseSelectionForTag(gitRepo, candidate)
|
||||
}
|
||||
|
||||
tagInfos, _, err := gitRepo.GetTagInfos(0, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetTagInfos: %w", err)
|
||||
}
|
||||
|
||||
if previousTag, ok := findPreviousTagName(tagInfos, tagName); ok {
|
||||
return buildBaseSelectionForTag(gitRepo, previousTag)
|
||||
}
|
||||
|
||||
initialCommit, err := findInitialCommit(headCommit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &baseSelection{
|
||||
CompareBase: initialCommit.ID.String(),
|
||||
PreviousTag: "",
|
||||
Commit: initialCommit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildBaseSelectionForTag(gitRepo *git.Repository, tagName string) (*baseSelection, error) {
|
||||
baseCommit, err := gitRepo.GetCommit(tagName)
|
||||
if err != nil {
|
||||
return nil, newErrReleaseNotesTagNotFound(tagName)
|
||||
}
|
||||
return &baseSelection{
|
||||
CompareBase: tagName,
|
||||
PreviousTag: tagName,
|
||||
Commit: baseCommit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func autoPreviousReleaseTag(ctx context.Context, repo *repo_model.Repository, tagName string) (string, error) {
|
||||
currentRelease, err := repo_model.GetRelease(ctx, repo.ID, tagName)
|
||||
switch {
|
||||
case err == nil:
|
||||
return findPreviousPublishedReleaseTag(ctx, repo, currentRelease)
|
||||
case repo_model.IsErrReleaseNotExist(err):
|
||||
// this tag has no stored release, fall back to latest release below
|
||||
default:
|
||||
return "", fmt.Errorf("GetRelease: %w", err)
|
||||
}
|
||||
|
||||
rel, err := repo_model.GetLatestReleaseByRepoID(ctx, repo.ID)
|
||||
switch {
|
||||
case err == nil:
|
||||
if strings.EqualFold(rel.TagName, tagName) {
|
||||
return "", nil
|
||||
}
|
||||
return rel.TagName, nil
|
||||
case repo_model.IsErrReleaseNotExist(err):
|
||||
return "", nil
|
||||
default:
|
||||
return "", fmt.Errorf("GetLatestReleaseByRepoID: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func findPreviousPublishedReleaseTag(ctx context.Context, repo *repo_model.Repository, current *repo_model.Release) (string, error) {
|
||||
prev, err := repo_model.GetPreviousPublishedRelease(ctx, repo.ID, current)
|
||||
switch {
|
||||
case err == nil:
|
||||
case repo_model.IsErrReleaseNotExist(err):
|
||||
return "", nil
|
||||
default:
|
||||
return "", fmt.Errorf("GetPreviousPublishedRelease: %w", err)
|
||||
}
|
||||
|
||||
return prev.TagName, nil
|
||||
}
|
||||
|
||||
func findPreviousTagName(tags []*git.Tag, target string) (string, bool) {
|
||||
foundTarget := false
|
||||
targetVersion := parseSemanticVersion(target)
|
||||
|
||||
for _, tag := range tags {
|
||||
name := strings.TrimSpace(tag.Name)
|
||||
if strings.EqualFold(name, target) {
|
||||
foundTarget = true
|
||||
continue
|
||||
}
|
||||
if foundTarget {
|
||||
if targetVersion != nil {
|
||||
if candidateVersion := parseSemanticVersion(name); candidateVersion != nil && candidateVersion.GreaterThan(targetVersion) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return name, true
|
||||
}
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
return strings.TrimSpace(tags[0].Name), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func parseSemanticVersion(tag string) *version.Version {
|
||||
tag = strings.TrimSpace(tag)
|
||||
tag = strings.TrimPrefix(tag, "v")
|
||||
tag = strings.TrimPrefix(tag, "V")
|
||||
v, err := version.NewVersion(tag)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func findInitialCommit(commit *git.Commit) (*git.Commit, error) {
|
||||
current := commit
|
||||
for current.ParentCount() > 0 {
|
||||
parent, err := current.Parent(0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parent: %w", err)
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func collectPullRequestsFromCommits(ctx context.Context, repoID int64, commits []*git.Commit) ([]*issues_model.PullRequest, error) {
|
||||
prs := make([]*issues_model.PullRequest, 0, len(commits))
|
||||
|
||||
for _, commit := range commits {
|
||||
pr, err := issues_model.GetPullRequestByMergedCommit(ctx, repoID, commit.ID.String())
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("GetPullRequestByMergedCommit: %w", err)
|
||||
}
|
||||
|
||||
if err = pr.LoadIssue(ctx); err != nil {
|
||||
return nil, fmt.Errorf("LoadIssue: %w", err)
|
||||
}
|
||||
if err = pr.Issue.LoadAttributes(ctx); err != nil {
|
||||
return nil, fmt.Errorf("LoadIssueAttributes: %w", err)
|
||||
}
|
||||
|
||||
prs = append(prs, pr)
|
||||
}
|
||||
|
||||
slices.SortFunc(prs, func(a, b *issues_model.PullRequest) int {
|
||||
if cmpRes := cmp.Compare(b.MergedUnix, a.MergedUnix); cmpRes != 0 {
|
||||
return cmpRes
|
||||
}
|
||||
return cmp.Compare(b.Issue.Index, a.Issue.Index)
|
||||
})
|
||||
|
||||
return prs, nil
|
||||
}
|
||||
|
||||
func buildReleaseNotesContent(ctx context.Context, repo *repo_model.Repository, tagName, baseRef string, prs []*issues_model.PullRequest, contributors []*user_model.User, newContributors []*issues_model.PullRequest) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("## What's Changed\n")
|
||||
|
||||
for _, pr := range prs {
|
||||
prURL := pr.Issue.HTMLURL(ctx)
|
||||
builder.WriteString(fmt.Sprintf("* %s in [#%d](%s)\n", pr.Issue.Title, pr.Issue.Index, prURL))
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
|
||||
if len(contributors) > 0 {
|
||||
builder.WriteString("## Contributors\n")
|
||||
for _, contributor := range contributors {
|
||||
builder.WriteString(fmt.Sprintf("* @%s\n", contributor.Name))
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
|
||||
if len(newContributors) > 0 {
|
||||
builder.WriteString("## New Contributors\n")
|
||||
for _, contributor := range newContributors {
|
||||
prURL := contributor.Issue.HTMLURL(ctx)
|
||||
builder.WriteString(fmt.Sprintf("* @%s made their first contribution in [#%d](%s)\n", contributor.Issue.Poster.Name, contributor.Issue.Index, prURL))
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
|
||||
builder.WriteString("**Full Changelog**: ")
|
||||
compareURL := fmt.Sprintf("%s/compare/%s...%s", repo.HTMLURL(ctx), util.PathEscapeSegments(baseRef), util.PathEscapeSegments(tagName))
|
||||
builder.WriteString(fmt.Sprintf("[%s...%s](%s)", baseRef, tagName, compareURL))
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func collectContributors(ctx context.Context, repoID int64, prs []*issues_model.PullRequest) ([]*user_model.User, []*issues_model.PullRequest, error) {
|
||||
contributors := make([]*user_model.User, 0, len(prs))
|
||||
newContributors := make([]*issues_model.PullRequest, 0, len(prs))
|
||||
seenContributors := container.Set[int64]{}
|
||||
seenNew := container.Set[int64]{}
|
||||
|
||||
for _, pr := range prs {
|
||||
poster := pr.Issue.Poster
|
||||
posterID := poster.ID
|
||||
|
||||
if posterID == 0 {
|
||||
// Migrated PRs may not have a linked local user (PosterID == 0). Skip them for now.
|
||||
continue
|
||||
}
|
||||
|
||||
if !seenContributors.Contains(posterID) {
|
||||
contributors = append(contributors, poster)
|
||||
seenContributors.Add(posterID)
|
||||
}
|
||||
|
||||
if seenNew.Contains(posterID) {
|
||||
continue
|
||||
}
|
||||
|
||||
isFirst, err := isFirstContribution(ctx, repoID, posterID, pr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if isFirst {
|
||||
seenNew.Add(posterID)
|
||||
newContributors = append(newContributors, pr)
|
||||
}
|
||||
}
|
||||
|
||||
return contributors, newContributors, nil
|
||||
}
|
||||
|
||||
func isFirstContribution(ctx context.Context, repoID, posterID int64, pr *issues_model.PullRequest) (bool, error) {
|
||||
hasMergedBefore, err := issues_model.HasMergedPullRequestInRepoBefore(ctx, repoID, posterID, pr.MergedUnix, pr.ID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check merged PRs for contributor: %w", err)
|
||||
}
|
||||
return !hasMergedBefore, nil
|
||||
}
|
||||
@ -0,0 +1,216 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package release
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenerateReleaseNotes(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
mergedCommit := "90c1019714259b24fb81711d4416ac0f18667dfa"
|
||||
pr := createMergedPullRequest(t, repo, mergedCommit, 5)
|
||||
|
||||
result, err := GenerateReleaseNotes(t.Context(), repo, gitRepo, GenerateReleaseNotesOptions{
|
||||
TagName: "v1.2.0",
|
||||
Target: "DefaultBranch",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "v1.1", result.PreviousTag)
|
||||
assert.Contains(t, result.Content, "## What's Changed")
|
||||
prURL := pr.Issue.HTMLURL(t.Context())
|
||||
assert.Contains(t, result.Content, fmt.Sprintf("%s in [#%d](%s)", pr.Issue.Title, pr.Index, prURL))
|
||||
assert.Contains(t, result.Content, "## Contributors")
|
||||
assert.Contains(t, result.Content, "@user5")
|
||||
assert.Contains(t, result.Content, "## New Contributors")
|
||||
compareURL := repo.HTMLURL(t.Context()) + "/compare/v1.1...v1.2.0"
|
||||
assert.Contains(t, result.Content, fmt.Sprintf("[v1.1...v1.2.0](%s)", compareURL))
|
||||
}
|
||||
|
||||
func TestGenerateReleaseNotes_NoReleaseFallsBackToTags(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
mergedCommit := "90c1019714259b24fb81711d4416ac0f18667dfa"
|
||||
createMergedPullRequest(t, repo, mergedCommit, 5)
|
||||
|
||||
_, err = db.GetEngine(t.Context()).
|
||||
Where("repo_id=?", repo.ID).
|
||||
Delete(new(repo_model.Release))
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := GenerateReleaseNotes(t.Context(), repo, gitRepo, GenerateReleaseNotesOptions{
|
||||
TagName: "v1.2.0",
|
||||
Target: "DefaultBranch",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "v1.1", result.PreviousTag)
|
||||
assert.Contains(t, result.Content, "@user5")
|
||||
}
|
||||
|
||||
func TestAutoPreviousReleaseTag_UsesPrevPublishedRelease(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
ctx := t.Context()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
prev := insertTestRelease(ctx, t, repo, "auto-prev", timeutil.TimeStamp(100), releaseInsertOptions{})
|
||||
insertTestRelease(ctx, t, repo, "auto-draft", timeutil.TimeStamp(150), releaseInsertOptions{IsDraft: true})
|
||||
insertTestRelease(ctx, t, repo, "auto-pre", timeutil.TimeStamp(175), releaseInsertOptions{IsPrerelease: true})
|
||||
current := insertTestRelease(ctx, t, repo, "auto-current", timeutil.TimeStamp(200), releaseInsertOptions{})
|
||||
|
||||
candidate, err := autoPreviousReleaseTag(ctx, repo, current.TagName)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, prev.TagName, candidate)
|
||||
}
|
||||
|
||||
func TestAutoPreviousReleaseTag_LatestReleaseFallback(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
ctx := t.Context()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
latest := insertTestRelease(ctx, t, repo, "auto-latest", timeutil.TimeStampNow(), releaseInsertOptions{})
|
||||
|
||||
candidate, err := autoPreviousReleaseTag(ctx, repo, "missing-tag")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, latest.TagName, candidate)
|
||||
}
|
||||
|
||||
func TestFindPreviousTagName(t *testing.T) {
|
||||
tags := []*git.Tag{
|
||||
{Name: "v2.0.0"},
|
||||
{Name: "v1.1.0"},
|
||||
{Name: "v1.0.0"},
|
||||
}
|
||||
|
||||
prev, ok := findPreviousTagName(tags, "v1.1.0")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "v1.0.0", prev)
|
||||
|
||||
prev, ok = findPreviousTagName(tags, "v9.9.9")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "v2.0.0", prev)
|
||||
|
||||
_, ok = findPreviousTagName([]*git.Tag{}, "v1.0.0")
|
||||
assert.False(t, ok)
|
||||
|
||||
t.Run("skips newer maintenance tags on older release line for latest release", func(t *testing.T) {
|
||||
tags := []*git.Tag{
|
||||
{Name: "v1.1.4"},
|
||||
{Name: "v1.2.0"},
|
||||
{Name: "v1.1.3"},
|
||||
{Name: "v1.1.1"},
|
||||
{Name: "v1.1.0"},
|
||||
}
|
||||
|
||||
prev, ok := findPreviousTagName(tags, "v1.2.0")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "v1.1.3", prev)
|
||||
})
|
||||
|
||||
t.Run("maintenance release picks previous tag in same line", func(t *testing.T) {
|
||||
tags := []*git.Tag{
|
||||
{Name: "v1.1.4"},
|
||||
{Name: "v1.2.0"},
|
||||
{Name: "v1.1.3"},
|
||||
{Name: "v1.1.1"},
|
||||
{Name: "v1.1.0"},
|
||||
}
|
||||
|
||||
prev, ok := findPreviousTagName(tags, "v1.1.4")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "v1.1.3", prev)
|
||||
})
|
||||
}
|
||||
|
||||
func createMergedPullRequest(t *testing.T, repo *repo_model.Repository, mergeCommit string, posterID int64) *issues_model.PullRequest {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: posterID})
|
||||
|
||||
issue := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Poster: user,
|
||||
PosterID: user.ID,
|
||||
Title: "Release notes test pull request",
|
||||
Content: "content",
|
||||
}
|
||||
|
||||
pr := &issues_model.PullRequest{
|
||||
HeadRepoID: repo.ID,
|
||||
BaseRepoID: repo.ID,
|
||||
HeadBranch: repo.DefaultBranch,
|
||||
BaseBranch: repo.DefaultBranch,
|
||||
Status: issues_model.PullRequestStatusMergeable,
|
||||
Flow: issues_model.PullRequestFlowGithub,
|
||||
}
|
||||
|
||||
require.NoError(t, issues_model.NewPullRequest(t.Context(), repo, issue, nil, nil, pr))
|
||||
|
||||
pr.HasMerged = true
|
||||
pr.MergedCommitID = mergeCommit
|
||||
pr.MergedUnix = timeutil.TimeStampNow()
|
||||
_, err := db.GetEngine(t.Context()).
|
||||
ID(pr.ID).
|
||||
Cols("has_merged", "merged_commit_id", "merged_unix").
|
||||
Update(pr)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, pr.LoadIssue(t.Context()))
|
||||
require.NoError(t, pr.Issue.LoadAttributes(t.Context()))
|
||||
return pr
|
||||
}
|
||||
|
||||
type releaseInsertOptions struct {
|
||||
IsDraft bool
|
||||
IsPrerelease bool
|
||||
IsTag bool
|
||||
}
|
||||
|
||||
func insertTestRelease(ctx context.Context, t *testing.T, repo *repo_model.Repository, tag string, created timeutil.TimeStamp, opts releaseInsertOptions) *repo_model.Release {
|
||||
t.Helper()
|
||||
lower := strings.ToLower(tag)
|
||||
|
||||
release := &repo_model.Release{
|
||||
RepoID: repo.ID,
|
||||
PublisherID: repo.OwnerID,
|
||||
TagName: tag,
|
||||
LowerTagName: lower,
|
||||
Target: repo.DefaultBranch,
|
||||
Title: tag,
|
||||
Sha1: fmt.Sprintf("%040d", int64(created)+time.Now().UnixNano()),
|
||||
IsDraft: opts.IsDraft,
|
||||
IsPrerelease: opts.IsPrerelease,
|
||||
IsTag: opts.IsTag,
|
||||
CreatedUnix: created,
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Insert(release)
|
||||
require.NoError(t, err)
|
||||
|
||||
return release
|
||||
}
|
||||
Loading…
Reference in New Issue