You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package models
  5. import (
  6. "context"
  7. "fmt"
  8. "strconv"
  9. _ "image/jpeg" // Needed for jpeg support
  10. asymkey_model "code.gitea.io/gitea/models/asymkey"
  11. "code.gitea.io/gitea/models/db"
  12. issues_model "code.gitea.io/gitea/models/issues"
  13. access_model "code.gitea.io/gitea/models/perm/access"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. "code.gitea.io/gitea/models/unit"
  16. user_model "code.gitea.io/gitea/models/user"
  17. "code.gitea.io/gitea/modules/log"
  18. )
  19. // Init initialize model
  20. func Init(ctx context.Context) error {
  21. return unit.LoadUnitConfig()
  22. }
  23. type repoChecker struct {
  24. querySQL func(ctx context.Context) ([]map[string][]byte, error)
  25. correctSQL func(ctx context.Context, id int64) error
  26. desc string
  27. }
  28. func repoStatsCheck(ctx context.Context, checker *repoChecker) {
  29. results, err := checker.querySQL(ctx)
  30. if err != nil {
  31. log.Error("Select %s: %v", checker.desc, err)
  32. return
  33. }
  34. for _, result := range results {
  35. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  36. select {
  37. case <-ctx.Done():
  38. log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
  39. return
  40. default:
  41. }
  42. log.Trace("Updating %s: %d", checker.desc, id)
  43. err = checker.correctSQL(ctx, id)
  44. if err != nil {
  45. log.Error("Update %s[%d]: %v", checker.desc, id, err)
  46. }
  47. }
  48. }
  49. func StatsCorrectSQL(ctx context.Context, sql string, id int64) error {
  50. _, err := db.GetEngine(ctx).Exec(sql, id, id)
  51. return err
  52. }
  53. func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
  54. return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id)
  55. }
  56. func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
  57. return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id)
  58. }
  59. func labelStatsCorrectNumIssues(ctx context.Context, id int64) error {
  60. return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id)
  61. }
  62. func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
  63. _, err := db.GetEngine(ctx).Exec("UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=id) WHERE repo_id=?", id)
  64. return err
  65. }
  66. func labelStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
  67. _, 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)
  68. return err
  69. }
  70. func labelStatsCorrectNumClosedIssuesRepo(ctx context.Context, id int64) error {
  71. _, 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)
  72. return err
  73. }
  74. 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)"
  75. func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
  76. e := db.GetEngine(ctx)
  77. results, err := e.Query(milestoneStatsQueryNumIssues+" AND `milestone`.repo_id = ?", true, id)
  78. if err != nil {
  79. return err
  80. }
  81. for _, result := range results {
  82. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  83. err = issues_model.UpdateMilestoneCounters(ctx, id)
  84. if err != nil {
  85. return err
  86. }
  87. }
  88. return nil
  89. }
  90. func userStatsCorrectNumRepos(ctx context.Context, id int64) error {
  91. return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id)
  92. }
  93. func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
  94. return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id)
  95. }
  96. func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
  97. return repo_model.UpdateRepoIssueNumbers(ctx, id, false, false)
  98. }
  99. func repoStatsCorrectNumPulls(ctx context.Context, id int64) error {
  100. return repo_model.UpdateRepoIssueNumbers(ctx, id, true, false)
  101. }
  102. func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
  103. return repo_model.UpdateRepoIssueNumbers(ctx, id, false, true)
  104. }
  105. func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
  106. return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
  107. }
  108. func statsQuery(args ...any) func(context.Context) ([]map[string][]byte, error) {
  109. return func(ctx context.Context) ([]map[string][]byte, error) {
  110. return db.GetEngine(ctx).Query(args...)
  111. }
  112. }
  113. // CheckRepoStats checks the repository stats
  114. func CheckRepoStats(ctx context.Context) error {
  115. log.Trace("Doing: CheckRepoStats")
  116. checkers := []*repoChecker{
  117. // Repository.NumWatches
  118. {
  119. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)"),
  120. repoStatsCorrectNumWatches,
  121. "repository count 'num_watches'",
  122. },
  123. // Repository.NumStars
  124. {
  125. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)"),
  126. repoStatsCorrectNumStars,
  127. "repository count 'num_stars'",
  128. },
  129. // Repository.NumIssues
  130. {
  131. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", false),
  132. repoStatsCorrectNumIssues,
  133. "repository count 'num_issues'",
  134. },
  135. // Repository.NumClosedIssues
  136. {
  137. 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),
  138. repoStatsCorrectNumClosedIssues,
  139. "repository count 'num_closed_issues'",
  140. },
  141. // Repository.NumPulls
  142. {
  143. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", true),
  144. repoStatsCorrectNumPulls,
  145. "repository count 'num_pulls'",
  146. },
  147. // Repository.NumClosedPulls
  148. {
  149. 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),
  150. repoStatsCorrectNumClosedPulls,
  151. "repository count 'num_closed_pulls'",
  152. },
  153. // Label.NumIssues
  154. {
  155. statsQuery("SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)"),
  156. labelStatsCorrectNumIssues,
  157. "label count 'num_issues'",
  158. },
  159. // Label.NumClosedIssues
  160. {
  161. 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),
  162. labelStatsCorrectNumClosedIssues,
  163. "label count 'num_closed_issues'",
  164. },
  165. // Milestone.Num{,Closed}Issues
  166. {
  167. statsQuery(milestoneStatsQueryNumIssues, true),
  168. issues_model.UpdateMilestoneCounters,
  169. "milestone count 'num_closed_issues' and 'num_issues'",
  170. },
  171. // User.NumRepos
  172. {
  173. statsQuery("SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)"),
  174. userStatsCorrectNumRepos,
  175. "user count 'num_repos'",
  176. },
  177. // Issue.NumComments
  178. {
  179. statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"),
  180. repoStatsCorrectIssueNumComments,
  181. "issue count 'num_comments'",
  182. },
  183. }
  184. for _, checker := range checkers {
  185. select {
  186. case <-ctx.Done():
  187. log.Warn("CheckRepoStats: Cancelled before %s", checker.desc)
  188. return db.ErrCancelledf("before checking %s", checker.desc)
  189. default:
  190. repoStatsCheck(ctx, checker)
  191. }
  192. }
  193. // FIXME: use checker when stop supporting old fork repo format.
  194. // ***** START: Repository.NumForks *****
  195. e := db.GetEngine(ctx)
  196. results, err := e.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
  197. if err != nil {
  198. log.Error("Select repository count 'num_forks': %v", err)
  199. } else {
  200. for _, result := range results {
  201. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  202. select {
  203. case <-ctx.Done():
  204. log.Warn("CheckRepoStats: Cancelled")
  205. return db.ErrCancelledf("during repository count 'num_fork' for repo ID %d", id)
  206. default:
  207. }
  208. log.Trace("Updating repository count 'num_forks': %d", id)
  209. repo, err := repo_model.GetRepositoryByID(ctx, id)
  210. if err != nil {
  211. log.Error("repo_model.GetRepositoryByID[%d]: %v", id, err)
  212. continue
  213. }
  214. _, err = e.SQL("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID).Get(&repo.NumForks)
  215. if err != nil {
  216. log.Error("Select count of forks[%d]: %v", repo.ID, err)
  217. continue
  218. }
  219. if _, err = e.ID(repo.ID).Cols("num_forks").Update(repo); err != nil {
  220. log.Error("UpdateRepository[%d]: %v", id, err)
  221. continue
  222. }
  223. }
  224. }
  225. // ***** END: Repository.NumForks *****
  226. return nil
  227. }
  228. func UpdateRepoStats(ctx context.Context, id int64) error {
  229. var err error
  230. for _, f := range []func(ctx context.Context, id int64) error{
  231. repoStatsCorrectNumWatches,
  232. repoStatsCorrectNumStars,
  233. repoStatsCorrectNumIssues,
  234. repoStatsCorrectNumPulls,
  235. repoStatsCorrectNumClosedIssues,
  236. repoStatsCorrectNumClosedPulls,
  237. labelStatsCorrectNumIssuesRepo,
  238. labelStatsCorrectNumClosedIssuesRepo,
  239. milestoneStatsCorrectNumIssuesRepo,
  240. } {
  241. err = f(ctx, id)
  242. if err != nil {
  243. return err
  244. }
  245. }
  246. return nil
  247. }
  248. func updateUserStarNumbers(users []user_model.User) error {
  249. ctx, committer, err := db.TxContext(db.DefaultContext)
  250. if err != nil {
  251. return err
  252. }
  253. defer committer.Close()
  254. for _, user := range users {
  255. if _, err = db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil {
  256. return err
  257. }
  258. }
  259. return committer.Commit()
  260. }
  261. // DoctorUserStarNum recalculate Stars number for all user
  262. func DoctorUserStarNum() (err error) {
  263. const batchSize = 100
  264. for start := 0; ; start += batchSize {
  265. users := make([]user_model.User, 0, batchSize)
  266. if err = db.GetEngine(db.DefaultContext).Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil {
  267. return err
  268. }
  269. if len(users) == 0 {
  270. break
  271. }
  272. if err = updateUserStarNumbers(users); err != nil {
  273. return err
  274. }
  275. }
  276. log.Debug("recalculate Stars number for all user finished")
  277. return err
  278. }
  279. // DeleteDeployKey delete deploy keys
  280. func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error {
  281. key, err := asymkey_model.GetDeployKeyByID(ctx, id)
  282. if err != nil {
  283. if asymkey_model.IsErrDeployKeyNotExist(err) {
  284. return nil
  285. }
  286. return fmt.Errorf("GetDeployKeyByID: %w", err)
  287. }
  288. // Check if user has access to delete this key.
  289. if !doer.IsAdmin {
  290. repo, err := repo_model.GetRepositoryByID(ctx, key.RepoID)
  291. if err != nil {
  292. return fmt.Errorf("GetRepositoryByID: %w", err)
  293. }
  294. has, err := access_model.IsUserRepoAdmin(ctx, repo, doer)
  295. if err != nil {
  296. return fmt.Errorf("GetUserRepoPermission: %w", err)
  297. } else if !has {
  298. return asymkey_model.ErrKeyAccessDenied{
  299. UserID: doer.ID,
  300. KeyID: key.ID,
  301. Note: "deploy",
  302. }
  303. }
  304. }
  305. if _, err := db.DeleteByBean(ctx, &asymkey_model.DeployKey{
  306. ID: key.ID,
  307. }); err != nil {
  308. return fmt.Errorf("delete deploy key [%d]: %w", key.ID, err)
  309. }
  310. // Check if this is the last reference to same key content.
  311. has, err := asymkey_model.IsDeployKeyExistByKeyID(ctx, key.KeyID)
  312. if err != nil {
  313. return err
  314. } else if !has {
  315. if err = asymkey_model.DeletePublicKeys(ctx, key.KeyID); err != nil {
  316. return err
  317. }
  318. }
  319. return nil
  320. }