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 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package models
  6. import (
  7. "context"
  8. "fmt"
  9. "strconv"
  10. _ "image/jpeg" // Needed for jpeg support
  11. admin_model "code.gitea.io/gitea/models/admin"
  12. asymkey_model "code.gitea.io/gitea/models/asymkey"
  13. "code.gitea.io/gitea/models/db"
  14. git_model "code.gitea.io/gitea/models/git"
  15. issues_model "code.gitea.io/gitea/models/issues"
  16. "code.gitea.io/gitea/models/organization"
  17. "code.gitea.io/gitea/models/perm"
  18. access_model "code.gitea.io/gitea/models/perm/access"
  19. project_model "code.gitea.io/gitea/models/project"
  20. repo_model "code.gitea.io/gitea/models/repo"
  21. "code.gitea.io/gitea/models/unit"
  22. user_model "code.gitea.io/gitea/models/user"
  23. "code.gitea.io/gitea/models/webhook"
  24. "code.gitea.io/gitea/modules/lfs"
  25. "code.gitea.io/gitea/modules/log"
  26. "code.gitea.io/gitea/modules/setting"
  27. "code.gitea.io/gitea/modules/storage"
  28. api "code.gitea.io/gitea/modules/structs"
  29. "code.gitea.io/gitea/modules/util"
  30. "xorm.io/builder"
  31. )
  32. // NewRepoContext creates a new repository context
  33. func NewRepoContext() {
  34. unit.LoadUnitConfig()
  35. }
  36. // CheckRepoUnitUser check whether user could visit the unit of this repository
  37. func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
  38. if user != nil && user.IsAdmin {
  39. return true
  40. }
  41. perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
  42. if err != nil {
  43. log.Error("GetUserRepoPermission(): %v", err)
  44. return false
  45. }
  46. return perm.CanRead(unitType)
  47. }
  48. // CreateRepoOptions contains the create repository options
  49. type CreateRepoOptions struct {
  50. Name string
  51. Description string
  52. OriginalURL string
  53. GitServiceType api.GitServiceType
  54. Gitignores string
  55. IssueLabels string
  56. License string
  57. Readme string
  58. DefaultBranch string
  59. IsPrivate bool
  60. IsMirror bool
  61. IsTemplate bool
  62. AutoInit bool
  63. Status repo_model.RepositoryStatus
  64. TrustModel repo_model.TrustModelType
  65. MirrorInterval string
  66. }
  67. // CreateRepository creates a repository for the user/organization.
  68. func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt bool) (err error) {
  69. if err = repo_model.IsUsableRepoName(repo.Name); err != nil {
  70. return err
  71. }
  72. has, err := repo_model.IsRepositoryExist(ctx, u, repo.Name)
  73. if err != nil {
  74. return fmt.Errorf("IsRepositoryExist: %v", err)
  75. } else if has {
  76. return repo_model.ErrRepoAlreadyExist{
  77. Uname: u.Name,
  78. Name: repo.Name,
  79. }
  80. }
  81. repoPath := repo_model.RepoPath(u.Name, repo.Name)
  82. isExist, err := util.IsExist(repoPath)
  83. if err != nil {
  84. log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
  85. return err
  86. }
  87. if !overwriteOrAdopt && isExist {
  88. log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
  89. return repo_model.ErrRepoFilesAlreadyExist{
  90. Uname: u.Name,
  91. Name: repo.Name,
  92. }
  93. }
  94. if err = db.Insert(ctx, repo); err != nil {
  95. return err
  96. }
  97. if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil {
  98. return err
  99. }
  100. // insert units for repo
  101. units := make([]repo_model.RepoUnit, 0, len(unit.DefaultRepoUnits))
  102. for _, tp := range unit.DefaultRepoUnits {
  103. if tp == unit.TypeIssues {
  104. units = append(units, repo_model.RepoUnit{
  105. RepoID: repo.ID,
  106. Type: tp,
  107. Config: &repo_model.IssuesConfig{
  108. EnableTimetracker: setting.Service.DefaultEnableTimetracking,
  109. AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
  110. EnableDependencies: setting.Service.DefaultEnableDependencies,
  111. },
  112. })
  113. } else if tp == unit.TypePullRequests {
  114. units = append(units, repo_model.RepoUnit{
  115. RepoID: repo.ID,
  116. Type: tp,
  117. Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), AllowRebaseUpdate: true},
  118. })
  119. } else {
  120. units = append(units, repo_model.RepoUnit{
  121. RepoID: repo.ID,
  122. Type: tp,
  123. })
  124. }
  125. }
  126. if err = db.Insert(ctx, units); err != nil {
  127. return err
  128. }
  129. // Remember visibility preference.
  130. u.LastRepoVisibility = repo.IsPrivate
  131. if err = user_model.UpdateUserCols(ctx, u, "last_repo_visibility"); err != nil {
  132. return fmt.Errorf("updateUser: %v", err)
  133. }
  134. if _, err = db.GetEngine(ctx).Incr("num_repos").ID(u.ID).Update(new(user_model.User)); err != nil {
  135. return fmt.Errorf("increment user total_repos: %v", err)
  136. }
  137. u.NumRepos++
  138. // Give access to all members in teams with access to all repositories.
  139. if u.IsOrganization() {
  140. teams, err := organization.FindOrgTeams(ctx, u.ID)
  141. if err != nil {
  142. return fmt.Errorf("loadTeams: %v", err)
  143. }
  144. for _, t := range teams {
  145. if t.IncludesAllRepositories {
  146. if err := addRepository(ctx, t, repo); err != nil {
  147. return fmt.Errorf("addRepository: %v", err)
  148. }
  149. }
  150. }
  151. if isAdmin, err := access_model.IsUserRepoAdmin(ctx, repo, doer); err != nil {
  152. return fmt.Errorf("IsUserRepoAdminCtx: %v", err)
  153. } else if !isAdmin {
  154. // Make creator repo admin if it wasn't assigned automatically
  155. if err = addCollaborator(ctx, repo, doer); err != nil {
  156. return fmt.Errorf("AddCollaborator: %v", err)
  157. }
  158. if err = repo_model.ChangeCollaborationAccessModeCtx(ctx, repo, doer.ID, perm.AccessModeAdmin); err != nil {
  159. return fmt.Errorf("ChangeCollaborationAccessMode: %v", err)
  160. }
  161. }
  162. } else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
  163. // Organization automatically called this in addRepository method.
  164. return fmt.Errorf("recalculateAccesses: %v", err)
  165. }
  166. if setting.Service.AutoWatchNewRepos {
  167. if err = repo_model.WatchRepo(ctx, doer.ID, repo.ID, true); err != nil {
  168. return fmt.Errorf("watchRepo: %v", err)
  169. }
  170. }
  171. if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil {
  172. return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err)
  173. }
  174. return nil
  175. }
  176. // DeleteRepository deletes a repository for a user or organization.
  177. // make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock)
  178. func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
  179. ctx, committer, err := db.TxContext()
  180. if err != nil {
  181. return err
  182. }
  183. defer committer.Close()
  184. sess := db.GetEngine(ctx)
  185. // In case is a organization.
  186. org, err := user_model.GetUserByIDCtx(ctx, uid)
  187. if err != nil {
  188. return err
  189. }
  190. repo := &repo_model.Repository{OwnerID: uid}
  191. has, err := sess.ID(repoID).Get(repo)
  192. if err != nil {
  193. return err
  194. } else if !has {
  195. return repo_model.ErrRepoNotExist{
  196. ID: repoID,
  197. UID: uid,
  198. OwnerName: "",
  199. Name: "",
  200. }
  201. }
  202. // Delete Deploy Keys
  203. deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID})
  204. if err != nil {
  205. return fmt.Errorf("listDeployKeys: %v", err)
  206. }
  207. needRewriteKeysFile := len(deployKeys) > 0
  208. for _, dKey := range deployKeys {
  209. if err := DeleteDeployKey(ctx, doer, dKey.ID); err != nil {
  210. return fmt.Errorf("deleteDeployKeys: %v", err)
  211. }
  212. }
  213. if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil {
  214. return err
  215. } else if cnt != 1 {
  216. return repo_model.ErrRepoNotExist{
  217. ID: repoID,
  218. UID: uid,
  219. OwnerName: "",
  220. Name: "",
  221. }
  222. }
  223. if org.IsOrganization() {
  224. teams, err := organization.FindOrgTeams(ctx, org.ID)
  225. if err != nil {
  226. return err
  227. }
  228. for _, t := range teams {
  229. if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) {
  230. continue
  231. } else if err = removeRepository(ctx, t, repo, false); err != nil {
  232. return err
  233. }
  234. }
  235. }
  236. attachments := make([]*repo_model.Attachment, 0, 20)
  237. if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id").
  238. Where("`release`.repo_id = ?", repoID).
  239. Find(&attachments); err != nil {
  240. return err
  241. }
  242. releaseAttachments := make([]string, 0, len(attachments))
  243. for i := 0; i < len(attachments); i++ {
  244. releaseAttachments = append(releaseAttachments, attachments[i].RelativePath())
  245. }
  246. if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil {
  247. return err
  248. }
  249. if err := db.DeleteBeans(ctx,
  250. &access_model.Access{RepoID: repo.ID},
  251. &Action{RepoID: repo.ID},
  252. &repo_model.Collaboration{RepoID: repoID},
  253. &issues_model.Comment{RefRepoID: repoID},
  254. &git_model.CommitStatus{RepoID: repoID},
  255. &git_model.DeletedBranch{RepoID: repoID},
  256. &webhook.HookTask{RepoID: repoID},
  257. &git_model.LFSLock{RepoID: repoID},
  258. &repo_model.LanguageStat{RepoID: repoID},
  259. &issues_model.Milestone{RepoID: repoID},
  260. &repo_model.Mirror{RepoID: repoID},
  261. &Notification{RepoID: repoID},
  262. &git_model.ProtectedBranch{RepoID: repoID},
  263. &git_model.ProtectedTag{RepoID: repoID},
  264. &repo_model.PushMirror{RepoID: repoID},
  265. &Release{RepoID: repoID},
  266. &repo_model.RepoIndexerStatus{RepoID: repoID},
  267. &repo_model.Redirect{RedirectRepoID: repoID},
  268. &repo_model.RepoUnit{RepoID: repoID},
  269. &repo_model.Star{RepoID: repoID},
  270. &Task{RepoID: repoID},
  271. &repo_model.Watch{RepoID: repoID},
  272. &webhook.Webhook{RepoID: repoID},
  273. ); err != nil {
  274. return fmt.Errorf("deleteBeans: %v", err)
  275. }
  276. // Delete Labels and related objects
  277. if err := issues_model.DeleteLabelsByRepoID(ctx, repoID); err != nil {
  278. return err
  279. }
  280. // Delete Pulls and related objects
  281. if err := issues_model.DeletePullsByBaseRepoID(ctx, repoID); err != nil {
  282. return err
  283. }
  284. // Delete Issues and related objects
  285. var attachmentPaths []string
  286. if attachmentPaths, err = issues_model.DeleteIssuesByRepoID(ctx, repoID); err != nil {
  287. return err
  288. }
  289. // Delete issue index
  290. if err := db.DeleteResouceIndex(ctx, "issue_index", repoID); err != nil {
  291. return err
  292. }
  293. if repo.IsFork {
  294. if _, err := db.Exec(ctx, "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
  295. return fmt.Errorf("decrease fork count: %v", err)
  296. }
  297. }
  298. if _, err := db.Exec(ctx, "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil {
  299. return err
  300. }
  301. if len(repo.Topics) > 0 {
  302. if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil {
  303. return err
  304. }
  305. }
  306. if err := project_model.DeleteProjectByRepoIDCtx(ctx, repoID); err != nil {
  307. return fmt.Errorf("unable to delete projects for repo[%d]: %v", repoID, err)
  308. }
  309. // Remove LFS objects
  310. var lfsObjects []*git_model.LFSMetaObject
  311. if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
  312. return err
  313. }
  314. lfsPaths := make([]string, 0, len(lfsObjects))
  315. for _, v := range lfsObjects {
  316. count, err := db.CountByBean(ctx, &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}})
  317. if err != nil {
  318. return err
  319. }
  320. if count > 1 {
  321. continue
  322. }
  323. lfsPaths = append(lfsPaths, v.RelativePath())
  324. }
  325. if _, err := db.DeleteByBean(ctx, &git_model.LFSMetaObject{RepositoryID: repoID}); err != nil {
  326. return err
  327. }
  328. // Remove archives
  329. var archives []*repo_model.RepoArchiver
  330. if err = sess.Where("repo_id=?", repoID).Find(&archives); err != nil {
  331. return err
  332. }
  333. archivePaths := make([]string, 0, len(archives))
  334. for _, v := range archives {
  335. p, _ := v.RelativePath()
  336. archivePaths = append(archivePaths, p)
  337. }
  338. if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil {
  339. return err
  340. }
  341. if repo.NumForks > 0 {
  342. if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
  343. log.Error("reset 'fork_id' and 'is_fork': %v", err)
  344. }
  345. }
  346. // Get all attachments with both issue_id and release_id are zero
  347. var newAttachments []*repo_model.Attachment
  348. if err := sess.Where(builder.Eq{
  349. "repo_id": repo.ID,
  350. "issue_id": 0,
  351. "release_id": 0,
  352. }).Find(&newAttachments); err != nil {
  353. return err
  354. }
  355. newAttachmentPaths := make([]string, 0, len(newAttachments))
  356. for _, attach := range newAttachments {
  357. newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath())
  358. }
  359. if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil {
  360. return err
  361. }
  362. if err = committer.Commit(); err != nil {
  363. return err
  364. }
  365. committer.Close()
  366. if needRewriteKeysFile {
  367. if err := asymkey_model.RewriteAllPublicKeys(); err != nil {
  368. log.Error("RewriteAllPublicKeys failed: %v", err)
  369. }
  370. }
  371. // We should always delete the files after the database transaction succeed. If
  372. // we delete the file but the database rollback, the repository will be broken.
  373. // Remove repository files.
  374. repoPath := repo.RepoPath()
  375. admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
  376. // Remove wiki files
  377. if repo.HasWiki() {
  378. admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath())
  379. }
  380. // Remove archives
  381. for _, archive := range archivePaths {
  382. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive)
  383. }
  384. // Remove lfs objects
  385. for _, lfsObj := range lfsPaths {
  386. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj)
  387. }
  388. // Remove issue attachment files.
  389. for _, attachment := range attachmentPaths {
  390. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment)
  391. }
  392. // Remove release attachment files.
  393. for _, releaseAttachment := range releaseAttachments {
  394. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment)
  395. }
  396. // Remove attachment with no issue_id and release_id.
  397. for _, newAttachment := range newAttachmentPaths {
  398. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment)
  399. }
  400. if len(repo.Avatar) > 0 {
  401. if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil {
  402. return fmt.Errorf("Failed to remove %s: %v", repo.Avatar, err)
  403. }
  404. }
  405. return nil
  406. }
  407. type repoChecker struct {
  408. querySQL func(ctx context.Context) ([]map[string][]byte, error)
  409. correctSQL func(ctx context.Context, id int64) error
  410. desc string
  411. }
  412. func repoStatsCheck(ctx context.Context, checker *repoChecker) {
  413. results, err := checker.querySQL(ctx)
  414. if err != nil {
  415. log.Error("Select %s: %v", checker.desc, err)
  416. return
  417. }
  418. for _, result := range results {
  419. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  420. select {
  421. case <-ctx.Done():
  422. log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
  423. return
  424. default:
  425. }
  426. log.Trace("Updating %s: %d", checker.desc, id)
  427. err = checker.correctSQL(ctx, id)
  428. if err != nil {
  429. log.Error("Update %s[%d]: %v", checker.desc, id, err)
  430. }
  431. }
  432. }
  433. func StatsCorrectSQL(ctx context.Context, sql string, id int64) error {
  434. _, err := db.GetEngine(ctx).Exec(sql, id, id)
  435. return err
  436. }
  437. func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
  438. return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id)
  439. }
  440. func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
  441. return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id)
  442. }
  443. func labelStatsCorrectNumIssues(ctx context.Context, id int64) error {
  444. return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id)
  445. }
  446. func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
  447. _, err := db.GetEngine(ctx).Exec("UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=id) WHERE repo_id=?", id)
  448. return err
  449. }
  450. func labelStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
  451. _, 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)
  452. return err
  453. }
  454. func labelStatsCorrectNumClosedIssuesRepo(ctx context.Context, id int64) error {
  455. _, 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)
  456. return err
  457. }
  458. 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)"
  459. func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
  460. e := db.GetEngine(ctx)
  461. results, err := e.Query(milestoneStatsQueryNumIssues+" AND `milestone`.repo_id = ?", true, id)
  462. if err != nil {
  463. return err
  464. }
  465. for _, result := range results {
  466. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  467. err = issues_model.UpdateMilestoneCounters(ctx, id)
  468. if err != nil {
  469. return err
  470. }
  471. }
  472. return nil
  473. }
  474. func userStatsCorrectNumRepos(ctx context.Context, id int64) error {
  475. return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id)
  476. }
  477. func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
  478. return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id)
  479. }
  480. func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
  481. return repoStatsCorrectNum(ctx, id, false, "num_issues")
  482. }
  483. func repoStatsCorrectNumPulls(ctx context.Context, id int64) error {
  484. return repoStatsCorrectNum(ctx, id, true, "num_pulls")
  485. }
  486. func repoStatsCorrectNum(ctx context.Context, id int64, isPull bool, field string) error {
  487. _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=?) WHERE id=?", id, isPull, id)
  488. return err
  489. }
  490. func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
  491. return repo_model.StatsCorrectNumClosed(ctx, id, false, "num_closed_issues")
  492. }
  493. func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
  494. return repo_model.StatsCorrectNumClosed(ctx, id, true, "num_closed_pulls")
  495. }
  496. func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) {
  497. return func(ctx context.Context) ([]map[string][]byte, error) {
  498. return db.GetEngine(ctx).Query(args...)
  499. }
  500. }
  501. // CheckRepoStats checks the repository stats
  502. func CheckRepoStats(ctx context.Context) error {
  503. log.Trace("Doing: CheckRepoStats")
  504. checkers := []*repoChecker{
  505. // Repository.NumWatches
  506. {
  507. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)"),
  508. repoStatsCorrectNumWatches,
  509. "repository count 'num_watches'",
  510. },
  511. // Repository.NumStars
  512. {
  513. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)"),
  514. repoStatsCorrectNumStars,
  515. "repository count 'num_stars'",
  516. },
  517. // Repository.NumClosedIssues
  518. {
  519. 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),
  520. repoStatsCorrectNumClosedIssues,
  521. "repository count 'num_closed_issues'",
  522. },
  523. // Repository.NumClosedPulls
  524. {
  525. 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, true),
  526. repoStatsCorrectNumClosedPulls,
  527. "repository count 'num_closed_pulls'",
  528. },
  529. // Label.NumIssues
  530. {
  531. statsQuery("SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)"),
  532. labelStatsCorrectNumIssues,
  533. "label count 'num_issues'",
  534. },
  535. // Label.NumClosedIssues
  536. {
  537. 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),
  538. labelStatsCorrectNumClosedIssues,
  539. "label count 'num_closed_issues'",
  540. },
  541. // Milestone.Num{,Closed}Issues
  542. {
  543. statsQuery(milestoneStatsQueryNumIssues, true),
  544. issues_model.UpdateMilestoneCounters,
  545. "milestone count 'num_closed_issues' and 'num_issues'",
  546. },
  547. // User.NumRepos
  548. {
  549. statsQuery("SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)"),
  550. userStatsCorrectNumRepos,
  551. "user count 'num_repos'",
  552. },
  553. // Issue.NumComments
  554. {
  555. statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"),
  556. repoStatsCorrectIssueNumComments,
  557. "issue count 'num_comments'",
  558. },
  559. }
  560. for _, checker := range checkers {
  561. select {
  562. case <-ctx.Done():
  563. log.Warn("CheckRepoStats: Cancelled before %s", checker.desc)
  564. return db.ErrCancelledf("before checking %s", checker.desc)
  565. default:
  566. repoStatsCheck(ctx, checker)
  567. }
  568. }
  569. // FIXME: use checker when stop supporting old fork repo format.
  570. // ***** START: Repository.NumForks *****
  571. e := db.GetEngine(ctx)
  572. results, err := e.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
  573. if err != nil {
  574. log.Error("Select repository count 'num_forks': %v", err)
  575. } else {
  576. for _, result := range results {
  577. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  578. select {
  579. case <-ctx.Done():
  580. log.Warn("CheckRepoStats: Cancelled")
  581. return db.ErrCancelledf("during repository count 'num_fork' for repo ID %d", id)
  582. default:
  583. }
  584. log.Trace("Updating repository count 'num_forks': %d", id)
  585. repo, err := repo_model.GetRepositoryByID(id)
  586. if err != nil {
  587. log.Error("repo_model.GetRepositoryByID[%d]: %v", id, err)
  588. continue
  589. }
  590. _, err = e.SQL("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID).Get(&repo.NumForks)
  591. if err != nil {
  592. log.Error("Select count of forks[%d]: %v", repo.ID, err)
  593. continue
  594. }
  595. if _, err = e.ID(repo.ID).Cols("num_forks").Update(repo); err != nil {
  596. log.Error("UpdateRepository[%d]: %v", id, err)
  597. continue
  598. }
  599. }
  600. }
  601. // ***** END: Repository.NumForks *****
  602. return nil
  603. }
  604. func UpdateRepoStats(ctx context.Context, id int64) error {
  605. var err error
  606. for _, f := range []func(ctx context.Context, id int64) error{
  607. repoStatsCorrectNumWatches,
  608. repoStatsCorrectNumStars,
  609. repoStatsCorrectNumIssues,
  610. repoStatsCorrectNumPulls,
  611. repoStatsCorrectNumClosedIssues,
  612. repoStatsCorrectNumClosedPulls,
  613. labelStatsCorrectNumIssuesRepo,
  614. labelStatsCorrectNumClosedIssuesRepo,
  615. milestoneStatsCorrectNumIssuesRepo,
  616. } {
  617. err = f(ctx, id)
  618. if err != nil {
  619. return err
  620. }
  621. }
  622. return nil
  623. }
  624. func updateUserStarNumbers(users []user_model.User) error {
  625. ctx, committer, err := db.TxContext()
  626. if err != nil {
  627. return err
  628. }
  629. defer committer.Close()
  630. for _, user := range users {
  631. if _, err = db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil {
  632. return err
  633. }
  634. }
  635. return committer.Commit()
  636. }
  637. // DoctorUserStarNum recalculate Stars number for all user
  638. func DoctorUserStarNum() (err error) {
  639. const batchSize = 100
  640. for start := 0; ; start += batchSize {
  641. users := make([]user_model.User, 0, batchSize)
  642. if err = db.GetEngine(db.DefaultContext).Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil {
  643. return
  644. }
  645. if len(users) == 0 {
  646. break
  647. }
  648. if err = updateUserStarNumbers(users); err != nil {
  649. return
  650. }
  651. }
  652. log.Debug("recalculate Stars number for all user finished")
  653. return err
  654. }
  655. // DeleteDeployKey delete deploy keys
  656. func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error {
  657. key, err := asymkey_model.GetDeployKeyByID(ctx, id)
  658. if err != nil {
  659. if asymkey_model.IsErrDeployKeyNotExist(err) {
  660. return nil
  661. }
  662. return fmt.Errorf("GetDeployKeyByID: %v", err)
  663. }
  664. // Check if user has access to delete this key.
  665. if !doer.IsAdmin {
  666. repo, err := repo_model.GetRepositoryByIDCtx(ctx, key.RepoID)
  667. if err != nil {
  668. return fmt.Errorf("GetRepositoryByID: %v", err)
  669. }
  670. has, err := access_model.IsUserRepoAdmin(ctx, repo, doer)
  671. if err != nil {
  672. return fmt.Errorf("GetUserRepoPermission: %v", err)
  673. } else if !has {
  674. return asymkey_model.ErrKeyAccessDenied{
  675. UserID: doer.ID,
  676. KeyID: key.ID,
  677. Note: "deploy",
  678. }
  679. }
  680. }
  681. if _, err := db.DeleteByBean(ctx, &asymkey_model.DeployKey{
  682. ID: key.ID,
  683. }); err != nil {
  684. return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err)
  685. }
  686. // Check if this is the last reference to same key content.
  687. has, err := asymkey_model.IsDeployKeyExistByKeyID(ctx, key.KeyID)
  688. if err != nil {
  689. return err
  690. } else if !has {
  691. if err = asymkey_model.DeletePublicKeys(ctx, key.KeyID); err != nil {
  692. return err
  693. }
  694. }
  695. return nil
  696. }