2014-02-14 23:16:54 +00:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2017-08-26 13:57:41 +00:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 18:20:29 +00:00
// SPDX-License-Identifier: MIT
2014-02-14 23:16:54 +00:00
2014-02-14 14:20:57 +00:00
package models
import (
2019-12-15 09:51:28 +00:00
"context"
2019-04-22 20:40:51 +00:00
"strconv"
2014-02-14 14:20:57 +00:00
2021-11-17 12:34:35 +00:00
_ "image/jpeg" // Needed for jpeg support
2021-09-19 11:49:59 +00:00
"code.gitea.io/gitea/models/db"
2022-04-08 09:11:15 +00:00
issues_model "code.gitea.io/gitea/models/issues"
2021-11-19 13:39:57 +00:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-09 19:57:58 +00:00
"code.gitea.io/gitea/models/unit"
2021-11-24 09:49:20 +00:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 16:24:48 +00:00
"code.gitea.io/gitea/modules/log"
2014-02-14 14:20:57 +00:00
)
2022-10-16 23:29:26 +00:00
// Init initialize model
2023-02-24 10:23:13 +00:00
func Init ( ctx context . Context ) error {
2023-10-05 01:08:19 +00:00
return unit . LoadUnitConfig ( )
2014-03-11 05:32:36 +00:00
}
2015-09-01 15:43:53 +00:00
type repoChecker struct {
2022-01-17 18:31:58 +00:00
querySQL func ( ctx context . Context ) ( [ ] map [ string ] [ ] byte , error )
correctSQL func ( ctx context . Context , id int64 ) error
desc string
2015-09-01 15:43:53 +00:00
}
2015-08-17 20:03:11 +00:00
2019-12-15 09:51:28 +00:00
func repoStatsCheck ( ctx context . Context , checker * repoChecker ) {
2022-01-17 18:31:58 +00:00
results , err := checker . querySQL ( ctx )
2015-08-17 20:03:11 +00:00
if err != nil {
2019-04-02 07:48:31 +00:00
log . Error ( "Select %s: %v" , checker . desc , err )
2015-08-17 20:03:11 +00:00
return
}
2015-09-01 15:43:53 +00:00
for _ , result := range results {
2020-12-25 09:59:32 +00:00
id , _ := strconv . ParseInt ( string ( result [ "id" ] ) , 10 , 64 )
2019-12-15 09:51:28 +00:00
select {
case <- ctx . Done ( ) :
2022-01-17 18:31:58 +00:00
log . Warn ( "CheckRepoStats: Cancelled before checking %s for with id=%d" , checker . desc , id )
2019-12-15 09:51:28 +00:00
return
default :
}
2015-09-01 15:43:53 +00:00
log . Trace ( "Updating %s: %d" , checker . desc , id )
2022-01-17 18:31:58 +00:00
err = checker . correctSQL ( ctx , id )
2015-08-17 20:03:11 +00:00
if err != nil {
2019-04-02 07:48:31 +00:00
log . Error ( "Update %s[%d]: %v" , checker . desc , id , err )
2015-08-17 20:03:11 +00:00
}
}
2015-09-01 15:43:53 +00:00
}
2015-08-17 20:03:11 +00:00
2022-01-17 18:31:58 +00:00
func StatsCorrectSQL ( ctx context . Context , sql string , id int64 ) error {
_ , err := db . GetEngine ( ctx ) . Exec ( sql , id , id )
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 )
}
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 )
}
func labelStatsCorrectNumIssues ( ctx context . Context , id int64 ) error {
return StatsCorrectSQL ( ctx , "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?" , id )
}
func labelStatsCorrectNumIssuesRepo ( ctx context . Context , id int64 ) error {
_ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=id) WHERE repo_id=?" , id )
return err
}
func labelStatsCorrectNumClosedIssues ( ctx context . Context , id int64 ) error {
_ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `label` SET num_closed_issues=(SELECT COUNT(*) FROM `issue_label`,`issue` WHERE `issue_label`.label_id=`label`.id AND `issue_label`.issue_id=`issue`.id AND `issue`.is_closed=?) WHERE `label`.id=?" , true , id )
return err
}
func labelStatsCorrectNumClosedIssuesRepo ( ctx context . Context , id int64 ) error {
_ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `label` SET num_closed_issues=(SELECT COUNT(*) FROM `issue_label`,`issue` WHERE `issue_label`.label_id=`label`.id AND `issue_label`.issue_id=`issue`.id AND `issue`.is_closed=?) WHERE `label`.repo_id=?" , true , id )
return err
}
var milestoneStatsQueryNumIssues = "SELECT `milestone`.id FROM `milestone` WHERE `milestone`.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id AND `issue`.is_closed=?) OR `milestone`.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id)"
func milestoneStatsCorrectNumIssuesRepo ( ctx context . Context , id int64 ) error {
e := db . GetEngine ( ctx )
results , err := e . Query ( milestoneStatsQueryNumIssues + " AND `milestone`.repo_id = ?" , true , id )
if err != nil {
return err
}
for _ , result := range results {
id , _ := strconv . ParseInt ( string ( result [ "id" ] ) , 10 , 64 )
2022-04-08 09:11:15 +00:00
err = issues_model . UpdateMilestoneCounters ( ctx , id )
2022-01-17 18:31:58 +00:00
if err != nil {
return err
}
}
return nil
}
func userStatsCorrectNumRepos ( ctx context . Context , id int64 ) error {
return StatsCorrectSQL ( ctx , "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?" , id )
}
func repoStatsCorrectIssueNumComments ( ctx context . Context , id int64 ) error {
return StatsCorrectSQL ( ctx , "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?" , id )
}
func repoStatsCorrectNumIssues ( ctx context . Context , id int64 ) error {
2022-10-25 12:47:46 +00:00
return repo_model . UpdateRepoIssueNumbers ( ctx , id , false , false )
2022-01-17 18:31:58 +00:00
}
func repoStatsCorrectNumPulls ( ctx context . Context , id int64 ) error {
2022-10-25 12:47:46 +00:00
return repo_model . UpdateRepoIssueNumbers ( ctx , id , true , false )
2022-01-17 18:31:58 +00:00
}
func repoStatsCorrectNumClosedIssues ( ctx context . Context , id int64 ) error {
2022-10-25 12:47:46 +00:00
return repo_model . UpdateRepoIssueNumbers ( ctx , id , false , true )
2022-01-17 18:31:58 +00:00
}
func repoStatsCorrectNumClosedPulls ( ctx context . Context , id int64 ) error {
2022-10-25 12:47:46 +00:00
return repo_model . UpdateRepoIssueNumbers ( ctx , id , true , true )
2022-01-17 18:31:58 +00:00
}
2023-07-04 18:36:08 +00:00
func statsQuery ( args ... any ) func ( context . Context ) ( [ ] map [ string ] [ ] byte , error ) {
2022-01-17 18:31:58 +00:00
return func ( ctx context . Context ) ( [ ] map [ string ] [ ] byte , error ) {
return db . GetEngine ( ctx ) . Query ( args ... )
}
}
2016-11-28 17:27:55 +00:00
// CheckRepoStats checks the repository stats
2020-05-16 23:31:38 +00:00
func CheckRepoStats ( ctx context . Context ) error {
2015-09-01 15:43:53 +00:00
log . Trace ( "Doing: CheckRepoStats" )
2015-08-29 17:13:24 +00:00
2015-09-01 15:43:53 +00:00
checkers := [ ] * repoChecker {
// Repository.NumWatches
{
2022-01-17 18:31:58 +00:00
statsQuery ( "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)" ) ,
repoStatsCorrectNumWatches ,
2015-09-01 15:43:53 +00:00
"repository count 'num_watches'" ,
} ,
// Repository.NumStars
{
2022-01-17 18:31:58 +00:00
statsQuery ( "SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)" ) ,
repoStatsCorrectNumStars ,
2015-09-01 15:43:53 +00:00
"repository count 'num_stars'" ,
} ,
2022-10-11 06:41:39 +00:00
// Repository.NumIssues
{
2022-12-06 07:53:09 +00:00
statsQuery ( "SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)" , false ) ,
2022-10-11 06:41:39 +00:00
repoStatsCorrectNumIssues ,
"repository count 'num_issues'" ,
} ,
2022-01-17 18:31:58 +00:00
// Repository.NumClosedIssues
{
statsQuery ( "SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)" , true , false ) ,
repoStatsCorrectNumClosedIssues ,
"repository count 'num_closed_issues'" ,
} ,
2022-10-11 06:41:39 +00:00
// Repository.NumPulls
{
2022-12-06 07:53:09 +00:00
statsQuery ( "SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)" , true ) ,
2022-10-11 06:41:39 +00:00
repoStatsCorrectNumPulls ,
"repository count 'num_pulls'" ,
} ,
2022-01-17 18:31:58 +00:00
// Repository.NumClosedPulls
{
2022-10-11 06:41:39 +00:00
statsQuery ( "SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)" , true , true ) ,
2022-01-17 18:31:58 +00:00
repoStatsCorrectNumClosedPulls ,
"repository count 'num_closed_pulls'" ,
} ,
2015-09-01 15:43:53 +00:00
// Label.NumIssues
{
2022-01-17 18:31:58 +00:00
statsQuery ( "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)" ) ,
labelStatsCorrectNumIssues ,
2015-09-01 15:43:53 +00:00
"label count 'num_issues'" ,
} ,
2022-01-17 18:31:58 +00:00
// Label.NumClosedIssues
{
statsQuery ( "SELECT `label`.id FROM `label` WHERE `label`.num_closed_issues!=(SELECT COUNT(*) FROM `issue_label`,`issue` WHERE `issue_label`.label_id=`label`.id AND `issue_label`.issue_id=`issue`.id AND `issue`.is_closed=?)" , true ) ,
labelStatsCorrectNumClosedIssues ,
"label count 'num_closed_issues'" ,
} ,
// Milestone.Num{,Closed}Issues
{
statsQuery ( milestoneStatsQueryNumIssues , true ) ,
2022-04-08 09:11:15 +00:00
issues_model . UpdateMilestoneCounters ,
2022-01-17 18:31:58 +00:00
"milestone count 'num_closed_issues' and 'num_issues'" ,
} ,
2015-09-01 15:43:53 +00:00
// User.NumRepos
{
2022-01-17 18:31:58 +00:00
statsQuery ( "SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)" ) ,
userStatsCorrectNumRepos ,
2015-09-01 15:43:53 +00:00
"user count 'num_repos'" ,
} ,
2015-10-30 00:40:57 +00:00
// Issue.NumComments
{
2022-01-17 18:31:58 +00:00
statsQuery ( "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)" ) ,
repoStatsCorrectIssueNumComments ,
2015-10-30 00:40:57 +00:00
"issue count 'num_comments'" ,
} ,
2015-09-01 15:43:53 +00:00
}
2020-05-16 23:31:38 +00:00
for _ , checker := range checkers {
2019-12-15 09:51:28 +00:00
select {
case <- ctx . Done ( ) :
2020-05-16 23:31:38 +00:00
log . Warn ( "CheckRepoStats: Cancelled before %s" , checker . desc )
2021-11-10 05:13:16 +00:00
return db . ErrCancelledf ( "before checking %s" , checker . desc )
2019-12-15 09:51:28 +00:00
default :
2020-05-16 23:31:38 +00:00
repoStatsCheck ( ctx , checker )
2019-12-15 09:51:28 +00:00
}
2015-09-01 15:43:53 +00:00
}
2016-05-28 01:23:39 +00:00
// FIXME: use checker when stop supporting old fork repo format.
2015-09-01 15:43:53 +00:00
// ***** START: Repository.NumForks *****
2022-01-17 18:31:58 +00:00
e := db . GetEngine ( ctx )
results , err := e . Query ( "SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)" )
2015-08-29 17:13:24 +00:00
if err != nil {
2019-04-02 07:48:31 +00:00
log . Error ( "Select repository count 'num_forks': %v" , err )
2015-09-01 15:43:53 +00:00
} else {
for _ , result := range results {
2020-12-25 09:59:32 +00:00
id , _ := strconv . ParseInt ( string ( result [ "id" ] ) , 10 , 64 )
2019-12-15 09:51:28 +00:00
select {
case <- ctx . Done ( ) :
2020-05-16 23:31:38 +00:00
log . Warn ( "CheckRepoStats: Cancelled" )
2022-01-17 18:31:58 +00:00
return db . ErrCancelledf ( "during repository count 'num_fork' for repo ID %d" , id )
2019-12-15 09:51:28 +00:00
default :
}
2015-09-01 15:43:53 +00:00
log . Trace ( "Updating repository count 'num_forks': %d" , id )
2022-12-03 02:48:26 +00:00
repo , err := repo_model . GetRepositoryByID ( ctx , id )
2015-09-01 15:43:53 +00:00
if err != nil {
2021-12-10 01:27:50 +00:00
log . Error ( "repo_model.GetRepositoryByID[%d]: %v" , id , err )
2015-09-01 15:43:53 +00:00
continue
}
2022-06-13 09:37:59 +00:00
_ , err = e . SQL ( "SELECT COUNT(*) FROM `repository` WHERE fork_id=?" , repo . ID ) . Get ( & repo . NumForks )
2015-09-01 15:43:53 +00:00
if err != nil {
2019-04-02 07:48:31 +00:00
log . Error ( "Select count of forks[%d]: %v" , repo . ID , err )
2015-09-01 15:43:53 +00:00
continue
}
2022-06-06 08:01:49 +00:00
if _ , err = e . ID ( repo . ID ) . Cols ( "num_forks" ) . Update ( repo ) ; err != nil {
2019-04-02 07:48:31 +00:00
log . Error ( "UpdateRepository[%d]: %v" , id , err )
2015-09-01 15:43:53 +00:00
continue
}
2015-08-29 17:13:24 +00:00
}
}
2015-09-01 15:43:53 +00:00
// ***** END: Repository.NumForks *****
2020-05-16 23:31:38 +00:00
return nil
2015-03-21 12:55:00 +00:00
}
2022-01-17 18:31:58 +00:00
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 ,
repoStatsCorrectNumClosedIssues ,
repoStatsCorrectNumClosedPulls ,
labelStatsCorrectNumIssuesRepo ,
labelStatsCorrectNumClosedIssuesRepo ,
milestoneStatsCorrectNumIssuesRepo ,
} {
err = f ( ctx , id )
if err != nil {
return err
}
}
return nil
}
2023-10-11 04:24:07 +00:00
func updateUserStarNumbers ( ctx context . Context , users [ ] user_model . User ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-11-21 15:41:00 +00:00
if err != nil {
return err
}
defer committer . Close ( )
for _ , user := range users {
if _ , err = db . Exec ( ctx , "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?" , user . ID , user . ID ) ; err != nil {
return err
}
}
return committer . Commit ( )
}
2020-07-07 19:16:34 +00:00
// DoctorUserStarNum recalculate Stars number for all user
2023-10-11 04:24:07 +00:00
func DoctorUserStarNum ( ctx context . Context ) ( err error ) {
2020-07-07 19:16:34 +00:00
const batchSize = 100
for start := 0 ; ; start += batchSize {
2021-11-24 09:49:20 +00:00
users := make ( [ ] user_model . User , 0 , batchSize )
2023-10-11 04:24:07 +00:00
if err = db . GetEngine ( ctx ) . Limit ( batchSize , start ) . Where ( "type = ?" , 0 ) . Cols ( "id" ) . Find ( & users ) ; err != nil {
2023-07-09 11:58:06 +00:00
return err
2020-07-07 19:16:34 +00:00
}
if len ( users ) == 0 {
break
}
2023-10-11 04:24:07 +00:00
if err = updateUserStarNumbers ( ctx , users ) ; err != nil {
2023-07-09 11:58:06 +00:00
return err
2020-07-07 19:16:34 +00:00
}
}
log . Debug ( "recalculate Stars number for all user finished" )
2022-06-20 10:02:49 +00:00
return err
2020-07-07 19:16:34 +00:00
}