Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ var migrations = []Migration{
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
// v291 -> v292
NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
// v292 -> v293
NewMigration("Add repo_archive_download_count table", v1_22.AddRepoArchiveDownloadCount),
}

// GetCurrentDBVersion returns the current db version
Expand Down
20 changes: 20 additions & 0 deletions models/migrations/v1_22/v292.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_22 //nolint

import (
"xorm.io/xorm"
)

func AddRepoArchiveDownloadCount(x *xorm.Engine) error {
type RepoArchiveDownloadCount struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"index unique(s)"`
ReleaseID int64 `xorm:"index unique(s)"`
Type int `xorm:"unique(s)"`
Count int64
}

return x.Sync(&RepoArchiveDownloadCount{})
}
87 changes: 87 additions & 0 deletions models/repo/archive_download_count.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package repo

import (
"context"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
)

// RepoArchiveDownloadCount counts all archive downloads for a tag
type RepoArchiveDownloadCount struct { //nolint:revive
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"index unique(s)"`
ReleaseID int64 `xorm:"index unique(s)"`
Type git.ArchiveType `xorm:"unique(s)"`
Count int64
}

func init() {
db.RegisterModel(new(RepoArchiveDownloadCount))
}

// CountArchiveDownload adds one download the the given archive
func CountArchiveDownload(ctx context.Context, repoID, releaseID int64, tp git.ArchiveType) error {
updateCount, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("release_id = ?", releaseID).And("`type` = ?", tp).Incr("count").Update(new(RepoArchiveDownloadCount))
if err != nil {
return err
}

if updateCount != 0 {
// The count was updated, so we can exit
return nil
}

// The archive does not esxists in the databse, so let's add it
newCounter := &RepoArchiveDownloadCount{
RepoID: repoID,
ReleaseID: releaseID,
Type: tp,
Count: 1,
}

_, err = db.GetEngine(ctx).Insert(newCounter)
return err
}

// GetArchiveDownloadCount returns the download count of a tag
func GetArchiveDownloadCount(ctx context.Context, repoID, releaseID int64) (*api.TagArchiveDownloadCount, error) {
downloadCountList := make([]RepoArchiveDownloadCount, 0)
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("release_id = ?", releaseID).Find(&downloadCountList)
if err != nil {
return nil, err
}

tagCounter := new(api.TagArchiveDownloadCount)

for _, singleCount := range downloadCountList {
switch singleCount.Type {
case git.ZIP:
tagCounter.Zip = singleCount.Count
case git.TARGZ:
tagCounter.TarGz = singleCount.Count
}
}

return tagCounter, nil
}

// GetDownloadCountForTagName returns the download count of a tag with the given name
func GetArchiveDownloadCountForTagName(ctx context.Context, repoID int64, tagName string) (*api.TagArchiveDownloadCount, error) {
release, err := GetRelease(ctx, repoID, tagName)
if err != nil {
return nil, err
}

return GetArchiveDownloadCount(ctx, repoID, release.ID)
}

// DeleteArchiveDownloadCountForRelease deletes the release from the repo_archive_download_count table
func DeleteArchiveDownloadCountForRelease(ctx context.Context, releaseID int64) error {
_, err := db.GetEngine(ctx).Delete(&RepoArchiveDownloadCount{ReleaseID: releaseID})
return err
}
65 changes: 65 additions & 0 deletions models/repo/archive_download_count_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package repo_test

import (
"testing"

"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRepoArchiveDownloadCount(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

release, err := repo_model.GetReleaseByID(db.DefaultContext, 1)
require.NoError(t, err)

// We have no count, so it should return 0
downloadCount, err := repo_model.GetArchiveDownloadCount(db.DefaultContext, release.RepoID, release.ID)
require.NoError(t, err)
assert.Equal(t, int64(0), downloadCount.Zip)
assert.Equal(t, int64(0), downloadCount.TarGz)

// Set the TarGz counter to 1
err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.TARGZ)
require.NoError(t, err)

downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
require.NoError(t, err)
assert.Equal(t, int64(0), downloadCount.Zip)
assert.Equal(t, int64(1), downloadCount.TarGz)

// Set the TarGz counter to 2
err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.TARGZ)
require.NoError(t, err)

downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
require.NoError(t, err)
assert.Equal(t, int64(0), downloadCount.Zip)
assert.Equal(t, int64(2), downloadCount.TarGz)

// Set the Zip counter to 1
err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.ZIP)
require.NoError(t, err)

downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
require.NoError(t, err)
assert.Equal(t, int64(1), downloadCount.Zip)
assert.Equal(t, int64(2), downloadCount.TarGz)

// Delete the count
err = repo_model.DeleteArchiveDownloadCountForRelease(db.DefaultContext, release.ID)
require.NoError(t, err)

downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName)
require.NoError(t, err)
assert.Equal(t, int64(0), downloadCount.Zip)
assert.Equal(t, int64(0), downloadCount.TarGz)
}
1 change: 1 addition & 0 deletions models/repo/archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type RepoArchiver struct { //revive:disable-line:exported
Status ArchiverStatus
CommitID string `xorm:"VARCHAR(64) unique(s)"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
ReleaseID int64 `xorm:"-"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just adding a count column here rather than having a new struct for the counting?

}

