Skip to content
Draft
57 changes: 0 additions & 57 deletions models/fixtures/repository.yml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/models/migrations/v1_21"
"code.gitea.io/gitea/models/migrations/v1_22"
"code.gitea.io/gitea/models/migrations/v1_23"
"code.gitea.io/gitea/models/migrations/v1_24"
"code.gitea.io/gitea/models/migrations/v1_6"
"code.gitea.io/gitea/models/migrations/v1_7"
"code.gitea.io/gitea/models/migrations/v1_8"
Expand Down Expand Up @@ -369,6 +370,9 @@ func prepareMigrationTasks() []*migration {
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable),

// Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312)
newMigration(312, "Remove repository column num_watches", v1_24.RemoveRepoNumWatches),
}
return preparedMigrations
}
Expand Down
16 changes: 16 additions & 0 deletions models/migrations/v1_24/v312.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_24 //nolint

import (
"code.gitea.io/gitea/models/migrations/base"

"xorm.io/xorm"
)

func RemoveRepoNumWatches(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()
return base.DropTableColumns(sess, "repository", "num_watches")
}
11 changes: 0 additions & 11 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ func StatsCorrectSQL(ctx context.Context, sql any, ids ...any) error {
return err
}

func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id, id)
}

func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id, id)
}
Expand Down Expand Up @@ -142,12 +138,6 @@ func CheckRepoStats(ctx context.Context) error {
log.Trace("Doing: CheckRepoStats")

checkers := []*repoChecker{
// Repository.NumWatches
{
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)"),
repoStatsCorrectNumWatches,
"repository count 'num_watches'",
},
// Repository.NumStars
{
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)"),
Expand Down Expand Up @@ -271,7 +261,6 @@ func UpdateRepoStats(ctx context.Context, id int64) error {
var err error

for _, f := range []func(ctx context.Context, id int64) error{
repoStatsCorrectNumWatches,
repoStatsCorrectNumStars,
repoStatsCorrectNumIssues,
repoStatsCorrectNumPulls,
Expand Down
14 changes: 13 additions & 1 deletion models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ type Repository struct {
DefaultBranch string
DefaultWikiBranch string

NumWatches int
NumWatches int `xorm:"-"`
NumStars int
NumForks int
NumIssues int
Expand Down Expand Up @@ -317,6 +317,18 @@ func (repo *Repository) LoadAttributes(ctx context.Context) error {
return nil
}

func (repo *Repository) LoadNumWatchers(ctx context.Context) error {
if repo.NumWatches > 0 {
return nil
}
cnt, err := CountRepoWatchers(ctx, repo.ID)
if err != nil {
return err
}
repo.NumWatches = int(cnt)
return nil
}

// FullName returns the repository full name
func (repo *Repository) FullName() string {
return repo.OwnerName + "/" + repo.Name
Expand Down
32 changes: 32 additions & 0 deletions models/repo/repo_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,38 @@ func (repos RepositoryList) LoadLanguageStats(ctx context.Context) error {
return nil
}

func (repos RepositoryList) LoadNumWatchers(ctx context.Context) error {
if len(repos) == 0 {
return nil
}

type results struct {
RepoID int64
NumWatchers int64
}
// Load watchers.
watches := make([]results, 0, len(repos))
if err := db.GetEngine(ctx).
Select("repo_id, COUNT(*) as num_watchers").
Table("watch").
In("repo_id", repos.IDs()).
And("`mode` <> ?", WatchModeDont).
GroupBy("repo_id").
Find(&watches); err != nil {
return fmt.Errorf("find watchers: %w", err)
}

watchesMap := make(map[int64]int, len(repos))
for _, watch := range watches {
watchesMap[watch.RepoID] = int(watch.NumWatchers)
}

for i := range repos {
repos[i].NumWatches = watchesMap[repos[i].ID]
}
return nil
}

// LoadAttributes loads the attributes for the given RepositoryList
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
if err := repos.LoadOwners(ctx); err != nil {
Expand Down
16 changes: 6 additions & 10 deletions models/repo/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,6 @@ func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error)

hadrec := watch.Mode != WatchModeNone
needsrec := mode != WatchModeNone
repodiff := 0

if IsWatchMode(mode) && !IsWatchMode(watch.Mode) {
repodiff = 1
} else if !IsWatchMode(mode) && IsWatchMode(watch.Mode) {
repodiff = -1
}

watch.Mode = mode

Expand All @@ -98,9 +91,6 @@ func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error)
} else if _, err = db.DeleteByID[Watch](ctx, watch.ID); err != nil {
return err
}
if repodiff != 0 {
_, err = db.GetEngine(ctx).Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID)
}
return err
}

Expand Down Expand Up @@ -162,6 +152,12 @@ func GetRepoWatchers(ctx context.Context, repoID int64, opts db.ListOptions) ([]
return users, sess.Find(&users)
}

func CountRepoWatchers(ctx context.Context, repoID int64) (int64, error) {
return db.GetEngine(ctx).Where("repo_id=?", repoID).
And("mode<>?", WatchModeDont).
Count(new(Watch))
}

// WatchIfAuto subscribes to repo if AutoWatchOnChanges is set
func WatchIfAuto(ctx context.Context, userID, repoID int64, isWrite bool) error {
if !isWrite || !setting.Service.AutoWatchOnChanges {
Expand Down
6 changes: 1 addition & 5 deletions models/repo/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ func TestGetWatchers(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
watches, err := repo_model.GetWatchers(db.DefaultContext, repo.ID)
assert.NoError(t, err)
// One watchers are inactive, thus minus 1
assert.Len(t, watches, repo.NumWatches-1)
for _, watch := range watches {
assert.EqualValues(t, repo.ID, watch.RepoID)
}
Expand All @@ -50,7 +48,6 @@ func TestRepository_GetWatchers(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
watchers, err := repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err)
assert.Len(t, watchers, repo.NumWatches)
for _, watcher := range watchers {
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{UserID: watcher.ID, RepoID: repo.ID})
}
Expand All @@ -69,11 +66,10 @@ func TestWatchIfAuto(t *testing.T) {

watchers, err := repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err)
assert.Len(t, watchers, repo.NumWatches)

setting.Service.AutoWatchOnChanges = false

prevCount := repo.NumWatches
prevCount := len(watchers)

// Must not add watch
assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 8, 1, true))
Expand Down
7 changes: 1 addition & 6 deletions models/unittest/consistency.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,7 @@ func init() {
AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": repo.int("ForkID")})
}

actual := GetCountByCond(t, "watch", builder.Eq{"repo_id": repo.int("ID")}.
And(builder.Neq{"mode": modelsRepoWatchModeDont}))
assert.EqualValues(t, repo.int("NumWatches"), actual,
"Unexpected number of watches for repo id: %d", repo.int("ID"))

actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "repo_id": repo.int("ID")})
actual := GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "repo_id": repo.int("ID")})
assert.EqualValues(t, repo.int("NumIssues"), actual,
"Unexpected number of issues for repo id: %d", repo.int("ID"))

