feat(release): allow setting a release as the latest (#40214)

Gitea picks the latest release by commit date, while GitHub keeps an
explicit pointer, so repositories migrated from GitHub could show a
different latest release than their source. Fixes
https://github.com/go-gitea/gitea/issues/35273.

- Add a "Set as Latest Release" checkbox and GitHub's `make_latest` API
option. Without them, the newest stable release by commit date stays the
latest.
- Migrations from GitHub and Gitea carry over the source's latest
release.
- Migrated releases are dated by their commit like native ones, and
equally dated ones keep the source's order.

<img width="355" height="122" alt="Set as Latest Release checkbox"
src="https://github.com/user-attachments/assets/d35f5324-7ae0-4e9f-b73c-d3c90496fb00"
/>

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Shivansh Garg
2026-11-06 08:23:13 +00:00
committed by GitHub
parent 98c937170f
commit a5201dc427
35 changed files with 406 additions and 76 deletions

View File

@@ -334,18 +334,32 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
return tags, sess.Find(&tags)
}
// GetLatestReleaseByRepoID returns the latest release for a repository
func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) {
cond := builder.NewCond().
And(builder.Eq{"repo_id": repoID}).
And(builder.Eq{"is_draft": false}).
And(builder.Eq{"is_prerelease": false}).
And(builder.Eq{"is_tag": false})
func (r *Release) CanBeLatest() bool {
return !r.IsDraft && !r.IsPrerelease && !r.IsTag
}
func latestReleaseCond(repoID int64) builder.Cond {
return FindReleasesOptions{RepoID: repoID, IsPreRelease: optional.Some(false)}.ToConds()
}
func GetLatestRelease(ctx context.Context, repo *Repository) (*Release, error) {
if repo.LatestReleaseID != 0 {
rel, has, err := db.Get[Release](ctx, latestReleaseCond(repo.ID).And(builder.Eq{"id": repo.LatestReleaseID}))
if err != nil {
return nil, err
} else if has {
return rel, nil
}
}
return GetLatestReleaseByRepoID(ctx, repo.ID)
}
// GetLatestReleaseByRepoID returns the newest published stable release of a repository by date
func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) {
rel := new(Release)
has, err := db.GetEngine(ctx).
Desc("created_unix", "id").
Where(cond).
Where(latestReleaseCond(repoID)).
Get(rel)
if err != nil {
return nil, err
@@ -356,6 +370,27 @@ func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, erro
return rel, nil
}
func SetLatestReleaseID(ctx context.Context, repo *Repository, releaseID int64) error {
repo.LatestReleaseID = releaseID
_, err := db.GetEngine(ctx).ID(repo.ID).Where(builder.Neq{"latest_release_id": releaseID}).
Cols("latest_release_id").NoAutoTime().Update(repo)
return err
}
func UnsetLatestReleaseID(ctx context.Context, repo *Repository, releaseID int64) error {
if releaseID == 0 {
return nil
}
if _, err := db.GetEngine(ctx).Where(builder.Eq{"id": repo.ID, "latest_release_id": releaseID}).
Cols("latest_release_id").NoAutoTime().Update(&Repository{LatestReleaseID: 0}); err != nil {
return err
}
if repo.LatestReleaseID == releaseID {
repo.LatestReleaseID = 0
}
return nil
}
type releaseMetaSearch struct {
IDs []int64
Releases []*Release

View File

@@ -209,6 +209,7 @@ type Repository struct {
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"`
ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"`
LatestReleaseID int64 `xorm:"NOT NULL DEFAULT 0"` // 0 selects the latest release by date
TrustModel TrustModelType