func init() {
Expand Down
70 changes: 48 additions & 22 deletions models/repo/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,28 +65,29 @@ func (err ErrReleaseNotExist) Unwrap() error {

// Release represents a release of repository.
type Release struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(n)"`
Repo *Repository `xorm:"-"`
PublisherID int64 `xorm:"INDEX"`
Publisher *user_model.User `xorm:"-"`
TagName string `xorm:"INDEX UNIQUE(n)"`
OriginalAuthor string
OriginalAuthorID int64 `xorm:"index"`
LowerTagName string
Target string
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
Title string
Sha1 string `xorm:"VARCHAR(64)"`
NumCommits int64
NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"`
RenderedNote template.HTML `xorm:"-"`
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
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"`
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(n)"`
Repo *Repository `xorm:"-"`
PublisherID int64 `xorm:"INDEX"`
Publisher *user_model.User `xorm:"-"`
TagName string `xorm:"INDEX UNIQUE(n)"`
OriginalAuthor string
OriginalAuthorID int64 `xorm:"index"`
LowerTagName string
Target string
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
Title string
Sha1 string `xorm:"VARCHAR(64)"`
NumCommits int64
NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"`
RenderedNote template.HTML `xorm:"-"`
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
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"`
ArchiveDownloadCount *structs.TagArchiveDownloadCount `xorm:"-"`
}

func init() {
Expand All @@ -112,9 +113,22 @@ func (r *Release) LoadAttributes(ctx context.Context) error {
}
}
}

err = r.LoadArchiveDownloadCount(ctx)
if err != nil {
return err
}

return GetReleaseAttachments(ctx, r)
}

// LoadArchiveDownloadCount loads the download count for the source archives
func (r *Release) LoadArchiveDownloadCount(ctx context.Context) error {
var err error
r.ArchiveDownloadCount, err = GetArchiveDownloadCount(ctx, r.RepoID, r.ID)
return err
}

// APIURL the api url for a release. release must have attributes loaded
func (r *Release) APIURL() string {
return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10)
Expand Down Expand Up @@ -447,6 +461,18 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s
lowerTags = append(lowerTags, strings.ToLower(tag))
}

for _, tag := range tags {
release, err := GetRelease(ctx, repo.ID, tag)
if err != nil {
return fmt.Errorf("GetRelease: %w", err)
}

err = DeleteArchiveDownloadCountForRelease(ctx, release.ID)
if err != nil {
return fmt.Errorf("DeleteTagArchiveDownloadCount: %w", err)
}
}

if _, err := db.GetEngine(ctx).
Where("repo_id = ? AND is_tag = ?", repo.ID, true).
In("lower_tag_name", lowerTags).
Expand Down
16 changes: 9 additions & 7 deletions modules/git/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sort"
"strings"

api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
)

Expand All @@ -18,13 +19,14 @@ const (

// Tag represents a Git tag.
type Tag struct {
Name string
ID ObjectID
Object ObjectID // The id of this commit object
Type string
Tagger *Signature
Message string
Signature *CommitGPGSignature
Name string
ID ObjectID
Object ObjectID // The id of this commit object
Type string
Tagger *Signature
Message string
Signature *CommitGPGSignature
ArchiveDownloadCount *api.TagArchiveDownloadCount
}

// Commit return the commit of the tag reference
Expand Down
7 changes: 4 additions & 3 deletions modules/structs/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ type Release struct {
// swagger:strfmt date-time
CreatedAt time.Time `json:"created_at"`
// swagger:strfmt date-time
PublishedAt time.Time `json:"published_at"`
Publisher *User `json:"author"`
Attachments []*Attachment `json:"assets"`
PublishedAt time.Time `json:"published_at"`
Publisher *User `json:"author"`
Attachments []*Attachment `json:"assets"`
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"`
}

// CreateReleaseOption options when creating a release
Expand Down
34 changes: 21 additions & 13 deletions modules/structs/repo_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@ package structs

// Tag represents a repository tag
type Tag struct {
Name string `json:"name"`
Message string `json:"message"`
ID string `json:"id"`
Commit *CommitMeta `json:"commit"`
ZipballURL string `json:"zipball_url"`
TarballURL string `json:"tarball_url"`
Name string `json:"name"`
Message string `json:"message"`
ID string `json:"id"`
Commit *CommitMeta `json:"commit"`
ZipballURL string `json:"zipball_url"`
TarballURL string `json:"tarball_url"`
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"`
}

// AnnotatedTag represents an annotated tag
type AnnotatedTag struct {
Tag string `json:"tag"`
SHA string `json:"sha"`
URL string `json:"url"`
Message string `json:"message"`
Tagger *CommitUser `json:"tagger"`
Object *AnnotatedTagObject `json:"object"`
Verification *PayloadCommitVerification `json:"verification"`
Tag string `json:"tag"`
SHA string `json:"sha"`
URL string `json:"url"`
Message string `json:"message"`
Tagger *CommitUser `json:"tagger"`
Object *AnnotatedTagObject `json:"object"`
Verification *PayloadCommitVerification `json:"verification"`
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"`
}

// AnnotatedTagObject contains meta information of the tag object
Expand All @@ -38,3 +40,9 @@ type CreateTagOption struct {
Message string `json:"message"`
Target string `json:"target"`
}

// TagArchiveDownloadCount counts how many times a archive was downloaded
type TagArchiveDownloadCount struct {
Zip int64 `json:"zip"`
TarGz int64 `json:"tar_gz"`
}
2 changes: 1 addition & 1 deletion routers/api/v1/repo/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func GetArchive(ctx *context.APIContext) {

func archiveDownload(ctx *context.APIContext) {
uri := ctx.Params("*")
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
Expand Down
Loading