fix(release): separate publication time from the release date (#36761)

`published_at` was an alias for `created_at`, so a release created from
an existing tag reported that tag's commit date as its publication time,
and drafts reported one despite never having been published. It is now
stored separately, set when a release is published and null for drafts.

`created_at` in turn means the date of the commit the release points at,
matching what GitHub documents it to be, and the latest release is
selected by it again. Publishing a release for an old commit no longer
takes over the latest badge, and a tag created in the web UI is dated
the same way as one pushed from the CLI.

Fixes https://github.com/go-gitea/gitea/issues/11206
Fixes https://github.com/go-gitea/gitea/issues/38714
Fixes https://github.com/go-gitea/gitea/issues/31789

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
silverwind
2026-08-22 08:59:37 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 6bb6ce678b
commit fa0b39a42b
22 changed files with 238 additions and 67 deletions
+1
View File
@@ -423,6 +423,7 @@ func prepareMigrationTasks() []*migration {
newMigration(347, "Add watch options", v28.AddWatchOptions),
newMigration(348, "Recreate email_hash table for SHA256 avatar hashes", v28.RecreateEmailHashTable),
newMigration(349, "Expand action_schedule content column", v28.ExpandActionScheduleContent),
newMigration(350, "Add published_unix column to release", v28.AddPublishedUnixToRelease),
}
return preparedMigrations
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v28
import (
"context"
"gitea.dev/modelmigration/base"
"xorm.io/xorm"
)
func AddPublishedUnixToRelease(_ context.Context, x base.EngineMigration) error {
type Release struct {
PublishedUnix int64 `xorm:"NOT NULL DEFAULT 0"`
}
if _, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreDropIndices: true,
}, new(Release)); err != nil {
return err
}
// existing rows have no recorded publication time, so fall back to their creation time
_, err := x.Exec("UPDATE `release` SET published_unix = created_unix WHERE published_unix = 0 AND is_draft = ?", false)
return err
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v28
import (
"testing"
"gitea.dev/modelmigration/migrationtest"
"github.com/stretchr/testify/require"
)
func TestAddPublishedUnixToRelease(t *testing.T) {
type Release struct {
ID int64 `xorm:"pk autoincr"`
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
IsTag bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix int64 `xorm:"INDEX"`
}
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(Release))
defer deferable()
if x == nil || t.Failed() {
return
}
_, err := x.Insert(
&Release{CreatedUnix: 1000000},
&Release{IsDraft: true, CreatedUnix: 2000000},
&Release{IsTag: true, CreatedUnix: 3000000},
)
require.NoError(t, err)
require.NoError(t, AddPublishedUnixToRelease(t.Context(), x))
var got []struct{ PublishedUnix int64 }
require.NoError(t, x.Table("release").OrderBy("id").Find(&got))
require.Equal(t, []int64{1000000, 0, 3000000}, []int64{got[0].PublishedUnix, got[1].PublishedUnix, got[2].PublishedUnix},
"everything but drafts is backfilled")
}
+2 -2
View File
@@ -367,7 +367,7 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from
// Published releases list
sess := releasesForActivityStatement(ctx, repoID, fromTime)
sess.OrderBy("`release`.created_unix DESC")
sess.OrderBy("`release`.published_unix DESC")
stats.PublishedReleases = make([]*repo_model.Release, 0)
if err = sess.Find(&stats.PublishedReleases); err != nil {
return err
@@ -386,5 +386,5 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from
func releasesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time) db.Session {
return db.GetEngine(ctx).Where("`release`.repo_id = ?", repoID).
And("`release`.is_draft = ?", false).
And("`release`.created_unix >= ?", fromTime.Unix())
And("`release`.published_unix >= ?", fromTime.Unix())
}
+10
View File
@@ -11,6 +11,7 @@
is_prerelease: false
is_tag: false
created_unix: 946684800
published_unix: 946684800
- id: 2
repo_id: 40
@@ -25,6 +26,7 @@
is_prerelease: false
is_tag: false
created_unix: 946684800
published_unix: 946684800
- id: 3
repo_id: 1
@@ -39,6 +41,7 @@
is_prerelease: false
is_tag: true
created_unix: 946684800
published_unix: 946684800
- id: 4
repo_id: 1
@@ -66,6 +69,7 @@
is_prerelease: true
is_tag: false
created_unix: 946684800
published_unix: 946684800
- id: 6
repo_id: 57
@@ -80,6 +84,7 @@
is_prerelease: false
is_tag: false
created_unix: 946684801
published_unix: 946684801
- id: 7
repo_id: 57
@@ -94,6 +99,7 @@
is_prerelease: false
is_tag: false
created_unix: 946684802
published_unix: 946684802
- id: 8
repo_id: 57
@@ -108,6 +114,7 @@
is_prerelease: false
is_tag: false
created_unix: 946684803
published_unix: 946684803
- id: 9
repo_id: 57
@@ -122,6 +129,7 @@
is_prerelease: false
is_tag: false
created_unix: 946684803
published_unix: 946684803
- id: 10
repo_id: 57
@@ -136,6 +144,7 @@
is_prerelease: false
is_tag: false
created_unix: 946684803
published_unix: 946684803
- id: 11
repo_id: 2
@@ -150,5 +159,6 @@
is_prerelease: false
is_tag: false
created_unix: 946684803
published_unix: 946684803
# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly
+2 -1
View File
@@ -87,6 +87,7 @@ type Release struct {
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
Attachments []*Attachment `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
PublishedUnix timeutil.TimeStamp `xorm:"NOT NULL DEFAULT 0"`
}
func init() {
@@ -473,7 +474,7 @@ func PushUpdateDeleteTags(ctx context.Context, repo *Repository, tags []string)
if _, err := db.GetEngine(ctx).
Where("repo_id = ? AND is_tag = ?", repo.ID, false).
In("lower_tag_name", lowerTags).
Cols("is_draft", "num_commits", "sha1").
Cols("is_draft", "num_commits", "sha1", "published_unix").
Update(&Release{
IsDraft: true,
}); err != nil {
+1 -1
View File
@@ -103,7 +103,7 @@ func (p *Parser) parseRef(refBlock string) (map[string]string, error) {
return nil, nil //nolint:nilnil // return nil to signal EOF
}
fieldValues := make(map[string]string)
fieldValues := make(map[string]string, len(p.format.fieldNames))
fields := strings.Split(refBlock, p.format.fieldDelimStr)
if len(fields) != len(p.format.fieldNames) {
+12 -1
View File
@@ -7,7 +7,9 @@ package git
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"gitea.dev/modules/git/foreachref"
"gitea.dev/modules/git/gitcmd"
@@ -113,7 +115,7 @@ func (repo *Repository) GetTagWithID(ctx context.Context, idStr, name string) (*
func (repo *Repository) GetTagInfos(ctx context.Context, page, pageSize int) ([]*Tag, int, error) {
// Generally, refname:short should be equal to refname:lstrip=2 except core.warnAmbiguousRefs is used to select the strict abbreviation mode.
// https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname
forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature")
forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature", "committerdate:unix", "*committerdate:unix")
var tags []*Tag
var tagsTotal int
@@ -179,6 +181,15 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {
tag.Tagger = parseSignatureFromCommitLine(ref["creator"])
tag.MessageRaw = ref["contents"]
// a lightweight tag reports the commit date directly, an annotated one only behind the dereferencing "*"
if committerDate := util.IfZero(ref["*committerdate:unix"], ref["committerdate:unix"]); committerDate != "" {
seconds, err := strconv.ParseInt(committerDate, 10, 64)
if err != nil {
return nil, fmt.Errorf("parse committerdate '%s': %w", committerDate, err)
}
tag.CommitDate = time.Unix(seconds, 0)
}
// strip any signature if present in contents field
_, tag.MessageRaw, _ = parsePayloadSignature(util.UnsafeStringToBytes(tag.MessageRaw), 0)
+10 -1
View File
@@ -6,6 +6,7 @@ package git
import (
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -33,6 +34,7 @@ func TestRepository_GetTagInfos(t *testing.T) {
assert.Equal(t, "test", tags[1].Name)
assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String())
assert.Equal(t, "tag", tags[1].Type)
assert.False(t, tags[0].CommitDate.IsZero(), "for-each-ref resolves the tagged commit's date")
}
func TestRepository_GetTag(t *testing.T) {
@@ -207,7 +209,9 @@ func TestRepository_parseTagRef(t *testing.T) {
* add changelog of v1.9.1
* Update CHANGELOG.md
`,
"contents:signature": "",
"contents:signature": "",
"committerdate:unix": "1565789218",
"*committerdate:unix": "",
},
want: &Tag{
@@ -216,6 +220,7 @@ func TestRepository_parseTagRef(t *testing.T) {
Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Type: "commit",
Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
CommitDate: time.Unix(1565789218, 0),
CommitMessage: CommitMessage{MessageRaw: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n"},
Signature: nil,
},
@@ -237,6 +242,9 @@ func TestRepository_parseTagRef(t *testing.T) {
* Update CHANGELOG.md
`,
"contents:signature": "",
// an annotated tag can be made long after the commit, so its own date is not the commit date
"committerdate:unix": "",
"*committerdate:unix": "1565700000",
},
want: &Tag{
@@ -245,6 +253,7 @@ func TestRepository_parseTagRef(t *testing.T) {
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag",
Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
CommitDate: time.Unix(1565700000, 0),
CommitMessage: CommitMessage{MessageRaw: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n"},
Signature: nil,
},
+3
View File
@@ -7,6 +7,7 @@ import (
"bytes"
"context"
"sort"
"time"
"gitea.dev/modules/util"
)
@@ -21,6 +22,8 @@ type Tag struct {
Type string
Tagger *Signature
Signature *CommitSignature
CommitDate time.Time // committer date of Object, only GetTagInfos resolves it
}
func parsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) {
+9 -6
View File
@@ -18,6 +18,7 @@ import (
"gitea.dev/modules/log"
"gitea.dev/modules/setting"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
)
/*
@@ -245,9 +246,10 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
LowerTagName: strings.ToLower(tag.Name),
Sha1: tag.Object.String(),
// NOTE: ignored, The NumCommits value is calculated and cached on demand when the UI requires it.
NumCommits: -1,
CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
IsTag: true,
NumCommits: -1,
CreatedUnix: timeutil.TimeStamp(util.IfZero(tag.CommitDate, tag.Tagger.When).Unix()),
PublishedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
IsTag: true,
}
if err := db.Insert(ctx, release); err != nil {
return fmt.Errorf("unable insert tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
@@ -265,10 +267,11 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
for _, tag := range updates {
if _, err := db.GetEngine(ctx).Where("repo_id = ? AND lower_tag_name = ?", repo.ID, strings.ToLower(tag.Name)).
Cols("sha1", "created_unix").
Cols("sha1", "created_unix", "published_unix").
Update(&repo_model.Release{
Sha1: tag.Object.String(),
CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
Sha1: tag.Object.String(),
CreatedUnix: timeutil.TimeStamp(util.IfZero(tag.CommitDate, tag.Tagger.When).Unix()),
PublishedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
}); err != nil {
return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
}
+1 -1
View File
@@ -36,7 +36,7 @@ type Release struct {
// swagger:strfmt date-time
CreatedAt time.Time `json:"created_at"`
// swagger:strfmt date-time
PublishedAt time.Time `json:"published_at"`
PublishedAt *time.Time `json:"published_at"`
// The user who published the release
Publisher *User `json:"author"`
// The files attached to the release
+1 -1
View File
@@ -301,7 +301,7 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (
items = append(items, &feeds.Item{
Title: title,
Link: link,
Created: rel.CreatedUnix.AsTime(),
Created: rel.PublishedUnix.AsTime(),
Author: &feeds.Author{
Name: rel.Publisher.GetDisplayName(),
Email: rel.Publisher.GetEmail(),
+2 -1
View File
@@ -8,6 +8,7 @@ import (
repo_model "gitea.dev/models/repo"
api "gitea.dev/modules/structs"
"gitea.dev/modules/util"
)
// ToAPIRelease convert a repo_model.Release to api.Release
@@ -26,7 +27,7 @@ func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_mode
IsDraft: r.IsDraft,
IsPrerelease: r.IsPrerelease,
CreatedAt: r.CreatedUnix.AsTime(),
PublishedAt: r.CreatedUnix.AsTime(),
PublishedAt: util.Iif(r.IsDraft, nil, r.PublishedUnix.AsTimePtr()),
Publisher: ToUser(ctx, r.Publisher, nil),
Attachments: ToAPIAttachments(repo, r.Attachments),
}
+4
View File
@@ -24,4 +24,8 @@ func TestRelease_ToRelease(t *testing.T) {
assert.EqualValues(t, 1, apiRelease.ID)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1", apiRelease.URL)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1/assets", apiRelease.UploadURL)
assert.Equal(t, release1.PublishedUnix.AsTimePtr(), apiRelease.PublishedAt)
release1.IsDraft = true
assert.Nil(t, ToAPIRelease(t.Context(), repo1, release1).PublishedAt, "an unpublished release has no publication date")
}
+13 -10
View File
@@ -278,17 +278,20 @@ func (g *GiteaLocalUploader) CreateReleases(ctx context.Context, releases ...*ba
release.TargetCommitish = ""
}
publishedAt := util.Iif(release.Published.IsZero(), release.Created, release.Published)
rel := repo_model.Release{
RepoID: g.repo.ID,
TagName: release.TagName,
LowerTagName: strings.ToLower(release.TagName),
Target: release.TargetCommitish,
Title: release.Name,
Note: release.Body,
IsDraft: release.Draft,
IsPrerelease: release.Prerelease,
IsTag: false,
CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
RepoID: g.repo.ID,
TagName: release.TagName,
LowerTagName: strings.ToLower(release.TagName),
Target: release.TargetCommitish,
Title: release.Name,
Note: release.Body,
IsDraft: release.Draft,
IsPrerelease: release.Prerelease,
IsTag: false,
CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
PublishedUnix: util.Iif(release.Draft, 0, timeutil.TimeStamp(publishedAt.Unix())),
}
if err := g.remapUser(ctx, release, &rel); err != nil {
+10 -1
View File
@@ -140,7 +140,9 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel
NewCommitID: commit.ID.String(),
}, commits)
notify_service.CreateRef(ctx, rel.Publisher, rel.Repo, refFullName, commit.ID.String())
rel.CreatedUnix = timeutil.TimeStampNow()
}
if rel.PublishedUnix.IsZero() {
rel.PublishedUnix = timeutil.TimeStampNow()
}
commit, err := gitRepo.GetTagCommit(ctx, rel.TagName)
if err != nil {
@@ -148,6 +150,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel
}
rel.Sha1 = commit.ID.String()
rel.CreatedUnix = timeutil.TimeStamp(commit.Committer.When.Unix()) // dated by its commit, so an old commit does not become the latest release
rel.NumCommits, err = git.CommitsCountOfCommit(ctx, rel.Repo, commit.ID.String())
if err != nil {
return false, fmt.Errorf("CommitsCount: %w", err)
@@ -273,6 +276,12 @@ func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repo
return err
}
isConvertedFromTag := oldRelease.IsTag && !rel.IsTag
// a zero PublishedUnix means "draft", so withdrawing a release has to clear it again
if rel.IsDraft {
rel.PublishedUnix = 0
} else if isConvertedFromTag || oldRelease.IsDraft {
rel.PublishedUnix = timeutil.TimeStampNow()
}
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
+46
View File
@@ -165,12 +165,14 @@ func TestRelease_Update(t *testing.T) {
release, err := repo_model.GetRelease(t.Context(), repo.ID, "v1.1.1")
assert.NoError(t, err)
releaseCreatedUnix := release.CreatedUnix
releasePublishedUnix := release.PublishedUnix
advance()
release.Note = "Changed note"
assert.NoError(t, UpdateRelease(t.Context(), user, gitRepo, release, nil, nil, nil))
release, err = repo_model.GetReleaseByID(t.Context(), release.ID)
assert.NoError(t, err)
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
assert.Equal(t, releasePublishedUnix, release.PublishedUnix, "editing does not republish")
// Test a changed draft
assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{
@@ -195,6 +197,19 @@ func TestRelease_Update(t *testing.T) {
release, err = repo_model.GetReleaseByID(t.Context(), release.ID)
assert.NoError(t, err)
assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
assert.Zero(t, release.PublishedUnix, "a draft is unpublished")
// Test publishing and withdrawing that draft
release.IsDraft = false
assert.NoError(t, UpdateRelease(t.Context(), user, gitRepo, release, nil, nil, nil))
release, err = repo_model.GetReleaseByID(t.Context(), release.ID)
assert.NoError(t, err)
assert.NotZero(t, release.PublishedUnix, "publishing stamps the publication time")
release.IsDraft = true
assert.NoError(t, UpdateRelease(t.Context(), user, gitRepo, release, nil, nil, nil))
release, err = repo_model.GetReleaseByID(t.Context(), release.ID)
assert.NoError(t, err)
assert.Zero(t, release.PublishedUnix, "withdrawing unpublishes it again")
// Test a changed pre-release
assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{
@@ -387,3 +402,34 @@ func TestCreateNewTag(t *testing.T) {
assert.NoError(t, CreateNewTag(t.Context(), user, repo, "master", "v2.0",
"v2.0 is released \n\n BUGFIX: .... \n\n 123"))
}
func TestRelease_DatedByTargetCommit(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
gitRepo, err := git.OpenRepository(t.Context(), repo)
assert.NoError(t, err)
defer gitRepo.Close()
newRelease := func(tagName, target string) *repo_model.Release {
rel := &repo_model.Release{
RepoID: repo.ID, Repo: repo, PublisherID: user.ID, Publisher: user,
TagName: tagName, Target: target, Title: tagName,
}
assert.NoError(t, CreateRelease(t.Context(), gitRepo, rel, nil, ""))
return rel
}
recent := newRelease("v9.9-recent", "DefaultBranch")
// released afterwards, but from an older commit, so it must not take over as the latest release
old := newRelease("v9.9-old", "master")
oldCommit, err := gitRepo.GetBranchCommit(t.Context(), "master")
assert.NoError(t, err)
assert.Equal(t, oldCommit.Committer.When.Unix(), int64(old.CreatedUnix), "a release is dated by the commit it points at")
assert.Greater(t, int64(old.PublishedUnix), int64(old.CreatedUnix), "but its publication time is now")
latest, err := repo_model.GetLatestReleaseByRepoID(t.Context(), repo.ID)
assert.NoError(t, err)
assert.Equal(t, recent.ID, latest.ID)
}
+23 -25
View File
@@ -368,47 +368,45 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
return fmt.Errorf("Commit: %w", err)
}
sig := tag.Tagger
if sig == nil {
sig = commit.Author
}
if sig == nil {
sig = commit.Committer
}
createdAt := time.Unix(1, 0)
if sig != nil {
createdAt = sig.When
createdUnix := timeutil.TimeStamp(commit.Committer.When.Unix()) // tagged whenever, but dated by its commit
publishedUnix := createdUnix
if tag.Tagger != nil {
publishedUnix = timeutil.TimeStamp(tag.Tagger.When.Unix())
}
rel, has := relMap[lowerTag]
title, note := git.SplitCommitTitleBody(tag.MessageUTF8(), 255)
if !has {
rel = &repo_model.Release{
RepoID: repo.ID,
Title: title,
TagName: tags[i],
LowerTagName: lowerTag,
Target: "",
Sha1: commit.ID.String(),
NumCommits: -1, // the commits count will be updated when the UI needs it
Note: note,
IsDraft: false,
IsPrerelease: false,
IsTag: true,
PublisherID: pusher.ID,
CreatedUnix: timeutil.TimeStamp(createdAt.Unix()),
RepoID: repo.ID,
Title: title,
TagName: tags[i],
LowerTagName: lowerTag,
Target: "",
Sha1: commit.ID.String(),
NumCommits: -1, // the commits count will be updated when the UI needs it
Note: note,
IsDraft: false,
IsPrerelease: false,
IsTag: true,
PublisherID: pusher.ID,
CreatedUnix: createdUnix,
PublishedUnix: publishedUnix,
}
newReleases = append(newReleases, rel)
} else {
rel.Sha1 = commit.ID.String()
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
rel.CreatedUnix = createdUnix
if rel.IsTag {
rel.Title = title
rel.Note = note
rel.PublishedUnix = publishedUnix
} else {
rel.IsDraft = false
if rel.PublishedUnix.IsZero() {
rel.PublishedUnix = timeutil.TimeStampNow()
}
}
rel.PublisherID = pusher.ID
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
+1 -1
View File
@@ -15,7 +15,7 @@
{{template "repo/release/label" (dict "Release" .LatestRelease "IsLatest" true)}}
</div>
</div>
<div class="tw-ml-[24px] tw-text-12">{{DateUtils.TimeSince .LatestRelease.CreatedUnix}}</div>
<div class="tw-ml-[24px] tw-text-12">{{DateUtils.TimeSince .LatestRelease.PublishedUnix}}</div>
</div>
{{end}}
+5 -5
View File
@@ -59,11 +59,11 @@
Ghost
{{end}}
</span>
<span class="released">
{{ctx.Locale.Tr "repo.released_this"}}
</span>
{{if $release.CreatedUnix}}
<span class="time">{{DateUtils.TimeSince $release.CreatedUnix}}</span>
{{if not $release.IsDraft}}
<span class="released">
{{ctx.Locale.Tr "repo.released_this"}}
</span>
<span class="time">{{DateUtils.TimeSince $release.PublishedUnix}}</span>
{{end}}
{{if and (gt $release.NumCommits 0) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span>
+13 -10
View File
@@ -46,18 +46,21 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()))
}
func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, version, label string, count int) {
// returns the first listed title, which is not necessarily the one just created because releases are ordered by commit date
func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, version, label string, count int) string {
req := NewRequest(t, "GET", repoURL+"/releases")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
labelText := htmlDoc.doc.Find("#release-list > li .detail .label").First().Text()
assert.Equal(t, label, labelText)
titleText := htmlDoc.doc.Find("#release-list > li .detail h4 a").First().Text()
assert.Equal(t, version, titleText)
releaseList := htmlDoc.doc.Find("#release-list > li")
releaseList := NewHTMLParser(t, resp.Body).doc.Find("#release-list > li")
assert.Equal(t, count, releaseList.Length())
item := releaseList.FilterFunction(func(_ int, selection *goquery.Selection) bool {
return selection.Find(".detail h4 a").Text() == version
})
if assert.Equal(t, 1, item.Length(), "release %q is listed exactly once", version) {
assert.Equal(t, label, item.Find(".detail .label").First().Text())
}
return releaseList.Find(".detail h4 a").First().Text()
}
func TestViewReleases(t *testing.T) {
@@ -113,11 +116,11 @@ func TestCreateReleasePaging(t *testing.T) {
}
createNewRelease(t, session, "/user2/repo1", "v0.0.12", "v0.0.12", false, true)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.12", translation.NewLocale("en-US").TrString("repo.release.draft"), 10)
assert.Equal(t, "v0.0.12", checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.12", translation.NewLocale("en-US").TrString("repo.release.draft"), 10))
// Check that user4 does not see draft and still see 10 latest releases
session2 := loginUser(t, "user4")
checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", translation.NewLocale("en-US").TrString("repo.release.stable"), 10)
assert.Equal(t, "v0.0.11", checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", translation.NewLocale("en-US").TrString("repo.release.stable"), 10))
}
func TestViewReleaseListNoLogin(t *testing.T) {