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 6package models 7 8import ( 9 "context" 10 "fmt" 11 "os" 12 "path" 13 "path/filepath" 14 "sort" 15 "strconv" 16 "strings" 17 "unicode/utf8" 18 19 _ "image/jpeg" // Needed for jpeg support 20 21 admin_model "code.gitea.io/gitea/models/admin" 22 asymkey_model "code.gitea.io/gitea/models/asymkey" 23 "code.gitea.io/gitea/models/db" 24 "code.gitea.io/gitea/models/perm" 25 repo_model "code.gitea.io/gitea/models/repo" 26 "code.gitea.io/gitea/models/unit" 27 user_model "code.gitea.io/gitea/models/user" 28 "code.gitea.io/gitea/models/webhook" 29 "code.gitea.io/gitea/modules/lfs" 30 "code.gitea.io/gitea/modules/log" 31 "code.gitea.io/gitea/modules/options" 32 "code.gitea.io/gitea/modules/setting" 33 "code.gitea.io/gitea/modules/storage" 34 api "code.gitea.io/gitea/modules/structs" 35 "code.gitea.io/gitea/modules/util" 36 37 "xorm.io/builder" 38) 39 40var ( 41 // Gitignores contains the gitiginore files 42 Gitignores []string 43 44 // Licenses contains the license files 45 Licenses []string 46 47 // Readmes contains the readme files 48 Readmes []string 49 50 // LabelTemplates contains the label template files and the list of labels for each file 51 LabelTemplates map[string]string 52 53 // ItemsPerPage maximum items per page in forks, watchers and stars of a repo 54 ItemsPerPage = 40 55) 56 57// loadRepoConfig loads the repository config 58func loadRepoConfig() { 59 // Load .gitignore and license files and readme templates. 60 types := []string{"gitignore", "license", "readme", "label"} 61 typeFiles := make([][]string, 4) 62 for i, t := range types { 63 files, err := options.Dir(t) 64 if err != nil { 65 log.Fatal("Failed to get %s files: %v", t, err) 66 } 67 customPath := path.Join(setting.CustomPath, "options", t) 68 isDir, err := util.IsDir(customPath) 69 if err != nil { 70 log.Fatal("Failed to get custom %s files: %v", t, err) 71 } 72 if isDir { 73 customFiles, err := util.StatDir(customPath) 74 if err != nil { 75 log.Fatal("Failed to get custom %s files: %v", t, err) 76 } 77 78 for _, f := range customFiles { 79 if !util.IsStringInSlice(f, files, true) { 80 files = append(files, f) 81 } 82 } 83 } 84 typeFiles[i] = files 85 } 86 87 Gitignores = typeFiles[0] 88 Licenses = typeFiles[1] 89 Readmes = typeFiles[2] 90 LabelTemplatesFiles := typeFiles[3] 91 sort.Strings(Gitignores) 92 sort.Strings(Licenses) 93 sort.Strings(Readmes) 94 sort.Strings(LabelTemplatesFiles) 95 96 // Load label templates 97 LabelTemplates = make(map[string]string) 98 for _, templateFile := range LabelTemplatesFiles { 99 labels, err := LoadLabelsFormatted(templateFile) 100 if err != nil { 101 log.Error("Failed to load labels: %v", err) 102 } 103 LabelTemplates[templateFile] = labels 104 } 105 106 // Filter out invalid names and promote preferred licenses. 107 sortedLicenses := make([]string, 0, len(Licenses)) 108 for _, name := range setting.Repository.PreferredLicenses { 109 if util.IsStringInSlice(name, Licenses, true) { 110 sortedLicenses = append(sortedLicenses, name) 111 } 112 } 113 for _, name := range Licenses { 114 if !util.IsStringInSlice(name, setting.Repository.PreferredLicenses, true) { 115 sortedLicenses = append(sortedLicenses, name) 116 } 117 } 118 Licenses = sortedLicenses 119} 120 121// NewRepoContext creates a new repository context 122func NewRepoContext() { 123 loadRepoConfig() 124 unit.LoadUnitConfig() 125 126 admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) 127} 128 129// CheckRepoUnitUser check whether user could visit the unit of this repository 130func CheckRepoUnitUser(repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { 131 return checkRepoUnitUser(db.DefaultContext, repo, user, unitType) 132} 133 134func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { 135 if user.IsAdmin { 136 return true 137 } 138 perm, err := getUserRepoPermission(ctx, repo, user) 139 if err != nil { 140 log.Error("getUserRepoPermission(): %v", err) 141 return false 142 } 143 144 return perm.CanRead(unitType) 145} 146 147func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { 148 if err = repo.GetOwner(ctx); err != nil { 149 return nil, err 150 } 151 152 e := db.GetEngine(ctx) 153 userIDs := make([]int64, 0, 10) 154 if err = e.Table("access"). 155 Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite). 156 Select("user_id"). 157 Find(&userIDs); err != nil { 158 return nil, err 159 } 160 161 additionalUserIDs := make([]int64, 0, 10) 162 if err = e.Table("team_user"). 163 Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). 164 Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). 165 Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). 166 Distinct("`team_user`.uid"). 167 Select("`team_user`.uid"). 168 Find(&additionalUserIDs); err != nil { 169 return nil, err 170 } 171 172 uidMap := map[int64]bool{} 173 i := 0 174 for _, uid := range userIDs { 175 if uidMap[uid] { 176 continue 177 } 178 uidMap[uid] = true 179 userIDs[i] = uid 180 i++ 181 } 182 userIDs = userIDs[:i] 183 userIDs = append(userIDs, additionalUserIDs...) 184 185 for _, uid := range additionalUserIDs { 186 if uidMap[uid] { 187 continue 188 } 189 userIDs[i] = uid 190 i++ 191 } 192 userIDs = userIDs[:i] 193 194 // Leave a seat for owner itself to append later, but if owner is an organization 195 // and just waste 1 unit is cheaper than re-allocate memory once. 196 users := make([]*user_model.User, 0, len(userIDs)+1) 197 if len(userIDs) > 0 { 198 if err = e.In("id", userIDs).Find(&users); err != nil { 199 return nil, err 200 } 201 } 202 if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] { 203 users = append(users, repo.Owner) 204 } 205 206 return users, nil 207} 208 209// GetRepoAssignees returns all users that have write access and can be assigned to issues 210// of the repository, 211func GetRepoAssignees(repo *repo_model.Repository) (_ []*user_model.User, err error) { 212 return getRepoAssignees(db.DefaultContext, repo) 213} 214 215func getReviewers(ctx context.Context, repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) { 216 // Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries 217 if err := repo.GetOwner(ctx); err != nil { 218 return nil, err 219 } 220 221 var users []*user_model.User 222 e := db.GetEngine(ctx) 223 224 if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate { 225 // This a private repository: 226 // Anyone who can read the repository is a requestable reviewer 227 if err := e. 228 SQL("SELECT * FROM `user` WHERE id in (SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?)) ORDER BY name", 229 repo.ID, perm.AccessModeRead, 230 doerID, posterID). 231 Find(&users); err != nil { 232 return nil, err 233 } 234 235 return users, nil 236 } 237 238 // This is a "public" repository: 239 // Any user that has read access, is a watcher or organization member can be requested to review 240 if err := e. 241 SQL("SELECT * FROM `user` WHERE id IN ( "+ 242 "SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? "+ 243 "UNION "+ 244 "SELECT user_id FROM `watch` WHERE repo_id = ? AND mode IN (?, ?) "+ 245 "UNION "+ 246 "SELECT uid AS user_id FROM `org_user` WHERE org_id = ? "+ 247 ") AND id NOT IN (?, ?) ORDER BY name", 248 repo.ID, perm.AccessModeRead, 249 repo.ID, repo_model.WatchModeNormal, repo_model.WatchModeAuto, 250 repo.OwnerID, 251 doerID, posterID). 252 Find(&users); err != nil { 253 return nil, err 254 } 255 256 return users, nil 257} 258 259// GetReviewers get all users can be requested to review: 260// * for private repositories this returns all users that have read access or higher to the repository. 261// * for public repositories this returns all users that have read access or higher to the repository, 262// all repo watchers and all organization members. 263// TODO: may be we should have a busy choice for users to block review request to them. 264func GetReviewers(repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) { 265 return getReviewers(db.DefaultContext, repo, doerID, posterID) 266} 267 268// GetReviewerTeams get all teams can be requested to review 269func GetReviewerTeams(repo *repo_model.Repository) ([]*Team, error) { 270 if err := repo.GetOwner(db.DefaultContext); err != nil { 271 return nil, err 272 } 273 if !repo.Owner.IsOrganization() { 274 return nil, nil 275 } 276 277 teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) 278 if err != nil { 279 return nil, err 280 } 281 282 return teams, err 283} 284 285func updateRepoSize(e db.Engine, repo *repo_model.Repository) error { 286 size, err := util.GetDirectorySize(repo.RepoPath()) 287 if err != nil { 288 return fmt.Errorf("updateSize: %v", err) 289 } 290 291 lfsSize, err := e.Where("repository_id = ?", repo.ID).SumInt(new(LFSMetaObject), "size") 292 if err != nil { 293 return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) 294 } 295 296 repo.Size = size + lfsSize 297 _, err = e.ID(repo.ID).Cols("size").NoAutoTime().Update(repo) 298 return err 299} 300 301// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize 302func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { 303 return updateRepoSize(db.GetEngine(ctx), repo) 304} 305 306// CanUserForkRepo returns true if specified user can fork repository. 307func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, error) { 308 if user == nil { 309 return false, nil 310 } 311 if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { 312 return true, nil 313 } 314 ownedOrgs, err := GetOrgsCanCreateRepoByUserID(user.ID) 315 if err != nil { 316 return false, err 317 } 318 for _, org := range ownedOrgs { 319 if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) { 320 return true, nil 321 } 322 } 323 return false, nil 324} 325 326// FindUserOrgForks returns the forked repositories for one user from a repository 327func FindUserOrgForks(repoID, userID int64) ([]*repo_model.Repository, error) { 328 var cond builder.Cond = builder.And( 329 builder.Eq{"fork_id": repoID}, 330 builder.In("owner_id", 331 builder.Select("org_id"). 332 From("org_user"). 333 Where(builder.Eq{"uid": userID}), 334 ), 335 ) 336 337 var repos []*repo_model.Repository 338 return repos, db.GetEngine(db.DefaultContext).Table("repository").Where(cond).Find(&repos) 339} 340 341// GetForksByUserAndOrgs return forked repos of the user and owned orgs 342func GetForksByUserAndOrgs(user *user_model.User, repo *repo_model.Repository) ([]*repo_model.Repository, error) { 343 var repoList []*repo_model.Repository 344 if user == nil { 345 return repoList, nil 346 } 347 forkedRepo, err := repo_model.GetUserFork(repo.ID, user.ID) 348 if err != nil { 349 return repoList, err 350 } 351 if forkedRepo != nil { 352 repoList = append(repoList, forkedRepo) 353 } 354 orgForks, err := FindUserOrgForks(repo.ID, user.ID) 355 if err != nil { 356 return nil, err 357 } 358 repoList = append(repoList, orgForks...) 359 return repoList, nil 360} 361 362// CanUserDelete returns true if user could delete the repository 363func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) { 364 if user.IsAdmin || user.ID == repo.OwnerID { 365 return true, nil 366 } 367 368 if err := repo.GetOwner(db.DefaultContext); err != nil { 369 return false, err 370 } 371 372 if repo.Owner.IsOrganization() { 373 isOwner, err := OrgFromUser(repo.Owner).IsOwnedBy(user.ID) 374 if err != nil { 375 return false, err 376 } else if isOwner { 377 return true, nil 378 } 379 } 380 381 return false, nil 382} 383 384// getUsersWithAccessMode returns users that have at least given access mode to the repository. 385func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm.AccessMode) (_ []*user_model.User, err error) { 386 if err = repo.GetOwner(ctx); err != nil { 387 return nil, err 388 } 389 390 e := db.GetEngine(ctx) 391 accesses := make([]*Access, 0, 10) 392 if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { 393 return nil, err 394 } 395 396 // Leave a seat for owner itself to append later, but if owner is an organization 397 // and just waste 1 unit is cheaper than re-allocate memory once. 398 users := make([]*user_model.User, 0, len(accesses)+1) 399 if len(accesses) > 0 { 400 userIDs := make([]int64, len(accesses)) 401 for i := 0; i < len(accesses); i++ { 402 userIDs[i] = accesses[i].UserID 403 } 404 405 if err = e.In("id", userIDs).Find(&users); err != nil { 406 return nil, err 407 } 408 } 409 if !repo.Owner.IsOrganization() { 410 users = append(users, repo.Owner) 411 } 412 413 return users, nil 414} 415 416// SetRepoReadBy sets repo to be visited by given user. 417func SetRepoReadBy(repoID, userID int64) error { 418 return setRepoNotificationStatusReadIfUnread(db.GetEngine(db.DefaultContext), userID, repoID) 419} 420 421// CreateRepoOptions contains the create repository options 422type CreateRepoOptions struct { 423 Name string 424 Description string 425 OriginalURL string 426 GitServiceType api.GitServiceType 427 Gitignores string 428 IssueLabels string 429 License string 430 Readme string 431 DefaultBranch string 432 IsPrivate bool 433 IsMirror bool 434 IsTemplate bool 435 AutoInit bool 436 Status repo_model.RepositoryStatus 437 TrustModel repo_model.TrustModelType 438 MirrorInterval string 439} 440 441// GetRepoInitFile returns repository init files 442func GetRepoInitFile(tp, name string) ([]byte, error) { 443 cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") 444 relPath := path.Join("options", tp, cleanedName) 445 446 // Use custom file when available. 447 customPath := path.Join(setting.CustomPath, relPath) 448 isFile, err := util.IsFile(customPath) 449 if err != nil { 450 log.Error("Unable to check if %s is a file. Error: %v", customPath, err) 451 } 452 if isFile { 453 return os.ReadFile(customPath) 454 } 455 456 switch tp { 457 case "readme": 458 return options.Readme(cleanedName) 459 case "gitignore": 460 return options.Gitignore(cleanedName) 461 case "license": 462 return options.License(cleanedName) 463 case "label": 464 return options.Labels(cleanedName) 465 default: 466 return []byte{}, fmt.Errorf("Invalid init file type") 467 } 468} 469 470// CreateRepository creates a repository for the user/organization. 471func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt bool) (err error) { 472 if err = repo_model.IsUsableRepoName(repo.Name); err != nil { 473 return err 474 } 475 476 has, err := repo_model.IsRepositoryExistCtx(ctx, u, repo.Name) 477 if err != nil { 478 return fmt.Errorf("IsRepositoryExist: %v", err) 479 } else if has { 480 return repo_model.ErrRepoAlreadyExist{ 481 Uname: u.Name, 482 Name: repo.Name, 483 } 484 } 485 486 repoPath := repo_model.RepoPath(u.Name, repo.Name) 487 isExist, err := util.IsExist(repoPath) 488 if err != nil { 489 log.Error("Unable to check if %s exists. Error: %v", repoPath, err) 490 return err 491 } 492 if !overwriteOrAdopt && isExist { 493 log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath) 494 return repo_model.ErrRepoFilesAlreadyExist{ 495 Uname: u.Name, 496 Name: repo.Name, 497 } 498 } 499 500 if err = db.Insert(ctx, repo); err != nil { 501 return err 502 } 503 if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil { 504 return err 505 } 506 507 // insert units for repo 508 units := make([]repo_model.RepoUnit, 0, len(unit.DefaultRepoUnits)) 509 for _, tp := range unit.DefaultRepoUnits { 510 if tp == unit.TypeIssues { 511 units = append(units, repo_model.RepoUnit{ 512 RepoID: repo.ID, 513 Type: tp, 514 Config: &repo_model.IssuesConfig{ 515 EnableTimetracker: setting.Service.DefaultEnableTimetracking, 516 AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime, 517 EnableDependencies: setting.Service.DefaultEnableDependencies, 518 }, 519 }) 520 } else if tp == unit.TypePullRequests { 521 units = append(units, repo_model.RepoUnit{ 522 RepoID: repo.ID, 523 Type: tp, 524 Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyleMerge}, 525 }) 526 } else { 527 units = append(units, repo_model.RepoUnit{ 528 RepoID: repo.ID, 529 Type: tp, 530 }) 531 } 532 } 533 534 if err = db.Insert(ctx, units); err != nil { 535 return err 536 } 537 538 // Remember visibility preference. 539 u.LastRepoVisibility = repo.IsPrivate 540 if err = user_model.UpdateUserColsEngine(db.GetEngine(ctx), u, "last_repo_visibility"); err != nil { 541 return fmt.Errorf("updateUser: %v", err) 542 } 543 544 if _, err = db.GetEngine(ctx).Incr("num_repos").ID(u.ID).Update(new(user_model.User)); err != nil { 545 return fmt.Errorf("increment user total_repos: %v", err) 546 } 547 u.NumRepos++ 548 549 // Give access to all members in teams with access to all repositories. 550 if u.IsOrganization() { 551 teams, err := OrgFromUser(u).loadTeams(db.GetEngine(ctx)) 552 if err != nil { 553 return fmt.Errorf("loadTeams: %v", err) 554 } 555 for _, t := range teams { 556 if t.IncludesAllRepositories { 557 if err := t.addRepository(ctx, repo); err != nil { 558 return fmt.Errorf("addRepository: %v", err) 559 } 560 } 561 } 562 563 if isAdmin, err := isUserRepoAdmin(db.GetEngine(ctx), repo, doer); err != nil { 564 return fmt.Errorf("isUserRepoAdmin: %v", err) 565 } else if !isAdmin { 566 // Make creator repo admin if it wasn't assigned automatically 567 if err = addCollaborator(ctx, repo, doer); err != nil { 568 return fmt.Errorf("AddCollaborator: %v", err) 569 } 570 if err = changeCollaborationAccessMode(db.GetEngine(ctx), repo, doer.ID, perm.AccessModeAdmin); err != nil { 571 return fmt.Errorf("ChangeCollaborationAccessMode: %v", err) 572 } 573 } 574 } else if err = recalculateAccesses(ctx, repo); err != nil { 575 // Organization automatically called this in addRepository method. 576 return fmt.Errorf("recalculateAccesses: %v", err) 577 } 578 579 if setting.Service.AutoWatchNewRepos { 580 if err = repo_model.WatchRepoCtx(ctx, doer.ID, repo.ID, true); err != nil { 581 return fmt.Errorf("watchRepo: %v", err) 582 } 583 } 584 585 if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil { 586 return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err) 587 } 588 589 return nil 590} 591 592// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... 593func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { 594 if err := repo.GetOwner(ctx); err != nil { 595 return err 596 } 597 598 // Create/Remove git-daemon-export-ok for git-daemon... 599 daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`) 600 601 isExist, err := util.IsExist(daemonExportFile) 602 if err != nil { 603 log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) 604 return err 605 } 606 607 isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic 608 if !isPublic && isExist { 609 if err = util.Remove(daemonExportFile); err != nil { 610 log.Error("Failed to remove %s: %v", daemonExportFile, err) 611 } 612 } else if isPublic && !isExist { 613 if f, err := os.Create(daemonExportFile); err != nil { 614 log.Error("Failed to create %s: %v", daemonExportFile, err) 615 } else { 616 f.Close() 617 } 618 } 619 620 return nil 621} 622 623// IncrementRepoForkNum increment repository fork number 624func IncrementRepoForkNum(ctx context.Context, repoID int64) error { 625 _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) 626 return err 627} 628 629// DecrementRepoForkNum decrement repository fork number 630func DecrementRepoForkNum(ctx context.Context, repoID int64) error { 631 _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repoID) 632 return err 633} 634 635func updateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { 636 repo.LowerName = strings.ToLower(repo.Name) 637 638 if utf8.RuneCountInString(repo.Description) > 255 { 639 repo.Description = string([]rune(repo.Description)[:255]) 640 } 641 if utf8.RuneCountInString(repo.Website) > 255 { 642 repo.Website = string([]rune(repo.Website)[:255]) 643 } 644 645 e := db.GetEngine(ctx) 646 647 if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { 648 return fmt.Errorf("update: %v", err) 649 } 650 651 if err = updateRepoSize(e, repo); err != nil { 652 log.Error("Failed to update size for repository: %v", err) 653 } 654 655 if visibilityChanged { 656 if err = repo.GetOwner(ctx); err != nil { 657 return fmt.Errorf("getOwner: %v", err) 658 } 659 if repo.Owner.IsOrganization() { 660 // Organization repository need to recalculate access table when visibility is changed. 661 if err = recalculateTeamAccesses(ctx, repo, 0); err != nil { 662 return fmt.Errorf("recalculateTeamAccesses: %v", err) 663 } 664 } 665 666 // If repo has become private, we need to set its actions to private. 667 if repo.IsPrivate { 668 _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{ 669 IsPrivate: true, 670 }) 671 if err != nil { 672 return err 673 } 674 } 675 676 // Create/Remove git-daemon-export-ok for git-daemon... 677 if err := CheckDaemonExportOK(db.WithEngine(ctx, e), repo); err != nil { 678 return err 679 } 680 681 forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) 682 if err != nil { 683 return fmt.Errorf("getRepositoriesByForkID: %v", err) 684 } 685 for i := range forkRepos { 686 forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate 687 if err = updateRepository(ctx, forkRepos[i], true); err != nil { 688 return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) 689 } 690 } 691 } 692 693 return nil 694} 695 696// UpdateRepositoryCtx updates a repository with db context 697func UpdateRepositoryCtx(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) error { 698 return updateRepository(ctx, repo, visibilityChanged) 699} 700 701// UpdateRepository updates a repository 702func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err error) { 703 ctx, committer, err := db.TxContext() 704 if err != nil { 705 return err 706 } 707 defer committer.Close() 708 709 if err = updateRepository(ctx, repo, visibilityChanged); err != nil { 710 return fmt.Errorf("updateRepository: %v", err) 711 } 712 713 return committer.Commit() 714} 715 716// DeleteRepository deletes a repository for a user or organization. 717// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) 718func DeleteRepository(doer *user_model.User, uid, repoID int64) error { 719 ctx, committer, err := db.TxContext() 720 if err != nil { 721 return err 722 } 723 defer committer.Close() 724 sess := db.GetEngine(ctx) 725 726 // In case is a organization. 727 org, err := user_model.GetUserByIDEngine(sess, uid) 728 if err != nil { 729 return err 730 } 731 732 repo := &repo_model.Repository{OwnerID: uid} 733 has, err := sess.ID(repoID).Get(repo) 734 if err != nil { 735 return err 736 } else if !has { 737 return repo_model.ErrRepoNotExist{ 738 ID: repoID, 739 UID: uid, 740 OwnerName: "", 741 Name: "", 742 } 743 } 744 745 // Delete Deploy Keys 746 deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID}) 747 if err != nil { 748 return fmt.Errorf("listDeployKeys: %v", err) 749 } 750 var needRewriteKeysFile = len(deployKeys) > 0 751 for _, dKey := range deployKeys { 752 if err := DeleteDeployKey(ctx, doer, dKey.ID); err != nil { 753 return fmt.Errorf("deleteDeployKeys: %v", err) 754 } 755 } 756 757 if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil { 758 return err 759 } else if cnt != 1 { 760 return repo_model.ErrRepoNotExist{ 761 ID: repoID, 762 UID: uid, 763 OwnerName: "", 764 Name: "", 765 } 766 } 767 768 if org.IsOrganization() { 769 teams, err := OrgFromUser(org).loadTeams(sess) 770 if err != nil { 771 return err 772 } 773 for _, t := range teams { 774 if !t.hasRepository(sess, repoID) { 775 continue 776 } else if err = t.removeRepository(ctx, repo, false); err != nil { 777 return err 778 } 779 } 780 } 781 782 attachments := make([]*repo_model.Attachment, 0, 20) 783 if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id"). 784 Where("`release`.repo_id = ?", repoID). 785 Find(&attachments); err != nil { 786 return err 787 } 788 releaseAttachments := make([]string, 0, len(attachments)) 789 for i := 0; i < len(attachments); i++ { 790 releaseAttachments = append(releaseAttachments, attachments[i].RelativePath()) 791 } 792 793 if _, err := sess.Exec("UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil { 794 return err 795 } 796 797 if err := deleteBeans(sess, 798 &Access{RepoID: repo.ID}, 799 &Action{RepoID: repo.ID}, 800 &Collaboration{RepoID: repoID}, 801 &Comment{RefRepoID: repoID}, 802 &CommitStatus{RepoID: repoID}, 803 &DeletedBranch{RepoID: repoID}, 804 &webhook.HookTask{RepoID: repoID}, 805 &LFSLock{RepoID: repoID}, 806 &repo_model.LanguageStat{RepoID: repoID}, 807 &Milestone{RepoID: repoID}, 808 &repo_model.Mirror{RepoID: repoID}, 809 &Notification{RepoID: repoID}, 810 &ProtectedBranch{RepoID: repoID}, 811 &ProtectedTag{RepoID: repoID}, 812 &PullRequest{BaseRepoID: repoID}, 813 &repo_model.PushMirror{RepoID: repoID}, 814 &Release{RepoID: repoID}, 815 &repo_model.RepoIndexerStatus{RepoID: repoID}, 816 &repo_model.Redirect{RedirectRepoID: repoID}, 817 &repo_model.RepoUnit{RepoID: repoID}, 818 &repo_model.Star{RepoID: repoID}, 819 &Task{RepoID: repoID}, 820 &repo_model.Watch{RepoID: repoID}, 821 &webhook.Webhook{RepoID: repoID}, 822 ); err != nil { 823 return fmt.Errorf("deleteBeans: %v", err) 824 } 825 826 // Delete Labels and related objects 827 if err := deleteLabelsByRepoID(sess, repoID); err != nil { 828 return err 829 } 830 831 // Delete Issues and related objects 832 var attachmentPaths []string 833 if attachmentPaths, err = deleteIssuesByRepoID(sess, repoID); err != nil { 834 return err 835 } 836 837 // Delete issue index 838 if err := db.DeleteResouceIndex(sess, "issue_index", repoID); err != nil { 839 return err 840 } 841 842 if repo.IsFork { 843 if _, err := sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { 844 return fmt.Errorf("decrease fork count: %v", err) 845 } 846 } 847 848 if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { 849 return err 850 } 851 852 if len(repo.Topics) > 0 { 853 if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil { 854 return err 855 } 856 } 857 858 projects, _, err := getProjects(sess, ProjectSearchOptions{ 859 RepoID: repoID, 860 }) 861 if err != nil { 862 return fmt.Errorf("get projects: %v", err) 863 } 864 for i := range projects { 865 if err := deleteProjectByID(sess, projects[i].ID); err != nil { 866 return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err) 867 } 868 } 869 870 // Remove LFS objects 871 var lfsObjects []*LFSMetaObject 872 if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { 873 return err 874 } 875 876 var lfsPaths = make([]string, 0, len(lfsObjects)) 877 for _, v := range lfsObjects { 878 count, err := sess.Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}}) 879 if err != nil { 880 return err 881 } 882 if count > 1 { 883 continue 884 } 885 886 lfsPaths = append(lfsPaths, v.RelativePath()) 887 } 888 889 if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { 890 return err 891 } 892 893 // Remove archives 894 var archives []*repo_model.RepoArchiver 895 if err = sess.Where("repo_id=?", repoID).Find(&archives); err != nil { 896 return err 897 } 898 899 var archivePaths = make([]string, 0, len(archives)) 900 for _, v := range archives { 901 p, _ := v.RelativePath() 902 archivePaths = append(archivePaths, p) 903 } 904 905 if _, err := sess.Delete(&repo_model.RepoArchiver{RepoID: repoID}); err != nil { 906 return err 907 } 908 909 if repo.NumForks > 0 { 910 if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { 911 log.Error("reset 'fork_id' and 'is_fork': %v", err) 912 } 913 } 914 915 // Get all attachments with both issue_id and release_id are zero 916 var newAttachments []*repo_model.Attachment 917 if err := sess.Where(builder.Eq{ 918 "repo_id": repo.ID, 919 "issue_id": 0, 920 "release_id": 0, 921 }).Find(&newAttachments); err != nil { 922 return err 923 } 924 925 var newAttachmentPaths = make([]string, 0, len(newAttachments)) 926 for _, attach := range newAttachments { 927 newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath()) 928 } 929 930 if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil { 931 return err 932 } 933 934 if err = committer.Commit(); err != nil { 935 return err 936 } 937 938 committer.Close() 939 940 if needRewriteKeysFile { 941 if err := asymkey_model.RewriteAllPublicKeys(); err != nil { 942 log.Error("RewriteAllPublicKeys failed: %v", err) 943 } 944 } 945 946 // We should always delete the files after the database transaction succeed. If 947 // we delete the file but the database rollback, the repository will be broken. 948 949 // Remove repository files. 950 repoPath := repo.RepoPath() 951 admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath) 952 953 // Remove wiki files 954 if repo.HasWiki() { 955 admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath()) 956 } 957 958 // Remove archives 959 for i := range archivePaths { 960 admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archivePaths[i]) 961 } 962 963 // Remove lfs objects 964 for i := range lfsPaths { 965 admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsPaths[i]) 966 } 967 968 // Remove issue attachment files. 969 for i := range attachmentPaths { 970 admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i]) 971 } 972 973 // Remove release attachment files. 974 for i := range releaseAttachments { 975 admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachments[i]) 976 } 977 978 // Remove attachment with no issue_id and release_id. 979 for i := range newAttachmentPaths { 980 admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachmentPaths[i]) 981 } 982 983 if len(repo.Avatar) > 0 { 984 if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil { 985 return fmt.Errorf("Failed to remove %s: %v", repo.Avatar, err) 986 } 987 } 988 989 return nil 990} 991 992type repoChecker struct { 993 querySQL func(ctx context.Context) ([]map[string][]byte, error) 994 correctSQL func(ctx context.Context, id int64) error 995 desc string 996} 997 998func repoStatsCheck(ctx context.Context, checker *repoChecker) { 999 results, err := checker.querySQL(ctx) 1000 if err != nil { 1001 log.Error("Select %s: %v", checker.desc, err) 1002 return 1003 } 1004 for _, result := range results { 1005 id, _ := strconv.ParseInt(string(result["id"]), 10, 64) 1006 select { 1007 case <-ctx.Done(): 1008 log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id) 1009 return 1010 default: 1011 } 1012 log.Trace("Updating %s: %d", checker.desc, id) 1013 err = checker.correctSQL(ctx, id) 1014 if err != nil { 1015 log.Error("Update %s[%d]: %v", checker.desc, id, err) 1016 } 1017 } 1018} 1019 1020func StatsCorrectSQL(ctx context.Context, sql string, id int64) error { 1021 _, err := db.GetEngine(ctx).Exec(sql, id, id) 1022 return err 1023} 1024 1025func repoStatsCorrectNumWatches(ctx context.Context, id int64) error { 1026 return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id) 1027} 1028 1029func repoStatsCorrectNumStars(ctx context.Context, id int64) error { 1030 return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id) 1031} 1032 1033func labelStatsCorrectNumIssues(ctx context.Context, id int64) error { 1034 return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id) 1035} 1036 1037func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error { 1038 _, err := db.GetEngine(ctx).Exec("UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=id) WHERE repo_id=?", id) 1039 return err 1040} 1041 1042func labelStatsCorrectNumClosedIssues(ctx context.Context, id int64) error { 1043 _, 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) 1044 return err 1045} 1046 1047func labelStatsCorrectNumClosedIssuesRepo(ctx context.Context, id int64) error { 1048 _, 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) 1049 return err 1050} 1051 1052var 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)" 1053 1054func milestoneStatsCorrectNumIssues(ctx context.Context, id int64) error { 1055 return updateMilestoneCounters(ctx, id) 1056} 1057 1058func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error { 1059 e := db.GetEngine(ctx) 1060 results, err := e.Query(milestoneStatsQueryNumIssues+" AND `milestone`.repo_id = ?", true, id) 1061 if err != nil { 1062 return err 1063 } 1064 for _, result := range results { 1065 id, _ := strconv.ParseInt(string(result["id"]), 10, 64) 1066 err = milestoneStatsCorrectNumIssues(ctx, id) 1067 if err != nil { 1068 return err 1069 } 1070 } 1071 return nil 1072} 1073 1074func userStatsCorrectNumRepos(ctx context.Context, id int64) error { 1075 return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id) 1076} 1077 1078func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error { 1079 return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id) 1080} 1081 1082func repoStatsCorrectNumIssues(ctx context.Context, id int64) error { 1083 return repoStatsCorrectNum(ctx, id, false, "num_issues") 1084} 1085 1086func repoStatsCorrectNumPulls(ctx context.Context, id int64) error { 1087 return repoStatsCorrectNum(ctx, id, true, "num_pulls") 1088} 1089 1090func repoStatsCorrectNum(ctx context.Context, id int64, isPull bool, field string) error { 1091 _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=?) WHERE id=?", id, isPull, id) 1092 return err 1093} 1094 1095func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error { 1096 return repoStatsCorrectNumClosed(ctx, id, false, "num_closed_issues") 1097} 1098 1099func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error { 1100 return repoStatsCorrectNumClosed(ctx, id, true, "num_closed_pulls") 1101} 1102 1103func repoStatsCorrectNumClosed(ctx context.Context, id int64, isPull bool, field string) error { 1104 _, 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) 1105 return err 1106} 1107 1108func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) { 1109 return func(ctx context.Context) ([]map[string][]byte, error) { 1110 return db.GetEngine(ctx).Query(args...) 1111 } 1112} 1113 1114// CheckRepoStats checks the repository stats 1115func CheckRepoStats(ctx context.Context) error { 1116 log.Trace("Doing: CheckRepoStats") 1117 1118 checkers := []*repoChecker{ 1119 // Repository.NumWatches 1120 { 1121 statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)"), 1122 repoStatsCorrectNumWatches, 1123 "repository count 'num_watches'", 1124 }, 1125 // Repository.NumStars 1126 { 1127 statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)"), 1128 repoStatsCorrectNumStars, 1129 "repository count 'num_stars'", 1130 }, 1131 // Repository.NumClosedIssues 1132 { 1133 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), 1134 repoStatsCorrectNumClosedIssues, 1135 "repository count 'num_closed_issues'", 1136 }, 1137 // Repository.NumClosedPulls 1138 { 1139 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), 1140 repoStatsCorrectNumClosedPulls, 1141 "repository count 'num_closed_pulls'", 1142 }, 1143 // Label.NumIssues 1144 { 1145 statsQuery("SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)"), 1146 labelStatsCorrectNumIssues, 1147 "label count 'num_issues'", 1148 }, 1149 // Label.NumClosedIssues 1150 { 1151 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), 1152 labelStatsCorrectNumClosedIssues, 1153 "label count 'num_closed_issues'", 1154 }, 1155 // Milestone.Num{,Closed}Issues 1156 { 1157 statsQuery(milestoneStatsQueryNumIssues, true), 1158 milestoneStatsCorrectNumIssues, 1159 "milestone count 'num_closed_issues' and 'num_issues'", 1160 }, 1161 // User.NumRepos 1162 { 1163 statsQuery("SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)"), 1164 userStatsCorrectNumRepos, 1165 "user count 'num_repos'", 1166 }, 1167 // Issue.NumComments 1168 { 1169 statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"), 1170 repoStatsCorrectIssueNumComments, 1171 "issue count 'num_comments'", 1172 }, 1173 } 1174 for _, checker := range checkers { 1175 select { 1176 case <-ctx.Done(): 1177 log.Warn("CheckRepoStats: Cancelled before %s", checker.desc) 1178 return db.ErrCancelledf("before checking %s", checker.desc) 1179 default: 1180 repoStatsCheck(ctx, checker) 1181 } 1182 } 1183 1184 // FIXME: use checker when stop supporting old fork repo format. 1185 // ***** START: Repository.NumForks ***** 1186 e := db.GetEngine(ctx) 1187 results, err := e.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") 1188 if err != nil { 1189 log.Error("Select repository count 'num_forks': %v", err) 1190 } else { 1191 for _, result := range results { 1192 id, _ := strconv.ParseInt(string(result["id"]), 10, 64) 1193 select { 1194 case <-ctx.Done(): 1195 log.Warn("CheckRepoStats: Cancelled") 1196 return db.ErrCancelledf("during repository count 'num_fork' for repo ID %d", id) 1197 default: 1198 } 1199 log.Trace("Updating repository count 'num_forks': %d", id) 1200 1201 repo, err := repo_model.GetRepositoryByID(id) 1202 if err != nil { 1203 log.Error("repo_model.GetRepositoryByID[%d]: %v", id, err) 1204 continue 1205 } 1206 1207 rawResult, err := db.GetEngine(db.DefaultContext).Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) 1208 if err != nil { 1209 log.Error("Select count of forks[%d]: %v", repo.ID, err) 1210 continue 1211 } 1212 repo.NumForks = int(parseCountResult(rawResult)) 1213 1214 if err = UpdateRepository(repo, false); err != nil { 1215 log.Error("UpdateRepository[%d]: %v", id, err) 1216 continue 1217 } 1218 } 1219 } 1220 // ***** END: Repository.NumForks ***** 1221 return nil 1222} 1223 1224func UpdateRepoStats(ctx context.Context, id int64) error { 1225 var err error 1226 1227 for _, f := range []func(ctx context.Context, id int64) error{ 1228 repoStatsCorrectNumWatches, 1229 repoStatsCorrectNumStars, 1230 repoStatsCorrectNumIssues, 1231 repoStatsCorrectNumPulls, 1232 repoStatsCorrectNumClosedIssues, 1233 repoStatsCorrectNumClosedPulls, 1234 labelStatsCorrectNumIssuesRepo, 1235 labelStatsCorrectNumClosedIssuesRepo, 1236 milestoneStatsCorrectNumIssuesRepo, 1237 } { 1238 err = f(ctx, id) 1239 if err != nil { 1240 return err 1241 } 1242 } 1243 return nil 1244} 1245 1246func updateUserStarNumbers(users []user_model.User) error { 1247 ctx, committer, err := db.TxContext() 1248 if err != nil { 1249 return err 1250 } 1251 defer committer.Close() 1252 1253 for _, user := range users { 1254 if _, err = db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil { 1255 return err 1256 } 1257 } 1258 1259 return committer.Commit() 1260} 1261 1262// DoctorUserStarNum recalculate Stars number for all user 1263func DoctorUserStarNum() (err error) { 1264 const batchSize = 100 1265 1266 for start := 0; ; start += batchSize { 1267 users := make([]user_model.User, 0, batchSize) 1268 if err = db.GetEngine(db.DefaultContext).Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil { 1269 return 1270 } 1271 if len(users) == 0 { 1272 break 1273 } 1274 1275 if err = updateUserStarNumbers(users); err != nil { 1276 return 1277 } 1278 } 1279 1280 log.Debug("recalculate Stars number for all user finished") 1281 1282 return 1283} 1284 1285// LinkedRepository returns the linked repo if any 1286func LinkedRepository(a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { 1287 if a.IssueID != 0 { 1288 iss, err := GetIssueByID(a.IssueID) 1289 if err != nil { 1290 return nil, unit.TypeIssues, err 1291 } 1292 repo, err := repo_model.GetRepositoryByID(iss.RepoID) 1293 unitType := unit.TypeIssues 1294 if iss.IsPull { 1295 unitType = unit.TypePullRequests 1296 } 1297 return repo, unitType, err 1298 } else if a.ReleaseID != 0 { 1299 rel, err := GetReleaseByID(a.ReleaseID) 1300 if err != nil { 1301 return nil, unit.TypeReleases, err 1302 } 1303 repo, err := repo_model.GetRepositoryByID(rel.RepoID) 1304 return repo, unit.TypeReleases, err 1305 } 1306 return nil, -1, nil 1307} 1308 1309// DeleteDeployKey delete deploy keys 1310func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error { 1311 key, err := asymkey_model.GetDeployKeyByID(ctx, id) 1312 if err != nil { 1313 if asymkey_model.IsErrDeployKeyNotExist(err) { 1314 return nil 1315 } 1316 return fmt.Errorf("GetDeployKeyByID: %v", err) 1317 } 1318 1319 sess := db.GetEngine(ctx) 1320 1321 // Check if user has access to delete this key. 1322 if !doer.IsAdmin { 1323 repo, err := repo_model.GetRepositoryByIDCtx(ctx, key.RepoID) 1324 if err != nil { 1325 return fmt.Errorf("GetRepositoryByID: %v", err) 1326 } 1327 has, err := isUserRepoAdmin(sess, repo, doer) 1328 if err != nil { 1329 return fmt.Errorf("GetUserRepoPermission: %v", err) 1330 } else if !has { 1331 return asymkey_model.ErrKeyAccessDenied{ 1332 UserID: doer.ID, 1333 KeyID: key.ID, 1334 Note: "deploy", 1335 } 1336 } 1337 } 1338 1339 if _, err = sess.ID(key.ID).Delete(new(asymkey_model.DeployKey)); err != nil { 1340 return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err) 1341 } 1342 1343 // Check if this is the last reference to same key content. 1344 has, err := sess. 1345 Where("key_id = ?", key.KeyID). 1346 Get(new(asymkey_model.DeployKey)) 1347 if err != nil { 1348 return err 1349 } else if !has { 1350 if err = asymkey_model.DeletePublicKeys(ctx, key.KeyID); err != nil { 1351 return err 1352 } 1353 } 1354 1355 return nil 1356} 1357