From 88127fbad7401068acef5ff65c1613384c41c650 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Fri, 30 Jun 2023 10:43:21 +0200 Subject: [PATCH 01/16] Count downloads for tag archives --- models/repo/archive_download_count.go | 77 ++++++++++++++++++++++++ models/repo/archiver.go | 1 + models/repo/release.go | 58 +++++++++++------- modules/structs/release.go | 7 ++- modules/structs/repo_tag.go | 6 ++ routers/web/repo/release.go | 11 ++++ routers/web/repo/repo.go | 8 +++ services/convert/release.go | 31 +++++----- services/repository/archiver/archiver.go | 4 ++ templates/repo/release/list.tmpl | 6 ++ templates/swagger/v1_json.tmpl | 20 ++++++ 11 files changed, 189 insertions(+), 40 deletions(-) create mode 100644 models/repo/archive_download_count.go diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go new file mode 100644 index 0000000000000..d4899e25e2332 --- /dev/null +++ b/models/repo/archive_download_count.go @@ -0,0 +1,77 @@ +// Copyright 2023 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 { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index unique(s)"` + Type git.ArchiveType `xorm:"unique(s)"` + Tag string `xorm:"index unique(s)"` + Count int64 +} + +func init() { + db.RegisterModel(new(RepoArchiveDownloadCount)) +} + +// CountArchiveDownload adds one download the the given archive +func CountArchiveDownload(ctx context.Context, repoID int64, tp git.ArchiveType, tag string) error { + var counter RepoArchiveDownloadCount + has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("`type` = ?", tp).And("tag = ?", tag).Get(&counter) + if err != nil { + return err + } + + if has { + // The archive already exists in the database, so let's add to the the counter + counter.Count += 1 + _, err = db.GetEngine(ctx).ID(counter.ID).Update(counter) + return err + } + + // The archive does not esxists in the databse, so let's add it + newCounter := &RepoArchiveDownloadCount{ + RepoID: repoID, + Type: tp, + Tag: tag, + Count: 1, + } + + _, err = db.GetEngine(ctx).Insert(newCounter) + return err +} + +// GetTagDownloadCount returns the download count of a tag +func GetTagDownloadCount(ctx context.Context, repoID int64, tag string) (*api.TagArchiveDownloadCount, error) { + tagCounter := new(api.TagArchiveDownloadCount) + + var zipCounter RepoArchiveDownloadCount + has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("`type` = ?", git.ZIP).And("tag = ?", tag).Get(&zipCounter) + if err != nil { + return nil, err + } + if has { + tagCounter.Zip = zipCounter.Count + } + + var targzCounter RepoArchiveDownloadCount + has, err = db.GetEngine(ctx).Where("repo_id = ?", repoID).And("`type` = ?", git.TARGZ).And("tag = ?", tag).Get(&targzCounter) + if err != nil { + return nil, err + } + if has { + tagCounter.TarGz = targzCounter.Count + } + + return tagCounter, nil +} diff --git a/models/repo/archiver.go b/models/repo/archiver.go index 70f53cfe158d0..5a6becc4ec0ec 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -35,6 +35,7 @@ type RepoArchiver struct { //revive:disable-line:exported Status ArchiverStatus CommitID string `xorm:"VARCHAR(40) unique(s)"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"` + TagName string `xorm:"-"` } func init() { diff --git a/models/repo/release.go b/models/repo/release.go index b77490584f901..378c8d5731333 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -62,28 +62,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(40)"` - NumCommits int64 - NumCommitsBehind int64 `xorm:"-"` - Note string `xorm:"TEXT"` - RenderedNote string `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(40)"` + NumCommits int64 + NumCommitsBehind int64 `xorm:"-"` + Note string `xorm:"TEXT"` + RenderedNote string `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() { @@ -109,9 +110,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 = GetTagDownloadCount(ctx, r.RepoID, r.TagName) + 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) diff --git a/modules/structs/release.go b/modules/structs/release.go index 3fe40389b1f4a..8484771def59f 100644 --- a/modules/structs/release.go +++ b/modules/structs/release.go @@ -23,9 +23,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 diff --git a/modules/structs/repo_tag.go b/modules/structs/repo_tag.go index 4a7d895288073..ee753f72ec98f 100644 --- a/modules/structs/repo_tag.go +++ b/modules/structs/repo_tag.go @@ -38,3 +38,9 @@ type CreateTagOption struct { Message string `json:"message"` Target string `json:"target"` } + +// CreateTagOption counts how many times a archive was downloaded +type TagArchiveDownloadCount struct { + Zip int64 `json:"zip"` + TarGz int64 `json:"targz"` +} diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 5fddddb344080..3a6d827c45b99 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -185,6 +185,12 @@ func releasesOrTags(ctx *context.Context, isTagList bool) { return } + err = r.LoadArchiveDownloadCount(ctx) + if err != nil { + ctx.ServerError("LoadArchiveDownloadCount", err) + return + } + if r.IsDraft { continue } @@ -291,6 +297,11 @@ func SingleRelease(ctx *context.Context) { return } + err = release.LoadArchiveDownloadCount(ctx) + if err != nil { + ctx.ServerError("LoadArchiveDownloadCount", err) + } + ctx.Data["Releases"] = []*repo_model.Release{release} ctx.HTML(http.StatusOK, tplReleasesList) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index a1e1346b38c4b..41c348b50e656 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -444,6 +444,14 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep } defer fr.Close() + if archiver.TagName != "" { + err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.Type, archiver.TagName) + if err != nil { + ctx.ServerError("CountArchiveDownload", err) + return + } + } + ctx.ServeContent(fr, &context.ServeHeaderOptions{ Filename: downloadName, LastModified: archiver.CreatedUnix.AsLocalTime(), diff --git a/services/convert/release.go b/services/convert/release.go index ca28aa0d6b07c..cb36b330e5e1e 100644 --- a/services/convert/release.go +++ b/services/convert/release.go @@ -13,20 +13,21 @@ import ( // ToRelease convert a repo_model.Release to api.Release func ToRelease(ctx context.Context, r *repo_model.Release) *api.Release { return &api.Release{ - ID: r.ID, - TagName: r.TagName, - Target: r.Target, - Title: r.Title, - Note: r.Note, - URL: r.APIURL(), - HTMLURL: r.HTMLURL(), - TarURL: r.TarURL(), - ZipURL: r.ZipURL(), - IsDraft: r.IsDraft, - IsPrerelease: r.IsPrerelease, - CreatedAt: r.CreatedUnix.AsTime(), - PublishedAt: r.CreatedUnix.AsTime(), - Publisher: ToUser(ctx, r.Publisher, nil), - Attachments: ToAttachments(r.Attachments), + ID: r.ID, + TagName: r.TagName, + Target: r.Target, + Title: r.Title, + Note: r.Note, + URL: r.APIURL(), + HTMLURL: r.HTMLURL(), + TarURL: r.TarURL(), + ZipURL: r.ZipURL(), + IsDraft: r.IsDraft, + IsPrerelease: r.IsPrerelease, + CreatedAt: r.CreatedUnix.AsTime(), + PublishedAt: r.CreatedUnix.AsTime(), + Publisher: ToUser(ctx, r.Publisher, nil), + Attachments: ToAttachments(r.Attachments), + ArchiveDownloadCount: r.ArchiveDownloadCount, } } diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 2e3defee8d15e..c48a5aaca06d2 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -34,6 +34,7 @@ type ArchiveRequest struct { refName string Type git.ArchiveType CommitID string + TagName string } // SHA1 hashes will only go up to 40 characters, but SHA256 hashes will go all @@ -105,6 +106,7 @@ func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest } } else if repo.IsTagExist(r.refName) { r.CommitID, err = repo.GetTagCommitID(r.refName) + r.TagName = r.refName if err != nil { return nil, err } @@ -141,6 +143,8 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver return nil, fmt.Errorf("models.GetRepoArchiver: %w", err) } + archiver.TagName = aReq.TagName + if archiver != nil && archiver.Status == repo_model.ArchiverReady { // Archive already generated, we're done. return archiver, nil diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index fb8534f102800..2c23288b2fa48 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -85,9 +85,15 @@ {{if and (not $.DisableDownloadSourceArchives) (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.source_code"}} (ZIP) + + {{svg "octicon-info"}} +
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.source_code"}} (TAR.GZ) + + {{svg "octicon-info"}} +
  • {{end}} {{if .Attachments}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index f98dc12d95cfa..65e1112f3307d 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -20805,6 +20805,9 @@ "description": "Release represents a repository release", "type": "object", "properties": { + "archive_download_count": { + "$ref": "#/definitions/TagArchiveDownloadCount" + }, "assets": { "type": "array", "items": { @@ -21350,6 +21353,23 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "TagArchiveDownloadCount": { + "description": "CreateTagOption counts how many times a archive was downloaded", + "type": "object", + "properties": { + "targz": { + "type": "integer", + "format": "int64", + "x-go-name": "TarGz" + }, + "zip": { + "type": "integer", + "format": "int64", + "x-go-name": "Zip" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Team": { "description": "Team represents a team in an organization", "type": "object", From 29facb97f0a87f12df96172d7018843c5aeee2ab Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 24 Jul 2023 11:45:45 +0200 Subject: [PATCH 02/16] Delete Download Count when Tag is deleted --- models/repo/archive_download_count.go | 13 +++++++++---- services/release/release.go | 5 +++++ services/repository/archiver/archiver.go | 4 +++- services/repository/push.go | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go index d4899e25e2332..97112811f573b 100644 --- a/models/repo/archive_download_count.go +++ b/models/repo/archive_download_count.go @@ -12,7 +12,7 @@ import ( ) // RepoArchiveDownloadCount counts all archive downloads for a tag -type RepoArchiveDownloadCount struct { +type RepoArchiveDownloadCount struct { //nolint:revive ID int64 `xorm:"pk autoincr"` RepoID int64 `xorm:"index unique(s)"` Type git.ArchiveType `xorm:"unique(s)"` @@ -33,9 +33,8 @@ func CountArchiveDownload(ctx context.Context, repoID int64, tp git.ArchiveType, } if has { - // The archive already exists in the database, so let's add to the the counter - counter.Count += 1 - _, err = db.GetEngine(ctx).ID(counter.ID).Update(counter) + // The archive already exists in the database, so let's increase the counter + _, err = db.GetEngine(ctx).Incr("count").ID(counter.ID).Update(new(RepoArchiveDownloadCount)) return err } @@ -75,3 +74,9 @@ func GetTagDownloadCount(ctx context.Context, repoID int64, tag string) (*api.Ta return tagCounter, nil } + +// DeleteTagArchiveDownloadCount delets the tag from the table +func DeleteTagArchiveDownloadCount(ctx context.Context, repoID int64, tag string) error { + _, err := db.GetEngine(ctx).Exec("DELETE FROM repo_archive_download_count WHERE repo_id = ? AND tag = ?", repoID, tag) + return err +} diff --git a/services/release/release.go b/services/release/release.go index c1190305b6688..7a4cb8e393dba 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -317,6 +317,11 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del } } + err = repo_model.DeleteTagArchiveDownloadCount(ctx, rel.RepoID, rel.TagName) + if err != nil { + return err + } + if stdout, _, err := git.NewCommand(ctx, "tag", "-d").AddDashesAndList(rel.TagName). SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)). RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index c48a5aaca06d2..385973f82735c 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -143,7 +143,9 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver return nil, fmt.Errorf("models.GetRepoArchiver: %w", err) } - archiver.TagName = aReq.TagName + if archiver != nil { + archiver.TagName = aReq.TagName + } if archiver != nil && archiver.Status == repo_model.ArchiverReady { // Archive already generated, we're done. diff --git a/services/repository/push.go b/services/repository/push.go index 7e7069f580b42..99edeff016c45 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -305,6 +305,11 @@ func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, g if err := repo_model.PushUpdateDeleteTagsContext(ctx, repo, delTags); err != nil { return err } + + if err := pushUpdateArchiveDownloadCount(ctx, repo, delTags); err != nil { + return err + } + return pushUpdateAddTags(ctx, repo, gitRepo, addTags) }) } @@ -422,3 +427,17 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo return nil } + +// pushUpdateArchiveDownloadCount updates the archive download count +func pushUpdateArchiveDownloadCount(ctx context.Context, repo *repo_model.Repository, delTags []string) error { + var err error + + for _, delTag := range delTags { + err = repo_model.DeleteTagArchiveDownloadCount(ctx, repo.ID, delTag) + if err != nil { + return err + } + } + + return nil +} From 3e53b689628839e170f49feaf3f1059b7008f77c Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 24 Jul 2023 12:01:33 +0200 Subject: [PATCH 03/16] Fix typo --- services/convert/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/convert/release.go b/services/convert/release.go index 375855d6a3b16..14ea34fba78ae 100644 --- a/services/convert/release.go +++ b/services/convert/release.go @@ -27,7 +27,7 @@ func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_mode CreatedAt: r.CreatedUnix.AsTime(), PublishedAt: r.CreatedUnix.AsTime(), Publisher: ToUser(ctx, r.Publisher, nil), - Attachments: ToAPIAttachments(repo, r.Attachments),, + Attachments: ToAPIAttachments(repo, r.Attachments), ArchiveDownloadCount: r.ArchiveDownloadCount, } } From ad05135fcb0a26b0dd5a2e8fa272da95f6fd0faa Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 24 Jul 2023 15:53:35 +0200 Subject: [PATCH 04/16] Add Count to Tags API and add Tests --- models/repo/archive_download_count.go | 2 +- models/repo/release.go | 2 +- modules/structs/repo_tag.go | 28 +++++++++--------- routers/api/v1/repo/tag.go | 33 +++++++++++++++++++--- routers/web/repo/repo.go | 8 ++++++ services/convert/convert.go | 24 +++++++++++++--- services/repository/archiver/archiver.go | 1 + templates/swagger/v1_json.tmpl | 6 ++++ tests/integration/api_releases_test.go | 36 ++++++++++++++++++++++++ tests/integration/api_repo_tags_test.go | 36 ++++++++++++++++++++++++ 10 files changed, 153 insertions(+), 23 deletions(-) diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go index 97112811f573b..e3546c4660f3d 100644 --- a/models/repo/archive_download_count.go +++ b/models/repo/archive_download_count.go @@ -51,7 +51,7 @@ func CountArchiveDownload(ctx context.Context, repoID int64, tp git.ArchiveType, } // GetTagDownloadCount returns the download count of a tag -func GetTagDownloadCount(ctx context.Context, repoID int64, tag string) (*api.TagArchiveDownloadCount, error) { +func GetTagArchiveDownloadCount(ctx context.Context, repoID int64, tag string) (*api.TagArchiveDownloadCount, error) { tagCounter := new(api.TagArchiveDownloadCount) var zipCounter RepoArchiveDownloadCount diff --git a/models/repo/release.go b/models/repo/release.go index 99d714dfdba1f..874a83ddd8118 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -122,7 +122,7 @@ func (r *Release) LoadAttributes(ctx context.Context) error { // LoadArchiveDownloadCount loads the download count for the source archives func (r *Release) LoadArchiveDownloadCount(ctx context.Context) error { var err error - r.ArchiveDownloadCount, err = GetTagDownloadCount(ctx, r.RepoID, r.TagName) + r.ArchiveDownloadCount, err = GetTagArchiveDownloadCount(ctx, r.RepoID, r.TagName) return err } diff --git a/modules/structs/repo_tag.go b/modules/structs/repo_tag.go index ee753f72ec98f..0eafd34e1571c 100644 --- a/modules/structs/repo_tag.go +++ b/modules/structs/repo_tag.go @@ -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 diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index b28b6b0b91d73..73e7c48b37e69 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -58,7 +58,11 @@ func ListTags(ctx *context.APIContext) { apiTags := make([]*api.Tag, len(tags)) for i := range tags { - apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i]) + apiTags[i], err = convert.ToTag(ctx, ctx.Repo.Repository, tags[i]) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ConvertTotag", err) + return + } } ctx.SetTotalCountHeader(int64(total)) @@ -107,7 +111,14 @@ func GetAnnotatedTag(ctx *context.APIContext) { if err != nil { ctx.Error(http.StatusBadRequest, "GetAnnotatedTag", err) } - ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit)) + + apiTag, err := convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ConvertToAnnotatedTag", err) + return + } + + ctx.JSON(http.StatusOK, apiTag) } } @@ -146,7 +157,14 @@ func GetTag(ctx *context.APIContext) { ctx.NotFound(tagName) return } - ctx.JSON(http.StatusOK, convert.ToTag(ctx.Repo.Repository, tag)) + + apiTag, err := convert.ToTag(ctx, ctx.Repo.Repository, tag) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ConvertToTag", err) + return + } + + ctx.JSON(http.StatusOK, apiTag) } // CreateTag create a new git tag in a repository @@ -212,7 +230,14 @@ func CreateTag(ctx *context.APIContext) { ctx.InternalServerError(err) return } - ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag)) + + apiTag, err := convert.ToTag(ctx, ctx.Repo.Repository, tag) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ConvertToTag", err) + return + } + + ctx.JSON(http.StatusCreated, apiTag) } // DeleteTag delete a specific tag of in a repository by name diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 653d7b5ff4dfb..d0639cf897b91 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -432,6 +432,14 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep // If we have a signed url (S3, object storage), redirect to this directly. u, err := storage.RepoArchives.URL(rPath, downloadName) if u != nil && err == nil { + if archiver.TagName != "" { + err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.Type, archiver.TagName) + if err != nil { + ctx.ServerError("CountArchiveDownload", err) + return + } + } + ctx.Redirect(u.String()) return } diff --git a/services/convert/convert.go b/services/convert/convert.go index cb246a8b4b79c..bbbc10ca61b70 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -168,8 +168,8 @@ func ToBranchProtection(bp *git_model.ProtectedBranch) *api.BranchProtection { } // ToTag convert a git.Tag to an api.Tag -func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag { - return &api.Tag{ +func ToTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag) (*api.Tag, error) { + tag := &api.Tag{ Name: t.Name, Message: strings.TrimSpace(t.Message), ID: t.ID.String(), @@ -177,6 +177,14 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag { ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"), } + + var err error + tag.ArchiveDownloadCount, err = repo_model.GetTagArchiveDownloadCount(ctx, repo.ID, t.Name) + if err != nil { + return nil, err + } + + return tag, nil } // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification @@ -347,8 +355,8 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([] } // ToAnnotatedTag convert git.Tag to api.AnnotatedTag -func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag { - return &api.AnnotatedTag{ +func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) (*api.AnnotatedTag, error) { + tag := &api.AnnotatedTag{ Tag: t.Name, SHA: t.ID.String(), Object: ToAnnotatedTagObject(repo, c), @@ -357,6 +365,14 @@ func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag Tagger: ToCommitUser(t.Tagger), Verification: ToVerification(ctx, c), } + + var err error + tag.ArchiveDownloadCount, err = repo_model.GetTagArchiveDownloadCount(ctx, repo.ID, t.Name) + if err != nil { + return nil, err + } + + return tag, nil } // ToAnnotatedTagObject convert a git.Commit to an api.AnnotatedTagObject diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 385973f82735c..90d0817fd6eab 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -172,6 +172,7 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver return nil, fmt.Errorf("repo_model.GetRepoArchiver: %w", err) } if archiver != nil && archiver.Status == repo_model.ArchiverReady { + archiver.TagName = aReq.TagName return archiver, nil } } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index fa0d8e39abf76..60bd1e90a1ba2 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -15853,6 +15853,9 @@ "description": "AnnotatedTag represents an annotated tag", "type": "object", "properties": { + "archive_download_count": { + "$ref": "#/definitions/TagArchiveDownloadCount" + }, "message": { "type": "string", "x-go-name": "Message" @@ -21346,6 +21349,9 @@ "description": "Tag represents a repository tag", "type": "object", "properties": { + "archive_download_count": { + "$ref": "#/definitions/TagArchiveDownloadCount" + }, "commit": { "$ref": "#/definitions/CommitMeta" }, diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index 7f439390833e3..3820500240c88 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -248,3 +248,39 @@ func TestAPIDeleteReleaseByTagName(t *testing.T) { req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s", owner.Name, repo.Name, token)) _ = MakeRequest(t, req, http.StatusNoContent) } + +func TestAPIGetReleaseArchiveDownloadCount(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + name := "ReleaseDownloadCount" + + createNewReleaseUsingAPI(t, session, token, owner, repo, name, "", name, "test") + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, name) + + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var release *api.Release + DecodeJSON(t, resp, &release) + + // Check if everything defaults to 0 + assert.Equal(t, int64(0), release.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), release.ArchiveDownloadCount.Zip) + + // Download the tarball to increase the count + MakeRequest(t, NewRequest(t, "GET", release.TarURL), http.StatusOK) + + // Check if the count has increased + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &release) + + assert.Equal(t, int64(1), release.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), release.ArchiveDownloadCount.Zip) +} diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index c4282f99284df..14bef5090e5c6 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -82,3 +82,39 @@ func createNewTagUsingAPI(t *testing.T, session *TestSession, token, ownerName, DecodeJSON(t, resp, &respObj) return &respObj } + +func TestAPIGetTagArchiveDownloadCount(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + repoName := "repo1" + tagName := "TagDownloadCount" + + createNewTagUsingAPI(t, session, token, user.Name, repoName, tagName, "", "") + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/%s?token=%s", user.Name, repoName, tagName, token) + + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var tagInfo *api.Tag + DecodeJSON(t, resp, &tagInfo) + + // Check if everything defaults to 0 + assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.Zip) + + // Download the tarball to increase the count + MakeRequest(t, NewRequest(t, "GET", tagInfo.TarballURL), http.StatusOK) + + // Check if the count has increased + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &tagInfo) + + assert.Equal(t, int64(1), tagInfo.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.Zip) +} From 4582d884aac6307493b2bfa5e2289ff9b4ee4b40 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Wed, 26 Jul 2023 12:00:47 +0200 Subject: [PATCH 05/16] Delete download count when repo is deleted --- models/repo.go | 5 +++++ models/repo/archive_download_count.go | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/models/repo.go b/models/repo.go index 7579d2ad7348b..95c075d8bfd49 100644 --- a/models/repo.go +++ b/models/repo.go @@ -216,6 +216,11 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { return fmt.Errorf("unable to delete projects for repo[%d]: %w", repoID, err) } + // Remove archive download count + if err := repo_model.DeleteRepoArchiveDownloadCount(ctx, repo.ID); err != nil { + return err + } + // Remove LFS objects var lfsObjects []*git_model.LFSMetaObject if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go index e3546c4660f3d..3bd3cb2785565 100644 --- a/models/repo/archive_download_count.go +++ b/models/repo/archive_download_count.go @@ -75,8 +75,14 @@ func GetTagArchiveDownloadCount(ctx context.Context, repoID int64, tag string) ( return tagCounter, nil } -// DeleteTagArchiveDownloadCount delets the tag from the table +// DeleteTagArchiveDownloadCount deletes the tag from the repo_archive_download_count table func DeleteTagArchiveDownloadCount(ctx context.Context, repoID int64, tag string) error { _, err := db.GetEngine(ctx).Exec("DELETE FROM repo_archive_download_count WHERE repo_id = ? AND tag = ?", repoID, tag) return err } + +// DeleteRepoArchiveDownloadCount deletes the repo from the repo_archive_download_count table +func DeleteRepoArchiveDownloadCount(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Exec("DELETE FROM repo_archive_download_count WHERE repo_id = ?", repoID) + return err +} From 1530b15d0dcd7f35ccf133000ff9142302b29d01 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 5 Dec 2023 21:26:47 -0800 Subject: [PATCH 06/16] make fmt --- services/convert/release.go | 34 +++++++++++++------------- tests/integration/api_releases_test.go | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/services/convert/release.go b/services/convert/release.go index 4e33f4aaea0ad..fb8bd45678ef2 100644 --- a/services/convert/release.go +++ b/services/convert/release.go @@ -13,22 +13,22 @@ import ( // ToAPIRelease convert a repo_model.Release to api.Release func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_model.Release) *api.Release { return &api.Release{ - ID: r.ID, - TagName: r.TagName, - Target: r.Target, - Title: r.Title, - Note: r.Note, - URL: r.APIURL(), - HTMLURL: r.HTMLURL(), - TarURL: r.TarURL(), - ZipURL: r.ZipURL(), - UploadURL: r.APIUploadURL(), - IsDraft: r.IsDraft, - IsPrerelease: r.IsPrerelease, - CreatedAt: r.CreatedUnix.AsTime(), - PublishedAt: r.CreatedUnix.AsTime(), - Publisher: ToUser(ctx, r.Publisher, nil), - Attachments: ToAPIAttachments(repo, r.Attachments), - ArchiveDownloadCount: r.ArchiveDownloadCount, + ID: r.ID, + TagName: r.TagName, + Target: r.Target, + Title: r.Title, + Note: r.Note, + URL: r.APIURL(), + HTMLURL: r.HTMLURL(), + TarURL: r.TarURL(), + ZipURL: r.ZipURL(), + UploadURL: r.APIUploadURL(), + IsDraft: r.IsDraft, + IsPrerelease: r.IsPrerelease, + CreatedAt: r.CreatedUnix.AsTime(), + PublishedAt: r.CreatedUnix.AsTime(), + Publisher: ToUser(ctx, r.Publisher, nil), + Attachments: ToAPIAttachments(repo, r.Attachments), + ArchiveDownloadCount: r.ArchiveDownloadCount, } } diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index a2d043cf18008..35e853a017617 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -323,4 +323,4 @@ func TestAPIGetReleaseArchiveDownloadCount(t *testing.T) { assert.Equal(t, int64(1), release.ArchiveDownloadCount.TarGz) assert.Equal(t, int64(0), release.ArchiveDownloadCount.Zip) -} \ No newline at end of file +} From ccb0d3ffbbff5a464272824598b2d1b8e5d7464b Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 5 Dec 2023 21:36:51 -0800 Subject: [PATCH 07/16] fix lint --- templates/repo/release/list.tmpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 18203d563f8b0..1fc9cb1362330 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -86,13 +86,13 @@ {{if and (not $.DisableDownloadSourceArchives) (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP) - + {{svg "octicon-info"}} -
  • +
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ) - + {{svg "octicon-info"}}
  • From 8ab3a9b9d4400fb2b493c6a66b3ec3a2e9b18181 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 15 Jan 2024 16:40:17 +0100 Subject: [PATCH 08/16] Run make fmt --- services/repository/archiver/archiver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index ebeae22c0c833..83b1afd95bb83 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -99,7 +99,7 @@ func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest } r.CommitID = commitID.String() - r.TagName = r.refName + r.TagName = r.refName return r, nil } From 7317815e256ecd29fbd14f5ea6df08b6ee2d5fb9 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 18 Mar 2024 16:05:12 +0100 Subject: [PATCH 09/16] Fix Template --- templates/repo/release/list.tmpl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index f04966dc383e4..a70678eb8aa22 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -69,16 +69,15 @@ {{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP) - - {{svg "octicon-info"}} + + {{svg "octicon-info"}}
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ) - - {{svg "octicon-info"}} - -
  • + + {{svg "octicon-info"}} + {{end}} {{range $release.Attachments}} From 1477b6ec737d89096ce05f4a26097f3f16619e76 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 18 Mar 2024 16:45:35 +0100 Subject: [PATCH 10/16] Fix compiling --- routers/web/repo/release.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 4905fa711a502..b85e2548abf09 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -127,8 +127,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) err = r.LoadArchiveDownloadCount(ctx) if err != nil { - ctx.ServerError("LoadArchiveDownloadCount", err) - return + return nil, err } if !r.IsDraft { @@ -321,7 +320,7 @@ func SingleRelease(ctx *context.Context) { if err != nil { ctx.ServerError("LoadArchiveDownloadCount", err) } - + ctx.Data["Releases"] = releases ctx.HTML(http.StatusOK, tplReleasesList) From 37c99e41214a853d01d4670a3e164805c1fd9046 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 18 Mar 2024 16:57:29 +0100 Subject: [PATCH 11/16] Run make fmt --- models/repo/release.go | 46 ++++++++++++++++++------------------- routers/web/repo/release.go | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/models/repo/release.go b/models/repo/release.go index c687534e6c65e..13a0a52b34ca4 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -65,29 +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"` - ArchiveDownloadCount *structs.TagArchiveDownloadCount `xorm:"-"` + 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() { diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index b85e2548abf09..02f55572644aa 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -316,7 +316,7 @@ func SingleRelease(ctx *context.Context) { ctx.Data["Title"] = release.Title } - err = release.LoadArchiveDownloadCount(ctx) + err = release.LoadArchiveDownloadCount(ctx) if err != nil { ctx.ServerError("LoadArchiveDownloadCount", err) } From 1e2786315ab9fcd8d6369fa3dbb63488cb6f5e86 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Tue, 19 Mar 2024 12:31:47 +0100 Subject: [PATCH 12/16] Update --- models/migrations/migrations.go | 2 + models/migrations/v1_22/v292.go | 20 +++++ models/repo/archive_download_count.go | 82 ++++++++++--------- models/repo/archive_download_count_test.go | 62 ++++++++++++++ models/repo/archiver.go | 2 +- models/repo/release.go | 14 +++- modules/git/tag.go | 16 ++-- modules/structs/repo_tag.go | 4 +- routers/api/v1/repo/file.go | 2 +- routers/api/v1/repo/tag.go | 25 +++--- routers/web/repo/repo.go | 12 +-- services/convert/convert.go | 52 +++++------- services/doctor/dbconsistency.go | 3 + services/release/release.go | 2 +- services/repository/archiver/archiver.go | 25 ++++-- services/repository/archiver/archiver_test.go | 24 +++--- services/repository/delete.go | 1 + services/repository/push.go | 18 ---- templates/repo/release/list.tmpl | 4 +- templates/swagger/v1_json.tmpl | 4 +- 20 files changed, 228 insertions(+), 146 deletions(-) create mode 100644 models/migrations/v1_22/v292.go create mode 100644 models/repo/archive_download_count_test.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 87fddefb8824a..3c80988054a37 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -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 diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go new file mode 100644 index 0000000000000..9ed9fc3cb1cda --- /dev/null +++ b/models/migrations/v1_22/v292.go @@ -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{}) +} diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go index 3bd3cb2785565..9c1382e0ac486 100644 --- a/models/repo/archive_download_count.go +++ b/models/repo/archive_download_count.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package repo @@ -13,11 +13,11 @@ import ( // RepoArchiveDownloadCount counts all archive downloads for a tag type RepoArchiveDownloadCount struct { //nolint:revive - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"index unique(s)"` - Type git.ArchiveType `xorm:"unique(s)"` - Tag string `xorm:"index unique(s)"` - Count int64 + 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() { @@ -25,64 +25,66 @@ func init() { } // CountArchiveDownload adds one download the the given archive -func CountArchiveDownload(ctx context.Context, repoID int64, tp git.ArchiveType, tag string) error { - var counter RepoArchiveDownloadCount - has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("`type` = ?", tp).And("tag = ?", tag).Get(&counter) +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 has { - // The archive already exists in the database, so let's increase the counter - _, err = db.GetEngine(ctx).Incr("count").ID(counter.ID).Update(new(RepoArchiveDownloadCount)) - 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, - Type: tp, - Tag: tag, - Count: 1, + RepoID: repoID, + ReleaseID: releaseID, + Type: tp, + Count: 1, } _, err = db.GetEngine(ctx).Insert(newCounter) return err } -// GetTagDownloadCount returns the download count of a tag -func GetTagArchiveDownloadCount(ctx context.Context, repoID int64, tag string) (*api.TagArchiveDownloadCount, error) { - tagCounter := new(api.TagArchiveDownloadCount) - - var zipCounter RepoArchiveDownloadCount - has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("`type` = ?", git.ZIP).And("tag = ?", tag).Get(&zipCounter) +// 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 } - if has { - tagCounter.Zip = zipCounter.Count - } - var targzCounter RepoArchiveDownloadCount - has, err = db.GetEngine(ctx).Where("repo_id = ?", repoID).And("`type` = ?", git.TARGZ).And("tag = ?", tag).Get(&targzCounter) - if err != nil { - return nil, err - } - if has { - tagCounter.TarGz = targzCounter.Count + 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 } -// DeleteTagArchiveDownloadCount deletes the tag from the repo_archive_download_count table -func DeleteTagArchiveDownloadCount(ctx context.Context, repoID int64, tag string) error { - _, err := db.GetEngine(ctx).Exec("DELETE FROM repo_archive_download_count WHERE repo_id = ? AND tag = ?", repoID, tag) - return err +// 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 { + if IsErrReleaseNotExist(err) { + return new(api.TagArchiveDownloadCount), nil + } + return nil, err + } + + return GetArchiveDownloadCount(ctx, repoID, release.ID) } -// DeleteRepoArchiveDownloadCount deletes the repo from the repo_archive_download_count table -func DeleteRepoArchiveDownloadCount(ctx context.Context, repoID int64) error { - _, err := db.GetEngine(ctx).Exec("DELETE FROM repo_archive_download_count WHERE repo_id = ?", repoID) +// 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 } diff --git a/models/repo/archive_download_count_test.go b/models/repo/archive_download_count_test.go new file mode 100644 index 0000000000000..4d22a4fdc188d --- /dev/null +++ b/models/repo/archive_download_count_test.go @@ -0,0 +1,62 @@ +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, 0, downloadCount.Zip) + assert.Equal(t, 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, 0, downloadCount.Zip) + assert.Equal(t, 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, 0, downloadCount.Zip) + assert.Equal(t, 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, 1, downloadCount.Zip) + assert.Equal(t, 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, 0, downloadCount.Zip) + assert.Equal(t, 0, downloadCount.TarGz) +} diff --git a/models/repo/archiver.go b/models/repo/archiver.go index c479ce170bf22..3f05fcf752c47 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -35,7 +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"` - TagName string `xorm:"-"` + ReleaseID int64 `xorm:"-"` } func init() { diff --git a/models/repo/release.go b/models/repo/release.go index 13a0a52b34ca4..3168bdaaeab34 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -125,7 +125,7 @@ func (r *Release) LoadAttributes(ctx context.Context) error { // LoadArchiveDownloadCount loads the download count for the source archives func (r *Release) LoadArchiveDownloadCount(ctx context.Context) error { var err error - r.ArchiveDownloadCount, err = GetTagArchiveDownloadCount(ctx, r.RepoID, r.TagName) + r.ArchiveDownloadCount, err = GetArchiveDownloadCount(ctx, r.RepoID, r.ID) return err } @@ -461,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). diff --git a/modules/git/tag.go b/modules/git/tag.go index 94e5cd7c63b78..a07ff22f013ec 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -8,6 +8,7 @@ import ( "sort" "strings" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" ) @@ -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 diff --git a/modules/structs/repo_tag.go b/modules/structs/repo_tag.go index 0eafd34e1571c..961ca4e53b0a2 100644 --- a/modules/structs/repo_tag.go +++ b/modules/structs/repo_tag.go @@ -41,8 +41,8 @@ type CreateTagOption struct { Target string `json:"target"` } -// CreateTagOption counts how many times a archive was downloaded +// TagArchiveDownloadCount counts how many times a archive was downloaded type TagArchiveDownloadCount struct { Zip int64 `json:"zip"` - TarGz int64 `json:"targz"` + TarGz int64 `json:"tar_gz"` } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 4895f7b1b3fcf..d0214a76414a7 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -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) diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index 965846c591244..66b023e62b1de 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -60,11 +60,13 @@ func ListTags(ctx *context.APIContext) { apiTags := make([]*api.Tag, len(tags)) for i := range tags { - apiTags[i], err = convert.ToTag(ctx, ctx.Repo.Repository, tags[i]) + tags[i].ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tags[i].Name) if err != nil { - ctx.Error(http.StatusInternalServerError, "ConvertTotag", err) + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) return } + + apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i]) } ctx.SetTotalCountHeader(int64(total)) @@ -114,15 +116,16 @@ func GetAnnotatedTag(ctx *context.APIContext) { commit, err := tag.Commit(ctx.Repo.GitRepo) if err != nil { ctx.Error(http.StatusBadRequest, "GetAnnotatedTag", err) + return } - apiTag, err := convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit) + tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name) if err != nil { - ctx.Error(http.StatusInternalServerError, "ConvertToAnnotatedTag", err) + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) return } - ctx.JSON(http.StatusOK, apiTag) + ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit)) } } @@ -162,13 +165,13 @@ func GetTag(ctx *context.APIContext) { return } - apiTag, err := convert.ToTag(ctx, ctx.Repo.Repository, tag) + tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name) if err != nil { - ctx.Error(http.StatusInternalServerError, "ConvertToTag", err) + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) return } - ctx.JSON(http.StatusOK, apiTag) + ctx.JSON(http.StatusOK, convert.ToTag(ctx.Repo.Repository, tag)) } // CreateTag create a new git tag in a repository @@ -237,13 +240,13 @@ func CreateTag(ctx *context.APIContext) { return } - apiTag, err := convert.ToTag(ctx, ctx.Repo.Repository, tag) + tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name) if err != nil { - ctx.Error(http.StatusInternalServerError, "ConvertToTag", err) + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) return } - ctx.JSON(http.StatusCreated, apiTag) + ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag)) } // DeleteTag delete a specific tag of in a repository by name diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 7cc303d3a345e..522c48d60c7bd 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -460,7 +460,7 @@ func RedirectDownload(ctx *context.Context) { // Download an archive of a repository func Download(ctx *context.Context) { 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, err.Error()) @@ -489,8 +489,8 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep // If we have a signed url (S3, object storage), redirect to this directly. u, err := storage.RepoArchives.URL(rPath, downloadName) if u != nil && err == nil { - if archiver.TagName != "" { - err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.Type, archiver.TagName) + if archiver.ReleaseID != 0 { + err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.ReleaseID, archiver.Type) if err != nil { ctx.ServerError("CountArchiveDownload", err) return @@ -510,8 +510,8 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep } defer fr.Close() - if archiver.TagName != "" { - err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.Type, archiver.TagName) + if archiver.ReleaseID != 0 { + err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.ReleaseID, archiver.Type) if err != nil { ctx.ServerError("CountArchiveDownload", err) return @@ -529,7 +529,7 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep // kind of drop it on the floor if this is the case. func InitiateDownload(ctx *context.Context) { 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 { ctx.ServerError("archiver_service.NewRequest", err) return diff --git a/services/convert/convert.go b/services/convert/convert.go index e9dc56ac0b064..8de2316d1a503 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -168,23 +168,16 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api } // ToTag convert a git.Tag to an api.Tag -func ToTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag) (*api.Tag, error) { - tag := &api.Tag{ - Name: t.Name, - Message: strings.TrimSpace(t.Message), - ID: t.ID.String(), - Commit: ToCommitMeta(repo, t), - ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), - TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"), - } - - var err error - tag.ArchiveDownloadCount, err = repo_model.GetTagArchiveDownloadCount(ctx, repo.ID, t.Name) - if err != nil { - return nil, err +func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag { + return &api.Tag{ + Name: t.Name, + Message: strings.TrimSpace(t.Message), + ID: t.ID.String(), + Commit: ToCommitMeta(repo, t), + ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), + TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"), + ArchiveDownloadCount: t.ArchiveDownloadCount, } - - return tag, nil } // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification @@ -354,24 +347,17 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([] } // ToAnnotatedTag convert git.Tag to api.AnnotatedTag -func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) (*api.AnnotatedTag, error) { - tag := &api.AnnotatedTag{ - Tag: t.Name, - SHA: t.ID.String(), - Object: ToAnnotatedTagObject(repo, c), - Message: t.Message, - URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()), - Tagger: ToCommitUser(t.Tagger), - Verification: ToVerification(ctx, c), - } - - var err error - tag.ArchiveDownloadCount, err = repo_model.GetTagArchiveDownloadCount(ctx, repo.ID, t.Name) - if err != nil { - return nil, err +func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag { + return &api.AnnotatedTag{ + Tag: t.Name, + SHA: t.ID.String(), + Object: ToAnnotatedTagObject(repo, c), + Message: t.Message, + URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()), + Tagger: ToCommitUser(t.Tagger), + Verification: ToVerification(ctx, c), + ArchiveDownloadCount: t.ArchiveDownloadCount, } - - return tag, nil } // ToAnnotatedTagObject convert a git.Commit to an api.AnnotatedTagObject diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go index e2dcb63f33a7c..f0b79081a140e 100644 --- a/services/doctor/dbconsistency.go +++ b/services/doctor/dbconsistency.go @@ -223,6 +223,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er // find redirects without existing user. genericOrphanCheck("Orphaned Redirects without existing redirect user", "user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"), + // find archive download count without existing release + genericOrphanCheck("Archive download count without existing Release", + "repo_archive_download_count", "release", "repo_archive_download_count.release_id=release.id"), ) for _, c := range consistencyChecks { diff --git a/services/release/release.go b/services/release/release.go index a780a7006145c..b387ccb5c4005 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -318,7 +318,7 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re } } - err = repo_model.DeleteTagArchiveDownloadCount(ctx, rel.RepoID, rel.TagName) + err = repo_model.DeleteArchiveDownloadCountForRelease(ctx, rel.ID) if err != nil { return err } diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 45d05d1e1ae1c..36884e9453484 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -30,11 +30,11 @@ import ( // This is entirely opaque to external entities, though, and mostly used as a // handle elsewhere. type ArchiveRequest struct { - RepoID int64 - refName string - Type git.ArchiveType - CommitID string - TagName string + RepoID int64 + refName string + Type git.ArchiveType + CommitID string + ReleaseID int64 } // ErrUnknownArchiveFormat request archive format is not supported @@ -71,7 +71,7 @@ func (e RepoRefNotFoundError) Is(err error) bool { // NewRequest creates an archival request, based on the URI. The // resulting ArchiveRequest is suitable for being passed to ArchiveRepository() // if it's determined that the request still needs to be satisfied. -func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) { +func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) { r := &ArchiveRequest{ RepoID: repoID, } @@ -99,8 +99,15 @@ func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest return nil, RepoRefNotFoundError{RefName: r.refName} } + repo_model.GetRelease(ctx, repoID, r.refName) r.CommitID = commitID.String() - r.TagName = r.refName + + release, err := repo_model.GetRelease(ctx, repoID, r.refName) + if err != nil { + return nil, err + } + r.ReleaseID = release.ID + return r, nil } @@ -123,7 +130,7 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver } if archiver != nil { - archiver.TagName = aReq.TagName + archiver.ReleaseID = aReq.ReleaseID } if archiver != nil && archiver.Status == repo_model.ArchiverReady { @@ -151,7 +158,7 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver return nil, fmt.Errorf("repo_model.GetRepoArchiver: %w", err) } if archiver != nil && archiver.Status == repo_model.ArchiverReady { - archiver.TagName = aReq.TagName + archiver.ReleaseID = aReq.ReleaseID return archiver, nil } } diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go index ec6e9dfac3c8a..dbd4d9b3c70e0 100644 --- a/services/repository/archiver/archiver_test.go +++ b/services/repository/archiver/archiver_test.go @@ -31,47 +31,47 @@ func TestArchive_Basic(t *testing.T) { contexttest.LoadGitRepo(t, ctx) defer ctx.Repo.GitRepo.Close() - bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + bogusReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName()) // Check a series of bogus requests. // Step 1, valid commit with a bad extension. - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert") assert.Error(t, err) assert.Nil(t, bogusReq) // Step 2, missing commit. - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip") assert.Error(t, err) assert.Nil(t, bogusReq) // Step 3, doesn't look like branch/tag/commit. - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip") assert.Error(t, err) assert.Nil(t, bogusReq) - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName()) - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName()) // Now two valid requests, firstCommit with valid extensions. - zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + zipReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) assert.NotNil(t, zipReq) - tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz") + tgzReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz") assert.NoError(t, err) assert.NotNil(t, tgzReq) - secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip") + secondReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip") assert.NoError(t, err) assert.NotNil(t, secondReq) @@ -91,7 +91,7 @@ func TestArchive_Basic(t *testing.T) { // Sleep two seconds to make sure the queue doesn't change. time.Sleep(2 * time.Second) - zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + zipReq2, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) // This zipReq should match what's sitting in the queue, as we haven't // let it release yet. From the consumer's point of view, this looks like @@ -106,12 +106,12 @@ func TestArchive_Basic(t *testing.T) { // Now we'll submit a request and TimedWaitForCompletion twice, before and // after we release it. We should trigger both the timeout and non-timeout // cases. - timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz") + timedReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz") assert.NoError(t, err) assert.NotNil(t, timedReq) ArchiveRepository(db.DefaultContext, timedReq) - zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + zipReq2, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) // Now, we're guaranteed to have released the original zipReq from the queue. // Ensure that we don't get handed back the released entry somehow, but they diff --git a/services/repository/delete.go b/services/repository/delete.go index 8d6729f31bc83..a37b4153261ca 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -163,6 +163,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID &actions_model.ActionScheduleSpec{RepoID: repoID}, &actions_model.ActionSchedule{RepoID: repoID}, &actions_model.ActionArtifact{RepoID: repoID}, + &repo_model.RepoArchiveDownloadCount{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) } diff --git a/services/repository/push.go b/services/repository/push.go index 6ce3c9882495e..68b7c1ea9d2e0 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -304,10 +304,6 @@ func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, g return err } - if err := pushUpdateArchiveDownloadCount(ctx, repo, delTags); err != nil { - return err - } - return pushUpdateAddTags(ctx, repo, gitRepo, addTags) }) } @@ -428,17 +424,3 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo return nil } - -// pushUpdateArchiveDownloadCount updates the archive download count -func pushUpdateArchiveDownloadCount(ctx context.Context, repo *repo_model.Repository, delTags []string) error { - var err error - - for _, delTag := range delTags { - err = repo_model.DeleteTagArchiveDownloadCount(ctx, repo.ID, delTag) - if err != nil { - return err - } - } - - return nil -} diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index a70678eb8aa22..c457b622b3999 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -69,13 +69,13 @@ {{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP) - + {{svg "octicon-info"}}
  • {{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ) - + {{svg "octicon-info"}}
  • diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 0eb55f6048ef1..72f1dc98c2051 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -22987,10 +22987,10 @@ "x-go-package": "code.gitea.io/gitea/modules/structs" }, "TagArchiveDownloadCount": { - "description": "CreateTagOption counts how many times a archive was downloaded", + "description": "TagArchiveDownloadCount counts how many times a archive was downloaded", "type": "object", "properties": { - "targz": { + "tar_gz": { "type": "integer", "format": "int64", "x-go-name": "TarGz" From 585d1deca58a8e7789749ea554752c0998cfd958 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Tue, 19 Mar 2024 12:52:01 +0100 Subject: [PATCH 13/16] Some fixes --- models/repo/archive_download_count_test.go | 20 ++++++++++---------- services/repository/archiver/archiver.go | 9 ++++++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/models/repo/archive_download_count_test.go b/models/repo/archive_download_count_test.go index 4d22a4fdc188d..3a1b8e02e5b14 100644 --- a/models/repo/archive_download_count_test.go +++ b/models/repo/archive_download_count_test.go @@ -21,8 +21,8 @@ func TestRepoArchiveDownloadCount(t *testing.T) { // 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, 0, downloadCount.Zip) - assert.Equal(t, 0, downloadCount.TarGz) + 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) @@ -30,8 +30,8 @@ func TestRepoArchiveDownloadCount(t *testing.T) { downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) require.NoError(t, err) - assert.Equal(t, 0, downloadCount.Zip) - assert.Equal(t, 1, downloadCount.TarGz) + 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) @@ -39,8 +39,8 @@ func TestRepoArchiveDownloadCount(t *testing.T) { downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) require.NoError(t, err) - assert.Equal(t, 0, downloadCount.Zip) - assert.Equal(t, 2, downloadCount.TarGz) + 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) @@ -48,8 +48,8 @@ func TestRepoArchiveDownloadCount(t *testing.T) { downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) require.NoError(t, err) - assert.Equal(t, 1, downloadCount.Zip) - assert.Equal(t, 2, downloadCount.TarGz) + 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) @@ -57,6 +57,6 @@ func TestRepoArchiveDownloadCount(t *testing.T) { downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) require.NoError(t, err) - assert.Equal(t, 0, downloadCount.Zip) - assert.Equal(t, 0, downloadCount.TarGz) + assert.Equal(t, int64(0), downloadCount.Zip) + assert.Equal(t, int64(0), downloadCount.TarGz) } diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 36884e9453484..c74712b4ba253 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -99,14 +99,17 @@ func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, uri str return nil, RepoRefNotFoundError{RefName: r.refName} } - repo_model.GetRelease(ctx, repoID, r.refName) r.CommitID = commitID.String() release, err := repo_model.GetRelease(ctx, repoID, r.refName) if err != nil { - return nil, err + if !repo_model.IsErrReleaseNotExist(err) { + return nil, err + } + } + if release != nil { + r.ReleaseID = release.ID } - r.ReleaseID = release.ID return r, nil } From 2d807caf80db37eec30f3acbc02c93006fc614b1 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Tue, 19 Mar 2024 13:58:25 +0100 Subject: [PATCH 14/16] Add missing header --- models/repo/archive_download_count_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/repo/archive_download_count_test.go b/models/repo/archive_download_count_test.go index 3a1b8e02e5b14..53bdf9a1e054d 100644 --- a/models/repo/archive_download_count_test.go +++ b/models/repo/archive_download_count_test.go @@ -1,3 +1,6 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + package repo_test import ( From a6b6dfae311b715ba4cf03f59a2057a3a6a85983 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Tue, 19 Mar 2024 14:39:44 +0100 Subject: [PATCH 15/16] Return not exists error --- models/repo/archive_download_count.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go index 9c1382e0ac486..06d2150489c9f 100644 --- a/models/repo/archive_download_count.go +++ b/models/repo/archive_download_count.go @@ -68,15 +68,12 @@ func GetArchiveDownloadCount(ctx context.Context, repoID, releaseID int64) (*api } return tagCounter, nil -} +}s // 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 { - if IsErrReleaseNotExist(err) { - return new(api.TagArchiveDownloadCount), nil - } return nil, err } From a9195b923bdfda85d50454efed2453b752cd22fa Mon Sep 17 00:00:00 2001 From: JakobDev Date: Tue, 19 Mar 2024 14:45:46 +0100 Subject: [PATCH 16/16] Fix building --- models/repo/archive_download_count.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go index 06d2150489c9f..6300307a3531e 100644 --- a/models/repo/archive_download_count.go +++ b/models/repo/archive_download_count.go @@ -68,7 +68,7 @@ func GetArchiveDownloadCount(ctx context.Context, repoID, releaseID int64) (*api } return tagCounter, nil -}s +} // GetDownloadCountForTagName returns the download count of a tag with the given name func GetArchiveDownloadCountForTagName(ctx context.Context, repoID int64, tagName string) (*api.TagArchiveDownloadCount, error) {