mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-10 06:53:25 +09:00
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:
co-authored by
Claude Opus 4.6
parent
6bb6ce678b
commit
fa0b39a42b
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user