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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  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. "os"
  10. "path"
  11. "strconv"
  12. "strings"
  13. "unicode/utf8"
  14. _ "image/jpeg" // Needed for jpeg support
  15. admin_model "code.gitea.io/gitea/models/admin"
  16. asymkey_model "code.gitea.io/gitea/models/asymkey"
  17. "code.gitea.io/gitea/models/db"
  18. issues_model "code.gitea.io/gitea/models/issues"
  19. "code.gitea.io/gitea/models/organization"
  20. "code.gitea.io/gitea/models/perm"
  21. access_model "code.gitea.io/gitea/models/perm/access"
  22. project_model "code.gitea.io/gitea/models/project"
  23. repo_model "code.gitea.io/gitea/models/repo"
  24. "code.gitea.io/gitea/models/unit"
  25. user_model "code.gitea.io/gitea/models/user"
  26. "code.gitea.io/gitea/models/webhook"
  27. "code.gitea.io/gitea/modules/lfs"
  28. "code.gitea.io/gitea/modules/log"
  29. "code.gitea.io/gitea/modules/setting"
  30. "code.gitea.io/gitea/modules/storage"
  31. api "code.gitea.io/gitea/modules/structs"
  32. "code.gitea.io/gitea/modules/util"
  33. "xorm.io/builder"
  34. )
  35. // ItemsPerPage maximum items per page in forks, watchers and stars of a repo
  36. var ItemsPerPage = 40
  37. // NewRepoContext creates a new repository context
  38. func NewRepoContext() {
  39. unit.LoadUnitConfig()
  40. }
  41. // CheckRepoUnitUser check whether user could visit the unit of this repository
  42. func CheckRepoUnitUser(repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
  43. return checkRepoUnitUser(db.DefaultContext, repo, user, unitType)
  44. }
  45. func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
  46. if user != nil && user.IsAdmin {
  47. return true
  48. }
  49. perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
  50. if err != nil {
  51. log.Error("GetUserRepoPermission(): %v", err)
  52. return false
  53. }
  54. return perm.CanRead(unitType)
  55. }
  56. func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
  57. if err = repo.GetOwner(ctx); err != nil {
  58. return nil, err
  59. }
  60. e := db.GetEngine(ctx)
  61. userIDs := make([]int64, 0, 10)
  62. if err = e.Table("access").
  63. Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
  64. Select("user_id").
  65. Find(&userIDs); err != nil {
  66. return nil, err
  67. }
  68. additionalUserIDs := make([]int64, 0, 10)
  69. if err = e.Table("team_user").
  70. Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
  71. Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
  72. Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
  73. Distinct("`team_user`.uid").
  74. Select("`team_user`.uid").
  75. Find(&additionalUserIDs); err != nil {
  76. return nil, err
  77. }
  78. uidMap := map[int64]bool{}
  79. i := 0
  80. for _, uid := range userIDs {
  81. if uidMap[uid] {
  82. continue
  83. }
  84. uidMap[uid] = true
  85. userIDs[i] = uid
  86. i++
  87. }
  88. userIDs = userIDs[:i]
  89. userIDs = append(userIDs, additionalUserIDs...)
  90. for _, uid := range additionalUserIDs {
  91. if uidMap[uid] {
  92. continue
  93. }
  94. userIDs[i] = uid
  95. i++
  96. }
  97. userIDs = userIDs[:i]
  98. // Leave a seat for owner itself to append later, but if owner is an organization
  99. // and just waste 1 unit is cheaper than re-allocate memory once.
  100. users := make([]*user_model.User, 0, len(userIDs)+1)
  101. if len(userIDs) > 0 {
  102. if err = e.In("id", userIDs).Find(&users); err != nil {
  103. return nil, err
  104. }
  105. }
  106. if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
  107. users = append(users, repo.Owner)
  108. }
  109. return users, nil
  110. }
  111. // GetRepoAssignees returns all users that have write access and can be assigned to issues
  112. // of the repository,
  113. func GetRepoAssignees(repo *repo_model.Repository) (_ []*user_model.User, err error) {
  114. return getRepoAssignees(db.DefaultContext, repo)
  115. }
  116. func getReviewers(ctx context.Context, repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) {
  117. // Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries
  118. if err := repo.GetOwner(ctx); err != nil {
  119. return nil, err
  120. }
  121. cond := builder.And(builder.Neq{"`user`.id": posterID})
  122. if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
  123. // This a private repository:
  124. // Anyone who can read the repository is a requestable reviewer
  125. cond = cond.And(builder.In("`user`.id",
  126. builder.Select("user_id").From("access").Where(
  127. builder.Eq{"repo_id": repo.ID}.
  128. And(builder.Gte{"mode": perm.AccessModeRead}),
  129. ),
  130. ))
  131. if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID {
  132. // as private *user* repos don't generate an entry in the `access` table,
  133. // the owner of a private repo needs to be explicitly added.
  134. cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID})
  135. }
  136. } else {
  137. // This is a "public" repository:
  138. // Any user that has read access, is a watcher or organization member can be requested to review
  139. cond = cond.And(builder.And(builder.In("`user`.id",
  140. builder.Select("user_id").From("access").
  141. Where(builder.Eq{"repo_id": repo.ID}.
  142. And(builder.Gte{"mode": perm.AccessModeRead})),
  143. ).Or(builder.In("`user`.id",
  144. builder.Select("user_id").From("watch").
  145. Where(builder.Eq{"repo_id": repo.ID}.
  146. And(builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),
  147. ).Or(builder.In("`user`.id",
  148. builder.Select("uid").From("org_user").
  149. Where(builder.Eq{"org_id": repo.OwnerID}),
  150. )))))
  151. }
  152. users := make([]*user_model.User, 0, 8)
  153. return users, db.GetEngine(ctx).Where(cond).OrderBy("name").Find(&users)
  154. }
  155. // GetReviewers get all users can be requested to review:
  156. // * for private repositories this returns all users that have read access or higher to the repository.
  157. // * for public repositories this returns all users that have read access or higher to the repository,
  158. // all repo watchers and all organization members.
  159. // TODO: may be we should have a busy choice for users to block review request to them.
  160. func GetReviewers(repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) {
  161. return getReviewers(db.DefaultContext, repo, doerID, posterID)
  162. }
  163. // GetReviewerTeams get all teams can be requested to review
  164. func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) {
  165. if err := repo.GetOwner(db.DefaultContext); err != nil {
  166. return nil, err
  167. }
  168. if !repo.Owner.IsOrganization() {
  169. return nil, nil
  170. }
  171. teams, err := organization.GetTeamsWithAccessToRepo(db.DefaultContext, repo.OwnerID, repo.ID, perm.AccessModeRead)
  172. if err != nil {
  173. return nil, err
  174. }
  175. return teams, err
  176. }
  177. // UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
  178. func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
  179. size, err := util.GetDirectorySize(repo.RepoPath())
  180. if err != nil {
  181. return fmt.Errorf("updateSize: %v", err)
  182. }
  183. lfsSize, err := db.GetEngine(ctx).Where("repository_id = ?", repo.ID).SumInt(new(LFSMetaObject), "size")
  184. if err != nil {
  185. return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err)
  186. }
  187. repo.Size = size + lfsSize
  188. _, err = db.GetEngine(ctx).ID(repo.ID).Cols("size").NoAutoTime().Update(repo)
  189. return err
  190. }
  191. // CanUserForkRepo returns true if specified user can fork repository.
  192. func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, error) {
  193. if user == nil {
  194. return false, nil
  195. }
  196. if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) {
  197. return true, nil
  198. }
  199. ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(user.ID)
  200. if err != nil {
  201. return false, err
  202. }
  203. for _, org := range ownedOrgs {
  204. if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) {
  205. return true, nil
  206. }
  207. }
  208. return false, nil
  209. }
  210. // FindUserOrgForks returns the forked repositories for one user from a repository
  211. func FindUserOrgForks(ctx context.Context, repoID, userID int64) ([]*repo_model.Repository, error) {
  212. cond := builder.And(
  213. builder.Eq{"fork_id": repoID},
  214. builder.In("owner_id",
  215. builder.Select("org_id").
  216. From("org_user").
  217. Where(builder.Eq{"uid": userID}),
  218. ),
  219. )
  220. var repos []*repo_model.Repository
  221. return repos, db.GetEngine(ctx).Table("repository").Where(cond).Find(&repos)
  222. }
  223. // GetForksByUserAndOrgs return forked repos of the user and owned orgs
  224. func GetForksByUserAndOrgs(ctx context.Context, user *user_model.User, repo *repo_model.Repository) ([]*repo_model.Repository, error) {
  225. var repoList []*repo_model.Repository
  226. if user == nil {
  227. return repoList, nil
  228. }
  229. forkedRepo, err := repo_model.GetUserFork(ctx, repo.ID, user.ID)
  230. if err != nil {
  231. return repoList, err
  232. }
  233. if forkedRepo != nil {
  234. repoList = append(repoList, forkedRepo)
  235. }
  236. orgForks, err := FindUserOrgForks(ctx, repo.ID, user.ID)
  237. if err != nil {
  238. return nil, err
  239. }
  240. repoList = append(repoList, orgForks...)
  241. return repoList, nil
  242. }
  243. // CanUserDelete returns true if user could delete the repository
  244. func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) {
  245. if user.IsAdmin || user.ID == repo.OwnerID {
  246. return true, nil
  247. }
  248. if err := repo.GetOwner(db.DefaultContext); err != nil {
  249. return false, err
  250. }
  251. if repo.Owner.IsOrganization() {
  252. isOwner, err := organization.OrgFromUser(repo.Owner).IsOwnedBy(user.ID)
  253. if err != nil {
  254. return false, err
  255. } else if isOwner {
  256. return true, nil
  257. }
  258. }
  259. return false, nil
  260. }
  261. // CreateRepoOptions contains the create repository options
  262. type CreateRepoOptions struct {
  263. Name string
  264. Description string
  265. OriginalURL string
  266. GitServiceType api.GitServiceType
  267. Gitignores string
  268. IssueLabels string
  269. License string
  270. Readme string
  271. DefaultBranch string
  272. IsPrivate bool
  273. IsMirror bool
  274. IsTemplate bool
  275. AutoInit bool
  276. Status repo_model.RepositoryStatus
  277. TrustModel repo_model.TrustModelType
  278. MirrorInterval string
  279. }
  280. // CreateRepository creates a repository for the user/organization.
  281. func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt bool) (err error) {
  282. if err = repo_model.IsUsableRepoName(repo.Name); err != nil {
  283. return err
  284. }
  285. has, err := repo_model.IsRepositoryExist(ctx, u, repo.Name)
  286. if err != nil {
  287. return fmt.Errorf("IsRepositoryExist: %v", err)
  288. } else if has {
  289. return repo_model.ErrRepoAlreadyExist{
  290. Uname: u.Name,
  291. Name: repo.Name,
  292. }
  293. }
  294. repoPath := repo_model.RepoPath(u.Name, repo.Name)
  295. isExist, err := util.IsExist(repoPath)
  296. if err != nil {
  297. log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
  298. return err
  299. }
  300. if !overwriteOrAdopt && isExist {
  301. log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
  302. return repo_model.ErrRepoFilesAlreadyExist{
  303. Uname: u.Name,
  304. Name: repo.Name,
  305. }
  306. }
  307. if err = db.Insert(ctx, repo); err != nil {
  308. return err
  309. }
  310. if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil {
  311. return err
  312. }
  313. // insert units for repo
  314. units := make([]repo_model.RepoUnit, 0, len(unit.DefaultRepoUnits))
  315. for _, tp := range unit.DefaultRepoUnits {
  316. if tp == unit.TypeIssues {
  317. units = append(units, repo_model.RepoUnit{
  318. RepoID: repo.ID,
  319. Type: tp,
  320. Config: &repo_model.IssuesConfig{
  321. EnableTimetracker: setting.Service.DefaultEnableTimetracking,
  322. AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
  323. EnableDependencies: setting.Service.DefaultEnableDependencies,
  324. },
  325. })
  326. } else if tp == unit.TypePullRequests {
  327. units = append(units, repo_model.RepoUnit{
  328. RepoID: repo.ID,
  329. Type: tp,
  330. Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyleMerge, AllowRebaseUpdate: true},
  331. })
  332. } else {
  333. units = append(units, repo_model.RepoUnit{
  334. RepoID: repo.ID,
  335. Type: tp,
  336. })
  337. }
  338. }
  339. if err = db.Insert(ctx, units); err != nil {
  340. return err
  341. }
  342. // Remember visibility preference.
  343. u.LastRepoVisibility = repo.IsPrivate
  344. if err = user_model.UpdateUserCols(ctx, u, "last_repo_visibility"); err != nil {
  345. return fmt.Errorf("updateUser: %v", err)
  346. }
  347. if _, err = db.GetEngine(ctx).Incr("num_repos").ID(u.ID).Update(new(user_model.User)); err != nil {
  348. return fmt.Errorf("increment user total_repos: %v", err)
  349. }
  350. u.NumRepos++
  351. // Give access to all members in teams with access to all repositories.
  352. if u.IsOrganization() {
  353. teams, err := organization.FindOrgTeams(ctx, u.ID)
  354. if err != nil {
  355. return fmt.Errorf("loadTeams: %v", err)
  356. }
  357. for _, t := range teams {
  358. if t.IncludesAllRepositories {
  359. if err := addRepository(ctx, t, repo); err != nil {
  360. return fmt.Errorf("addRepository: %v", err)
  361. }
  362. }
  363. }
  364. if isAdmin, err := access_model.IsUserRepoAdmin(ctx, repo, doer); err != nil {
  365. return fmt.Errorf("IsUserRepoAdminCtx: %v", err)
  366. } else if !isAdmin {
  367. // Make creator repo admin if it wasn't assigned automatically
  368. if err = addCollaborator(ctx, repo, doer); err != nil {
  369. return fmt.Errorf("AddCollaborator: %v", err)
  370. }
  371. if err = repo_model.ChangeCollaborationAccessModeCtx(ctx, repo, doer.ID, perm.AccessModeAdmin); err != nil {
  372. return fmt.Errorf("ChangeCollaborationAccessMode: %v", err)
  373. }
  374. }
  375. } else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
  376. // Organization automatically called this in addRepository method.
  377. return fmt.Errorf("recalculateAccesses: %v", err)
  378. }
  379. if setting.Service.AutoWatchNewRepos {
  380. if err = repo_model.WatchRepo(ctx, doer.ID, repo.ID, true); err != nil {
  381. return fmt.Errorf("watchRepo: %v", err)
  382. }
  383. }
  384. if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil {
  385. return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err)
  386. }
  387. return nil
  388. }
  389. // CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
  390. func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error {
  391. if err := repo.GetOwner(ctx); err != nil {
  392. return err
  393. }
  394. // Create/Remove git-daemon-export-ok for git-daemon...
  395. daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
  396. isExist, err := util.IsExist(daemonExportFile)
  397. if err != nil {
  398. log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
  399. return err
  400. }
  401. isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic
  402. if !isPublic && isExist {
  403. if err = util.Remove(daemonExportFile); err != nil {
  404. log.Error("Failed to remove %s: %v", daemonExportFile, err)
  405. }
  406. } else if isPublic && !isExist {
  407. if f, err := os.Create(daemonExportFile); err != nil {
  408. log.Error("Failed to create %s: %v", daemonExportFile, err)
  409. } else {
  410. f.Close()
  411. }
  412. }
  413. return nil
  414. }
  415. // IncrementRepoForkNum increment repository fork number
  416. func IncrementRepoForkNum(ctx context.Context, repoID int64) error {
  417. _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID)
  418. return err
  419. }
  420. // DecrementRepoForkNum decrement repository fork number
  421. func DecrementRepoForkNum(ctx context.Context, repoID int64) error {
  422. _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repoID)
  423. return err
  424. }
  425. // UpdateRepositoryCtx updates a repository with db context
  426. func UpdateRepositoryCtx(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) {
  427. repo.LowerName = strings.ToLower(repo.Name)
  428. if utf8.RuneCountInString(repo.Description) > 255 {
  429. repo.Description = string([]rune(repo.Description)[:255])
  430. }
  431. if utf8.RuneCountInString(repo.Website) > 255 {
  432. repo.Website = string([]rune(repo.Website)[:255])
  433. }
  434. e := db.GetEngine(ctx)
  435. if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
  436. return fmt.Errorf("update: %v", err)
  437. }
  438. if err = UpdateRepoSize(ctx, repo); err != nil {
  439. log.Error("Failed to update size for repository: %v", err)
  440. }
  441. if visibilityChanged {
  442. if err = repo.GetOwner(ctx); err != nil {
  443. return fmt.Errorf("getOwner: %v", err)
  444. }
  445. if repo.Owner.IsOrganization() {
  446. // Organization repository need to recalculate access table when visibility is changed.
  447. if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
  448. return fmt.Errorf("recalculateTeamAccesses: %v", err)
  449. }
  450. }
  451. // If repo has become private, we need to set its actions to private.
  452. if repo.IsPrivate {
  453. _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{
  454. IsPrivate: true,
  455. })
  456. if err != nil {
  457. return err
  458. }
  459. }
  460. // Create/Remove git-daemon-export-ok for git-daemon...
  461. if err := CheckDaemonExportOK(ctx, repo); err != nil {
  462. return err
  463. }
  464. forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
  465. if err != nil {
  466. return fmt.Errorf("GetRepositoriesByForkID: %v", err)
  467. }
  468. for i := range forkRepos {
  469. forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate
  470. if err = UpdateRepositoryCtx(ctx, forkRepos[i], true); err != nil {
  471. return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err)
  472. }
  473. }
  474. }
  475. return nil
  476. }
  477. // UpdateRepository updates a repository
  478. func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err error) {
  479. ctx, committer, err := db.TxContext()
  480. if err != nil {
  481. return err
  482. }
  483. defer committer.Close()
  484. if err = UpdateRepositoryCtx(ctx, repo, visibilityChanged); err != nil {
  485. return fmt.Errorf("updateRepository: %v", err)
  486. }
  487. return committer.Commit()
  488. }
  489. // DeleteRepository deletes a repository for a user or organization.
  490. // make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock)
  491. func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
  492. ctx, committer, err := db.TxContext()
  493. if err != nil {
  494. return err
  495. }
  496. defer committer.Close()
  497. sess := db.GetEngine(ctx)
  498. // In case is a organization.
  499. org, err := user_model.GetUserByIDCtx(ctx, uid)
  500. if err != nil {
  501. return err
  502. }
  503. repo := &repo_model.Repository{OwnerID: uid}
  504. has, err := sess.ID(repoID).Get(repo)
  505. if err != nil {
  506. return err
  507. } else if !has {
  508. return repo_model.ErrRepoNotExist{
  509. ID: repoID,
  510. UID: uid,
  511. OwnerName: "",
  512. Name: "",
  513. }
  514. }
  515. // Delete Deploy Keys
  516. deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID})
  517. if err != nil {
  518. return fmt.Errorf("listDeployKeys: %v", err)
  519. }
  520. needRewriteKeysFile := len(deployKeys) > 0
  521. for _, dKey := range deployKeys {
  522. if err := DeleteDeployKey(ctx, doer, dKey.ID); err != nil {
  523. return fmt.Errorf("deleteDeployKeys: %v", err)
  524. }
  525. }
  526. if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil {
  527. return err
  528. } else if cnt != 1 {
  529. return repo_model.ErrRepoNotExist{
  530. ID: repoID,
  531. UID: uid,
  532. OwnerName: "",
  533. Name: "",
  534. }
  535. }
  536. if org.IsOrganization() {
  537. teams, err := organization.FindOrgTeams(ctx, org.ID)
  538. if err != nil {
  539. return err
  540. }
  541. for _, t := range teams {
  542. if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) {
  543. continue
  544. } else if err = removeRepository(ctx, t, repo, false); err != nil {
  545. return err
  546. }
  547. }
  548. }
  549. attachments := make([]*repo_model.Attachment, 0, 20)
  550. if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id").
  551. Where("`release`.repo_id = ?", repoID).
  552. Find(&attachments); err != nil {
  553. return err
  554. }
  555. releaseAttachments := make([]string, 0, len(attachments))
  556. for i := 0; i < len(attachments); i++ {
  557. releaseAttachments = append(releaseAttachments, attachments[i].RelativePath())
  558. }
  559. 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 {
  560. return err
  561. }
  562. if err := db.DeleteBeans(ctx,
  563. &access_model.Access{RepoID: repo.ID},
  564. &Action{RepoID: repo.ID},
  565. &repo_model.Collaboration{RepoID: repoID},
  566. &Comment{RefRepoID: repoID},
  567. &CommitStatus{RepoID: repoID},
  568. &DeletedBranch{RepoID: repoID},
  569. &webhook.HookTask{RepoID: repoID},
  570. &LFSLock{RepoID: repoID},
  571. &repo_model.LanguageStat{RepoID: repoID},
  572. &issues_model.Milestone{RepoID: repoID},
  573. &repo_model.Mirror{RepoID: repoID},
  574. &Notification{RepoID: repoID},
  575. &ProtectedBranch{RepoID: repoID},
  576. &ProtectedTag{RepoID: repoID},
  577. &repo_model.PushMirror{RepoID: repoID},
  578. &Release{RepoID: repoID},
  579. &repo_model.RepoIndexerStatus{RepoID: repoID},
  580. &repo_model.Redirect{RedirectRepoID: repoID},
  581. &repo_model.RepoUnit{RepoID: repoID},
  582. &repo_model.Star{RepoID: repoID},
  583. &Task{RepoID: repoID},
  584. &repo_model.Watch{RepoID: repoID},
  585. &webhook.Webhook{RepoID: repoID},
  586. ); err != nil {
  587. return fmt.Errorf("deleteBeans: %v", err)
  588. }
  589. // Delete Labels and related objects
  590. if err := deleteLabelsByRepoID(ctx, repoID); err != nil {
  591. return err
  592. }
  593. // Delete Pulls and related objects
  594. if err := deletePullsByBaseRepoID(ctx, repoID); err != nil {
  595. return err
  596. }
  597. // Delete Issues and related objects
  598. var attachmentPaths []string
  599. if attachmentPaths, err = deleteIssuesByRepoID(ctx, repoID); err != nil {
  600. return err
  601. }
  602. // Delete issue index
  603. if err := db.DeleteResouceIndex(ctx, "issue_index", repoID); err != nil {
  604. return err
  605. }
  606. if repo.IsFork {
  607. if _, err := db.Exec(ctx, "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
  608. return fmt.Errorf("decrease fork count: %v", err)
  609. }
  610. }
  611. if _, err := db.Exec(ctx, "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil {
  612. return err
  613. }
  614. if len(repo.Topics) > 0 {
  615. if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil {
  616. return err
  617. }
  618. }
  619. projects, _, err := project_model.GetProjects(ctx, project_model.SearchOptions{
  620. RepoID: repoID,
  621. })
  622. if err != nil {
  623. return fmt.Errorf("get projects: %v", err)
  624. }
  625. for i := range projects {
  626. if err := project_model.DeleteProjectByIDCtx(ctx, projects[i].ID); err != nil {
  627. return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err)
  628. }
  629. }
  630. // Remove LFS objects
  631. var lfsObjects []*LFSMetaObject
  632. if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
  633. return err
  634. }
  635. lfsPaths := make([]string, 0, len(lfsObjects))
  636. for _, v := range lfsObjects {
  637. count, err := db.CountByBean(ctx, &LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}})
  638. if err != nil {
  639. return err
  640. }
  641. if count > 1 {
  642. continue
  643. }
  644. lfsPaths = append(lfsPaths, v.RelativePath())
  645. }
  646. if _, err := db.DeleteByBean(ctx, &LFSMetaObject{RepositoryID: repoID}); err != nil {
  647. return err
  648. }
  649. // Remove archives
  650. var archives []*repo_model.RepoArchiver
  651. if err = sess.Where("repo_id=?", repoID).Find(&archives); err != nil {
  652. return err
  653. }
  654. archivePaths := make([]string, 0, len(archives))
  655. for _, v := range archives {
  656. p, _ := v.RelativePath()
  657. archivePaths = append(archivePaths, p)
  658. }
  659. if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil {
  660. return err
  661. }
  662. if repo.NumForks > 0 {
  663. if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
  664. log.Error("reset 'fork_id' and 'is_fork': %v", err)
  665. }
  666. }
  667. // Get all attachments with both issue_id and release_id are zero
  668. var newAttachments []*repo_model.Attachment
  669. if err := sess.Where(builder.Eq{
  670. "repo_id": repo.ID,
  671. "issue_id": 0,
  672. "release_id": 0,
  673. }).Find(&newAttachments); err != nil {
  674. return err
  675. }
  676. newAttachmentPaths := make([]string, 0, len(newAttachments))
  677. for _, attach := range newAttachments {
  678. newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath())
  679. }
  680. if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil {
  681. return err
  682. }
  683. if err = committer.Commit(); err != nil {
  684. return err
  685. }
  686. committer.Close()
  687. if needRewriteKeysFile {
  688. if err := asymkey_model.RewriteAllPublicKeys(); err != nil {
  689. log.Error("RewriteAllPublicKeys failed: %v", err)
  690. }
  691. }
  692. // We should always delete the files after the database transaction succeed. If
  693. // we delete the file but the database rollback, the repository will be broken.
  694. // Remove repository files.
  695. repoPath := repo.RepoPath()
  696. admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
  697. // Remove wiki files
  698. if repo.HasWiki() {
  699. admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath())
  700. }
  701. // Remove archives
  702. for _, archive := range archivePaths {
  703. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive)
  704. }
  705. // Remove lfs objects
  706. for _, lfsObj := range lfsPaths {
  707. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj)
  708. }
  709. // Remove issue attachment files.
  710. for _, attachment := range attachmentPaths {
  711. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment)
  712. }
  713. // Remove release attachment files.
  714. for _, releaseAttachment := range releaseAttachments {
  715. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment)
  716. }
  717. // Remove attachment with no issue_id and release_id.
  718. for _, newAttachment := range newAttachmentPaths {
  719. admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment)
  720. }
  721. if len(repo.Avatar) > 0 {
  722. if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil {
  723. return fmt.Errorf("Failed to remove %s: %v", repo.Avatar, err)
  724. }
  725. }
  726. return nil
  727. }
  728. type repoChecker struct {
  729. querySQL func(ctx context.Context) ([]map[string][]byte, error)
  730. correctSQL func(ctx context.Context, id int64) error
  731. desc string
  732. }
  733. func repoStatsCheck(ctx context.Context, checker *repoChecker) {
  734. results, err := checker.querySQL(ctx)
  735. if err != nil {
  736. log.Error("Select %s: %v", checker.desc, err)
  737. return
  738. }
  739. for _, result := range results {
  740. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  741. select {
  742. case <-ctx.Done():
  743. log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
  744. return
  745. default:
  746. }
  747. log.Trace("Updating %s: %d", checker.desc, id)
  748. err = checker.correctSQL(ctx, id)
  749. if err != nil {
  750. log.Error("Update %s[%d]: %v", checker.desc, id, err)
  751. }
  752. }
  753. }
  754. func StatsCorrectSQL(ctx context.Context, sql string, id int64) error {
  755. _, err := db.GetEngine(ctx).Exec(sql, id, id)
  756. return err
  757. }
  758. func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
  759. return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id)
  760. }
  761. func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
  762. return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id)
  763. }
  764. func labelStatsCorrectNumIssues(ctx context.Context, id int64) error {
  765. return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id)
  766. }
  767. func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
  768. _, err := db.GetEngine(ctx).Exec("UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=id) WHERE repo_id=?", id)
  769. return err
  770. }
  771. func labelStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
  772. _, 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)
  773. return err
  774. }
  775. func labelStatsCorrectNumClosedIssuesRepo(ctx context.Context, id int64) error {
  776. _, 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)
  777. return err
  778. }
  779. 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)"
  780. func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
  781. e := db.GetEngine(ctx)
  782. results, err := e.Query(milestoneStatsQueryNumIssues+" AND `milestone`.repo_id = ?", true, id)
  783. if err != nil {
  784. return err
  785. }
  786. for _, result := range results {
  787. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  788. err = issues_model.UpdateMilestoneCounters(ctx, id)
  789. if err != nil {
  790. return err
  791. }
  792. }
  793. return nil
  794. }
  795. func userStatsCorrectNumRepos(ctx context.Context, id int64) error {
  796. return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id)
  797. }
  798. func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
  799. return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id)
  800. }
  801. func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
  802. return repoStatsCorrectNum(ctx, id, false, "num_issues")
  803. }
  804. func repoStatsCorrectNumPulls(ctx context.Context, id int64) error {
  805. return repoStatsCorrectNum(ctx, id, true, "num_pulls")
  806. }
  807. func repoStatsCorrectNum(ctx context.Context, id int64, isPull bool, field string) error {
  808. _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=?) WHERE id=?", id, isPull, id)
  809. return err
  810. }
  811. func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
  812. return repoStatsCorrectNumClosed(ctx, id, false, "num_closed_issues")
  813. }
  814. func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
  815. return repoStatsCorrectNumClosed(ctx, id, true, "num_closed_pulls")
  816. }
  817. func repoStatsCorrectNumClosed(ctx context.Context, id int64, isPull bool, field string) error {
  818. _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, isPull, id)
  819. return err
  820. }
  821. func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) {
  822. return func(ctx context.Context) ([]map[string][]byte, error) {
  823. return db.GetEngine(ctx).Query(args...)
  824. }
  825. }
  826. // CheckRepoStats checks the repository stats
  827. func CheckRepoStats(ctx context.Context) error {
  828. log.Trace("Doing: CheckRepoStats")
  829. checkers := []*repoChecker{
  830. // Repository.NumWatches
  831. {
  832. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)"),
  833. repoStatsCorrectNumWatches,
  834. "repository count 'num_watches'",
  835. },
  836. // Repository.NumStars
  837. {
  838. statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)"),
  839. repoStatsCorrectNumStars,
  840. "repository count 'num_stars'",
  841. },
  842. // Repository.NumClosedIssues
  843. {
  844. 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),
  845. repoStatsCorrectNumClosedIssues,
  846. "repository count 'num_closed_issues'",
  847. },
  848. // Repository.NumClosedPulls
  849. {
  850. 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),
  851. repoStatsCorrectNumClosedPulls,
  852. "repository count 'num_closed_pulls'",
  853. },
  854. // Label.NumIssues
  855. {
  856. statsQuery("SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)"),
  857. labelStatsCorrectNumIssues,
  858. "label count 'num_issues'",
  859. },
  860. // Label.NumClosedIssues
  861. {
  862. 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),
  863. labelStatsCorrectNumClosedIssues,
  864. "label count 'num_closed_issues'",
  865. },
  866. // Milestone.Num{,Closed}Issues
  867. {
  868. statsQuery(milestoneStatsQueryNumIssues, true),
  869. issues_model.UpdateMilestoneCounters,
  870. "milestone count 'num_closed_issues' and 'num_issues'",
  871. },
  872. // User.NumRepos
  873. {
  874. statsQuery("SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)"),
  875. userStatsCorrectNumRepos,
  876. "user count 'num_repos'",
  877. },
  878. // Issue.NumComments
  879. {
  880. statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"),
  881. repoStatsCorrectIssueNumComments,
  882. "issue count 'num_comments'",
  883. },
  884. }
  885. for _, checker := range checkers {
  886. select {
  887. case <-ctx.Done():
  888. log.Warn("CheckRepoStats: Cancelled before %s", checker.desc)
  889. return db.ErrCancelledf("before checking %s", checker.desc)
  890. default:
  891. repoStatsCheck(ctx, checker)
  892. }
  893. }
  894. // FIXME: use checker when stop supporting old fork repo format.
  895. // ***** START: Repository.NumForks *****
  896. e := db.GetEngine(ctx)
  897. results, err := e.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
  898. if err != nil {
  899. log.Error("Select repository count 'num_forks': %v", err)
  900. } else {
  901. for _, result := range results {
  902. id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
  903. select {
  904. case <-ctx.Done():
  905. log.Warn("CheckRepoStats: Cancelled")
  906. return db.ErrCancelledf("during repository count 'num_fork' for repo ID %d", id)
  907. default:
  908. }
  909. log.Trace("Updating repository count 'num_forks': %d", id)
  910. repo, err := repo_model.GetRepositoryByID(id)
  911. if err != nil {
  912. log.Error("repo_model.GetRepositoryByID[%d]: %v", id, err)
  913. continue
  914. }
  915. rawResult, err := db.GetEngine(db.DefaultContext).Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID)
  916. if err != nil {
  917. log.Error("Select count of forks[%d]: %v", repo.ID, err)
  918. continue
  919. }
  920. repo.NumForks = int(parseCountResult(rawResult))
  921. if err = UpdateRepository(repo, false); err != nil {
  922. log.Error("UpdateRepository[%d]: %v", id, err)
  923. continue
  924. }
  925. }
  926. }
  927. // ***** END: Repository.NumForks *****
  928. return nil
  929. }
  930. func UpdateRepoStats(ctx context.Context, id int64) error {
  931. var err error
  932. for _, f := range []func(ctx context.Context, id int64) error{
  933. repoStatsCorrectNumWatches,
  934. repoStatsCorrectNumStars,
  935. repoStatsCorrectNumIssues,
  936. repoStatsCorrectNumPulls,
  937. repoStatsCorrectNumClosedIssues,
  938. repoStatsCorrectNumClosedPulls,
  939. labelStatsCorrectNumIssuesRepo,
  940. labelStatsCorrectNumClosedIssuesRepo,
  941. milestoneStatsCorrectNumIssuesRepo,
  942. } {
  943. err = f(ctx, id)
  944. if err != nil {
  945. return err
  946. }
  947. }
  948. return nil
  949. }
  950. func updateUserStarNumbers(users []user_model.User) error {
  951. ctx, committer, err := db.TxContext()
  952. if err != nil {
  953. return err
  954. }
  955. defer committer.Close()
  956. for _, user := range users {
  957. if _, err = db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil {
  958. return err
  959. }
  960. }
  961. return committer.Commit()
  962. }
  963. // DoctorUserStarNum recalculate Stars number for all user
  964. func DoctorUserStarNum() (err error) {
  965. const batchSize = 100
  966. for start := 0; ; start += batchSize {
  967. users := make([]user_model.User, 0, batchSize)
  968. if err = db.GetEngine(db.DefaultContext).Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil {
  969. return
  970. }
  971. if len(users) == 0 {
  972. break
  973. }
  974. if err = updateUserStarNumbers(users); err != nil {
  975. return
  976. }
  977. }
  978. log.Debug("recalculate Stars number for all user finished")
  979. return
  980. }
  981. // LinkedRepository returns the linked repo if any
  982. func LinkedRepository(a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) {
  983. if a.IssueID != 0 {
  984. iss, err := GetIssueByID(a.IssueID)
  985. if err != nil {
  986. return nil, unit.TypeIssues, err
  987. }
  988. repo, err := repo_model.GetRepositoryByID(iss.RepoID)
  989. unitType := unit.TypeIssues
  990. if iss.IsPull {
  991. unitType = unit.TypePullRequests
  992. }
  993. return repo, unitType, err
  994. } else if a.ReleaseID != 0 {
  995. rel, err := GetReleaseByID(a.ReleaseID)
  996. if err != nil {
  997. return nil, unit.TypeReleases, err
  998. }
  999. repo, err := repo_model.GetRepositoryByID(rel.RepoID)
  1000. return repo, unit.TypeReleases, err
  1001. }
  1002. return nil, -1, nil
  1003. }
  1004. // DeleteDeployKey delete deploy keys
  1005. func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error {
  1006. key, err := asymkey_model.GetDeployKeyByID(ctx, id)
  1007. if err != nil {
  1008. if asymkey_model.IsErrDeployKeyNotExist(err) {
  1009. return nil
  1010. }
  1011. return fmt.Errorf("GetDeployKeyByID: %v", err)
  1012. }
  1013. sess := db.GetEngine(ctx)
  1014. // Check if user has access to delete this key.
  1015. if !doer.IsAdmin {
  1016. repo, err := repo_model.GetRepositoryByIDCtx(ctx, key.RepoID)
  1017. if err != nil {
  1018. return fmt.Errorf("GetRepositoryByID: %v", err)
  1019. }
  1020. has, err := access_model.IsUserRepoAdmin(ctx, repo, doer)
  1021. if err != nil {
  1022. return fmt.Errorf("GetUserRepoPermission: %v", err)
  1023. } else if !has {
  1024. return asymkey_model.ErrKeyAccessDenied{
  1025. UserID: doer.ID,
  1026. KeyID: key.ID,
  1027. Note: "deploy",
  1028. }
  1029. }
  1030. }
  1031. if _, err = sess.ID(key.ID).Delete(new(asymkey_model.DeployKey)); err != nil {
  1032. return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err)
  1033. }
  1034. // Check if this is the last reference to same key content.
  1035. has, err := sess.
  1036. Where("key_id = ?", key.KeyID).
  1037. Get(new(asymkey_model.DeployKey))
  1038. if err != nil {
  1039. return err
  1040. } else if !has {
  1041. if err = asymkey_model.DeletePublicKeys(ctx, key.KeyID); err != nil {
  1042. return err
  1043. }
  1044. }
  1045. return nil
  1046. }