Expand Down
8 changes: 7 additions & 1 deletion routers/api/v1/repo/subscriber.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ func ListSubscribers(ctx *context.APIContext) {
users[i] = convert.ToUser(ctx, subscriber, ctx.Doer)
}

ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumWatches))
total, err := repo_model.CountRepoWatchers(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountRepoWatchers", err)
return
}

ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, users)
}
14 changes: 7 additions & 7 deletions routers/web/explore/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
opts.PageSize = setting.UI.SitemapPagingNum
}

var (
repos []*repo_model.Repository
count int64
err error
orderBy db.SearchOrderBy
)
var orderBy db.SearchOrderBy

sortOrder := ctx.FormString("sort")
if sortOrder == "" {
Expand Down Expand Up @@ -94,7 +89,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
private := ctx.FormOptionalBool("private")
ctx.Data["IsPrivate"] = private

repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: opts.PageSize,
Expand Down Expand Up @@ -132,6 +127,11 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
return
}

if err := repos.LoadNumWatchers(ctx); err != nil {
ctx.ServerError("LoadNumWatchers", err)
return
}

ctx.Data["Keyword"] = keyword
ctx.Data["Total"] = count
ctx.Data["Repos"] = repos
Expand Down
7 changes: 6 additions & 1 deletion routers/web/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,15 +357,20 @@ func Action(ctx *context.Context) {
switch ctx.PathParam("action") {
case "watch", "unwatch", "star", "unstar":
// we have to reload the repository because NumStars or NumWatching (used in the templates) has just changed
ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.Name)
ctx.Repo.Repository, err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.Name)
if err != nil {
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
return
}
ctx.Data["Repository"] = ctx.Repo.Repository
}

switch ctx.PathParam("action") {
case "watch", "unwatch":
if err := ctx.Repo.Repository.LoadNumWatchers(ctx); err != nil {
ctx.ServerError("LoadNumWatchers", err)
return
}
ctx.HTML(http.StatusOK, tplWatchUnwatch)
return
case "star", "unstar":
Expand Down
8 changes: 7 additions & 1 deletion routers/web/repo/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,13 @@ func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOp
func Watchers(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.watchers")
ctx.Data["CardsTitle"] = ctx.Tr("repo.watchers")
RenderUserCards(ctx, ctx.Repo.Repository.NumWatches, func(opts db.ListOptions) ([]*user_model.User, error) {
numWatchers, err := repo_model.CountRepoWatchers(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("CountRepoWatchers", err)
return
}

RenderUserCards(ctx, int(numWatchers), func(opts db.ListOptions) ([]*user_model.User, error) {
return repo_model.GetRepoWatchers(ctx, ctx.Repo.Repository.ID, opts)
}, tplWatchers)
}
Expand Down
9 changes: 8 additions & 1 deletion services/convert/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR

repoLicenses, err := repo_model.GetRepoLicenses(ctx, repo)
if err != nil {
log.Error("GetRepoLicenses: %v", err)
return nil
}

numWatchers, err := repo_model.CountRepoWatchers(ctx, repo.ID)
if err != nil {
log.Error("CountRepoWatchers: %v", err)
return nil
}

Expand Down Expand Up @@ -205,7 +212,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
LanguagesURL: repoAPIURL + "/languages",
Stars: repo.NumStars,
Forks: repo.NumForks,
Watchers: repo.NumWatches,
Watchers: int(numWatchers),
OpenIssues: repo.NumOpenIssues,
OpenPulls: repo.NumOpenPulls,
Releases: int(numReleases),
Expand Down
12 changes: 0 additions & 12 deletions services/repository/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"time"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
Expand Down Expand Up @@ -72,17 +71,6 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
httpTransport *http.Transport,
) (*repo_model.Repository, error) {
repoPath := repo_model.RepoPath(u.Name, opts.RepoName)

if u.IsOrganization() {
t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
if err != nil {
return nil, err
}
repo.NumWatches = t.NumMembers
} else {
repo.NumWatches = 1
}

migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second

if err := util.RemoveAll(repoPath); err != nil {
Expand Down
12 changes: 0 additions & 12 deletions services/user/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,6 @@ import (
func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) {
e := db.GetEngine(ctx)

// ***** START: Watch *****
watchedRepoIDs, err := db.FindIDs(ctx, "watch", "watch.repo_id",
builder.Eq{"watch.user_id": u.ID}.
And(builder.Neq{"watch.mode": repo_model.WatchModeDont}))
if err != nil {
return fmt.Errorf("get all watches: %w", err)
}
if err = db.DecrByIDs(ctx, watchedRepoIDs, "num_watches", new(repo_model.Repository)); err != nil {
return fmt.Errorf("decrease repository num_watches: %w", err)
}
// ***** END: Watch *****

// ***** START: Star *****
starredRepoIDs, err := db.FindIDs(ctx, "star", "star.repo_id",
builder.Eq{"star.uid": u.ID})
Expand Down
Loading