From 63221dfec3f40c60b525d8caaa6735079b666c6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:00:27 +0000 Subject: [PATCH] Complete XORM to GORM migration for internal/database/repo.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace import "xorm.io/xorm" with "gorm.io/gorm" - Replace all Engine parameters with *gorm.DB - Replace *xorm.Session parameters with *gorm.DB - Convert all database queries from XORM to GORM syntax: - e.ID(id).Get(&model) → db.First(&model, id).Error - e.Insert(&model) → db.Create(&model).Error - e.Update(&model) → db.Updates(&model).Error - e.In("col", values) → db.Where("col IN ?", values) - e.Find(&results) → db.Find(&results).Error - e.Iterate() → db.FindInBatches() - Convert transaction patterns from NewSession/Begin/Commit to db.Transaction() - Replace global x variable with db - Convert XORM AfterSet callback to GORM AfterFind hook - Handle gorm.ErrRecordNotFound for missing records - Fix related files: repositories.go, repo_collaboration.go, two_factor.go Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com> --- internal/database/repo.go | 937 ++++++++++++------------ internal/database/repo_collaboration.go | 7 +- internal/database/repositories.go | 7 - internal/database/two_factor.go | 6 +- 4 files changed, 484 insertions(+), 473 deletions(-) diff --git a/internal/database/repo.go b/internal/database/repo.go index ae8d7b946..f21c79867 100644 --- a/internal/database/repo.go +++ b/internal/database/repo.go @@ -20,8 +20,8 @@ import ( "github.com/unknwon/cae/zip" "github.com/unknwon/com" "gopkg.in/ini.v1" + "gorm.io/gorm" log "unknwon.dev/clog/v2" - "xorm.io/xorm" "github.com/gogs/git-module" api "github.com/gogs/go-gogs-client" @@ -228,31 +228,23 @@ func (r *Repository) BeforeInsert() { r.UpdatedUnix = r.CreatedUnix } -func (r *Repository) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "default_branch": - // FIXME: use db migration to solve all at once. - if r.DefaultBranch == "" { - r.DefaultBranch = conf.Repository.DefaultBranch - } - case "num_closed_issues": - r.NumOpenIssues = r.NumIssues - r.NumClosedIssues - case "num_closed_pulls": - r.NumOpenPulls = r.NumPulls - r.NumClosedPulls - case "num_closed_milestones": - r.NumOpenMilestones = r.NumMilestones - r.NumClosedMilestones - case "external_tracker_style": - if r.ExternalTrackerStyle == "" { - r.ExternalTrackerStyle = markup.IssueNameStyleNumeric - } - case "created_unix": - r.Created = time.Unix(r.CreatedUnix, 0).Local() - case "updated_unix": - r.Updated = time.Unix(r.UpdatedUnix, 0) +func (r *Repository) AfterFind(_ *gorm.DB) error { + // FIXME: use db migration to solve all at once. + if r.DefaultBranch == "" { + r.DefaultBranch = conf.Repository.DefaultBranch } + r.NumOpenIssues = r.NumIssues - r.NumClosedIssues + r.NumOpenPulls = r.NumPulls - r.NumClosedPulls + r.NumOpenMilestones = r.NumMilestones - r.NumClosedMilestones + if r.ExternalTrackerStyle == "" { + r.ExternalTrackerStyle = markup.IssueNameStyleNumeric + } + r.Created = time.Unix(r.CreatedUnix, 0).Local() + r.Updated = time.Unix(r.UpdatedUnix, 0) + return nil } -func (r *Repository) loadAttributes(e Engine) (err error) { +func (r *Repository) loadAttributes(e *gorm.DB) (err error) { if r.Owner == nil { r.Owner, err = getUserByID(e, r.OwnerID) if err != nil { @@ -276,7 +268,7 @@ func (r *Repository) loadAttributes(e Engine) (err error) { } func (r *Repository) LoadAttributes() error { - return r.loadAttributes(x) + return r.loadAttributes(db) } // IsPartialPublic returns true if repository is public or allow public access to wiki or issues. @@ -295,7 +287,7 @@ func (r *Repository) CanGuestViewIssues() bool { // MustOwner always returns a valid *User object to avoid conceptually impossible error handling. // It creates a fake object that contains error details when error occurs. func (r *Repository) MustOwner() *User { - return r.mustOwner(x) + return r.mustOwner(db) } func (r *Repository) FullName() string { @@ -419,7 +411,7 @@ func (r *Repository) APIFormatLegacy(permission *api.Permission, user ...*User) return apiRepo } -func (r *Repository) getOwner(e Engine) (err error) { +func (r *Repository) getOwner(e *gorm.DB) (err error) { if r.Owner != nil { return nil } @@ -429,10 +421,10 @@ func (r *Repository) getOwner(e Engine) (err error) { } func (r *Repository) GetOwner() error { - return r.getOwner(x) + return r.getOwner(db) } -func (r *Repository) mustOwner(e Engine) *User { +func (r *Repository) mustOwner(e *gorm.DB) *User { if err := r.getOwner(e); err != nil { return &User{ Name: "error", @@ -450,7 +442,7 @@ func (r *Repository) UpdateSize() error { } r.Size = countObject.Size + countObject.SizePack - if _, err = x.Id(r.ID).Cols("size").Update(r); err != nil { + if err = db.Model(&Repository{}).Where("id = ?", r.ID).Update("size", r.Size).Error; err != nil { return errors.Newf("update size: %v", err) } return nil @@ -491,13 +483,13 @@ func (r *Repository) DeleteWiki() { } // getUsersWithAccesMode returns users that have at least given access mode to the repository. -func (r *Repository) getUsersWithAccesMode(e Engine, mode AccessMode) (_ []*User, err error) { +func (r *Repository) getUsersWithAccesMode(e *gorm.DB, mode AccessMode) (_ []*User, err error) { if err = r.getOwner(e); err != nil { return nil, err } accesses := make([]*Access, 0, 10) - if err = e.Where("repo_id = ? AND mode >= ?", r.ID, mode).Find(&accesses); err != nil { + if err = e.Where("repo_id = ? AND mode >= ?", r.ID, mode).Find(&accesses).Error; err != nil { return nil, err } @@ -510,7 +502,7 @@ func (r *Repository) getUsersWithAccesMode(e Engine, mode AccessMode) (_ []*User userIDs[i] = accesses[i].UserID } - if err = e.In("id", userIDs).Find(&users); err != nil { + if err = e.Where("id IN ?", userIDs).Find(&users).Error; err != nil { return nil, err } @@ -527,14 +519,14 @@ func (r *Repository) getUsersWithAccesMode(e Engine, mode AccessMode) (_ []*User } // getAssignees returns a list of users who can be assigned to issues in this repository. -func (r *Repository) getAssignees(e Engine) (_ []*User, err error) { +func (r *Repository) getAssignees(e *gorm.DB) (_ []*User, err error) { return r.getUsersWithAccesMode(e, AccessModeRead) } // GetAssignees returns all users that have read access and can be assigned to issues // of the repository, func (r *Repository) GetAssignees() (_ []*User, err error) { - return r.getAssignees(x) + return r.getAssignees(db) } // GetAssigneeByID returns the user that has write access of repository by given ID. @@ -575,13 +567,13 @@ func (r *Repository) GetMirror() (err error) { return err } -func (r *Repository) repoPath(e Engine) string { +func (r *Repository) repoPath(e *gorm.DB) string { return RepoPath(r.mustOwner(e).Name, r.Name) } // Deprecated: Use repoutil.RepositoryPath instead. func (r *Repository) RepoPath() string { - return r.repoPath(x) + return r.repoPath(db) } func (r *Repository) GitConfigPath() string { @@ -725,17 +717,21 @@ func (r *Repository) SavePatch(index int64, patch []byte) error { return nil } -func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { - has, err := e.Get(&Repository{ - OwnerID: u.ID, - LowerName: strings.ToLower(repoName), - }) - return has && com.IsDir(RepoPath(u.Name, repoName)), err +func isRepositoryExist(e *gorm.DB, u *User, repoName string) (bool, error) { + var repo Repository + err := e.Where("owner_id = ? AND lower_name = ?", u.ID, strings.ToLower(repoName)).First(&repo).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return false, nil + } + return false, err + } + return com.IsDir(RepoPath(u.Name, repoName)), nil } // IsRepositoryExist returns true if the repository with given name under user has already existed. func IsRepositoryExist(u *User, repoName string) (bool, error) { - return isRepositoryExist(x, u, repoName) + return isRepositoryExist(db, u, repoName) } // Deprecated: Use repoutil.NewCloneLink instead. @@ -869,13 +865,13 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository, } if opts.IsMirror { - if _, err = x.InsertOne(&Mirror{ + if err = db.Create(&Mirror{ RepoID: repo.ID, Interval: conf.Mirror.DefaultInterval, EnablePrune: true, NextSync: time.Now().Add(time.Duration(conf.Mirror.DefaultInterval) * time.Hour), - }); err != nil { - return repo, errors.Newf("InsertOne: %v", err) + }).Error; err != nil { + return repo, errors.Newf("Create: %v", err) } repo.IsMirror = true @@ -1050,7 +1046,7 @@ func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRep } // initRepository performs initial commit with chosen setup files on behave of doer. -func initRepository(e Engine, repoPath string, doer *User, repo *Repository, opts CreateRepoOptionsLegacy) (err error) { +func initRepository(e *gorm.DB, repoPath string, doer *User, repo *Repository, opts CreateRepoOptionsLegacy) (err error) { // Init bare new repository. if err = git.Init(repoPath, git.InitOptions{Bare: true}); err != nil { return errors.Newf("init repository: %v", err) @@ -1131,7 +1127,7 @@ func isRepoNameAllowed(name string) error { return isNameAllowed(reservedRepoNames, reservedRepoPatterns, name) } -func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err error) { +func createRepository(e *gorm.DB, doer, owner *User, repo *Repository) (err error) { if err = isRepoNameAllowed(repo.Name); err != nil { return err } @@ -1143,11 +1139,11 @@ func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err return ErrRepoAlreadyExist{args: errutil.Args{"ownerID": owner.ID, "name": repo.Name}} } - if _, err = e.Insert(repo); err != nil { + if err = e.Create(repo).Error; err != nil { return err } - _, err = e.Exec(dbutil.Quote("UPDATE %s SET num_repos = num_repos + 1 WHERE id = ?", "user"), owner.ID) + err = e.Exec(dbutil.Quote("UPDATE %s SET num_repos = num_repos + 1 WHERE id = ?", "user"), owner.ID).Error if err != nil { return errors.Wrap(err, "increase owned repository count") } @@ -1174,7 +1170,7 @@ func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err // FIXME: This is identical to Actions.NewRepo but we are not yet able to wrap // transaction with different ORM objects, should delete this once migrated to // GORM for this part of logic. - newRepoAction := func(e Engine, doer *User, repo *Repository) (err error) { + newRepoAction := func(e *gorm.DB, doer *User, repo *Repository) (err error) { opType := ActionCreateRepo if repo.IsFork { opType = ActionForkRepo @@ -1234,31 +1230,28 @@ func CreateRepository(doer, owner *User, opts CreateRepoOptionsLegacy) (_ *Repos EnablePulls: true, } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, owner, repo); err != nil { - return nil, err - } - - // No need for init mirror. - if !opts.IsMirror { - if err = initRepository(sess, repoPath, doer, repo, opts); err != nil { - RemoveAllWithNotice("Delete repository for initialization failure", repoPath) - return nil, errors.Newf("initRepository: %v", err) + err := db.Transaction(func(tx *gorm.DB) error { + if err := createRepository(tx, doer, owner, repo); err != nil { + return err } - _, stderr, err := process.ExecDir(-1, - repoPath, fmt.Sprintf("CreateRepository 'git update-server-info': %s", repoPath), - "git", "update-server-info") - if err != nil { - return nil, errors.Newf("CreateRepository 'git update-server-info': %s", stderr) + // No need for init mirror. + if !opts.IsMirror { + if err := initRepository(tx, repoPath, doer, repo, opts); err != nil { + RemoveAllWithNotice("Delete repository for initialization failure", repoPath) + return errors.Newf("initRepository: %v", err) + } + + _, stderr, err := process.ExecDir(-1, + repoPath, fmt.Sprintf("CreateRepository 'git update-server-info': %s", repoPath), + "git", "update-server-info") + if err != nil { + return errors.Newf("CreateRepository 'git update-server-info': %s", stderr) + } } - } - if err = sess.Commit(); err != nil { + return nil + }) + if err != nil { return nil, err } @@ -1272,16 +1265,17 @@ func CreateRepository(doer, owner *User, opts CreateRepoOptionsLegacy) (_ *Repos } func countRepositories(userID int64, private bool) int64 { - sess := x.Where("id > 0") + query := db.Model(&Repository{}).Where("id > 0") if userID > 0 { - sess.And("owner_id = ?", userID) + query = query.Where("owner_id = ?", userID) } if !private { - sess.And("is_private=?", false) + query = query.Where("is_private = ?", false) } - count, err := sess.Count(new(Repository)) + var count int64 + err := query.Count(&count).Error if err != nil { log.Error("countRepositories: %v", err) } @@ -1304,7 +1298,7 @@ func CountUserRepositories(userID int64, private bool) int64 { func Repositories(page, pageSize int) (_ []*Repository, err error) { repos := make([]*Repository, 0, pageSize) - return repos, x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos) + return repos, db.Limit(pageSize).Offset((page - 1) * pageSize).Order("id ASC").Find(&repos).Error } // RepositoriesWithUsers returns number of repos in given page. @@ -1332,11 +1326,11 @@ func FilterRepositoryWithIssues(repoIDs []int64) ([]int64, error) { } repos := make([]*Repository, 0, len(repoIDs)) - if err := x.Where("enable_issues=?", true). - And("enable_external_tracker=?", false). - In("id", repoIDs). - Cols("id"). - Find(&repos); err != nil { + if err := db.Select("id"). + Where("enable_issues = ?", true). + Where("enable_external_tracker = ?", false). + Where("id IN ?", repoIDs). + Find(&repos).Error; err != nil { return nil, errors.Newf("filter valid repositories %v: %v", repoIDs, err) } @@ -1373,12 +1367,6 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error return ErrRepoAlreadyExist{args: errutil.Args{"ownerName": newOwnerName, "name": repo.Name}} } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return errors.Newf("sess.Begin: %v", err) - } - owner := repo.Owner // Note: we have to set value here to make sure recalculate accesses is based on @@ -1386,121 +1374,123 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error repo.OwnerID = newOwner.ID repo.Owner = newOwner - // Update repository. - if _, err := sess.ID(repo.ID).Update(repo); err != nil { - return errors.Newf("update owner: %v", err) - } - - // Remove redundant collaborators. - collaborators, err := repo.getCollaborators(sess) - if err != nil { - return errors.Newf("getCollaborators: %v", err) - } - - // Dummy object. - collaboration := &Collaboration{RepoID: repo.ID} - for _, c := range collaborators { - collaboration.UserID = c.ID - if c.ID == newOwner.ID || newOwner.IsOrgMember(c.ID) { - if _, err = sess.Delete(collaboration); err != nil { - return errors.Newf("remove collaborator '%d': %v", c.ID, err) - } - } - } - - // Remove old team-repository relations. - if owner.IsOrganization() { - if err = owner.getTeams(sess); err != nil { - return errors.Newf("getTeams: %v", err) - } - for _, t := range owner.Teams { - if !t.hasRepository(sess, repo.ID) { - continue - } - - t.NumRepos-- - if _, err := sess.ID(t.ID).AllCols().Update(t); err != nil { - return errors.Newf("decrease team repository count '%d': %v", t.ID, err) - } + return db.Transaction(func(tx *gorm.DB) error { + // Update repository. + if err := tx.Model(&Repository{}).Where("id = ?", repo.ID).Updates(repo).Error; err != nil { + return errors.Newf("update owner: %v", err) } - if err = owner.removeOrgRepo(sess, repo.ID); err != nil { - return errors.Newf("removeOrgRepo: %v", err) - } - } - - if newOwner.IsOrganization() { - t, err := newOwner.getOwnerTeam(sess) + // Remove redundant collaborators. + collaborators, err := repo.getCollaborators(tx) if err != nil { - return errors.Newf("getOwnerTeam: %v", err) - } else if err = t.addRepository(sess, repo); err != nil { - return errors.Newf("add to owner team: %v", err) + return errors.Newf("getCollaborators: %v", err) } - } else { - // Organization called this in addRepository method. - if err = repo.recalculateAccesses(sess); err != nil { - return errors.Newf("recalculateAccesses: %v", err) + + // Dummy object. + collaboration := &Collaboration{RepoID: repo.ID} + for _, c := range collaborators { + collaboration.UserID = c.ID + if c.ID == newOwner.ID || newOwner.IsOrgMember(c.ID) { + if err := tx.Delete(collaboration).Error; err != nil { + return errors.Newf("remove collaborator '%d': %v", c.ID, err) + } + } } - } - // Update repository count. - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { - return errors.Newf("increase new owner repository count: %v", err) - } else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID); err != nil { - return errors.Newf("decrease old owner repository count: %v", err) - } + // Remove old team-repository relations. + if owner.IsOrganization() { + if err = owner.getTeams(tx); err != nil { + return errors.Newf("getTeams: %v", err) + } + for _, t := range owner.Teams { + if !t.hasRepository(tx, repo.ID) { + continue + } - // Remove watch for organization. - if owner.IsOrganization() { - if err = watchRepo(sess, owner.ID, repo.ID, false); err != nil { - return errors.Wrap(err, "unwatch repository for the organization owner") + t.NumRepos-- + if err := tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil { + return errors.Newf("decrease team repository count '%d': %v", t.ID, err) + } + } + + if err = owner.removeOrgRepo(tx, repo.ID); err != nil { + return errors.Newf("removeOrgRepo: %v", err) + } } - } - if err = watchRepo(sess, newOwner.ID, repo.ID, true); err != nil { - return errors.Newf("watchRepo: %v", err) - } - - // FIXME: This is identical to Actions.TransferRepo but we are not yet able to - // wrap transaction with different ORM objects, should delete this once migrated - // to GORM for this part of logic. - transferRepoAction := func(e Engine, doer, oldOwner *User, repo *Repository) error { - return notifyWatchers(e, &Action{ - ActUserID: doer.ID, - ActUserName: doer.Name, - OpType: ActionTransferRepo, - RepoID: repo.ID, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - IsPrivate: repo.IsPrivate || repo.IsUnlisted, - Content: path.Join(oldOwner.Name, repo.Name), - CreatedUnix: time.Now().Unix(), - }) - } - if err = transferRepoAction(sess, doer, owner, repo); err != nil { - return errors.Newf("transferRepoAction: %v", err) - } - - // Rename remote repository to new path and delete local copy. - if err = os.MkdirAll(repoutil.UserPath(newOwner.Name), os.ModePerm); err != nil { - return err - } - if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { - return errors.Newf("rename repository directory: %v", err) - } - - deleteRepoLocalCopy(repo.ID) - - // Rename remote wiki repository to new path and delete local copy. - wikiPath := WikiPath(owner.Name, repo.Name) - if com.IsExist(wikiPath) { - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { - return errors.Newf("rename repository wiki: %v", err) + if newOwner.IsOrganization() { + t, err := newOwner.getOwnerTeam(tx) + if err != nil { + return errors.Newf("getOwnerTeam: %v", err) + } else if err = t.addRepository(tx, repo); err != nil { + return errors.Newf("add to owner team: %v", err) + } + } else { + // Organization called this in addRepository method. + if err = repo.recalculateAccesses(tx); err != nil { + return errors.Newf("recalculateAccesses: %v", err) + } } - } - return sess.Commit() + // Update repository count. + if err = tx.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID).Error; err != nil { + return errors.Newf("increase new owner repository count: %v", err) + } else if err = tx.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID).Error; err != nil { + return errors.Newf("decrease old owner repository count: %v", err) + } + + // Remove watch for organization. + if owner.IsOrganization() { + if err = watchRepo(tx, owner.ID, repo.ID, false); err != nil { + return errors.Wrap(err, "unwatch repository for the organization owner") + } + } + + if err = watchRepo(tx, newOwner.ID, repo.ID, true); err != nil { + return errors.Newf("watchRepo: %v", err) + } + + // FIXME: This is identical to Actions.TransferRepo but we are not yet able to + // wrap transaction with different ORM objects, should delete this once migrated + // to GORM for this part of logic. + transferRepoAction := func(e *gorm.DB, doer, oldOwner *User, repo *Repository) error { + return notifyWatchers(e, &Action{ + ActUserID: doer.ID, + ActUserName: doer.Name, + OpType: ActionTransferRepo, + RepoID: repo.ID, + RepoUserName: repo.Owner.Name, + RepoName: repo.Name, + IsPrivate: repo.IsPrivate || repo.IsUnlisted, + Content: path.Join(oldOwner.Name, repo.Name), + CreatedUnix: time.Now().Unix(), + }) + } + if err = transferRepoAction(tx, doer, owner, repo); err != nil { + return errors.Newf("transferRepoAction: %v", err) + } + + // Rename remote repository to new path and delete local copy. + if err = os.MkdirAll(repoutil.UserPath(newOwner.Name), os.ModePerm); err != nil { + return err + } + if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { + return errors.Newf("rename repository directory: %v", err) + } + + deleteRepoLocalCopy(repo.ID) + + // Rename remote wiki repository to new path and delete local copy. + wikiPath := WikiPath(owner.Name, repo.Name) + if com.IsExist(wikiPath) { + RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) + if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { + return errors.Newf("rename repository wiki: %v", err) + } + } + + return nil + }) } func deleteRepoLocalCopy(repoID int64) { @@ -1546,27 +1536,27 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) return nil } -func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { +func getRepositoriesByForkID(e *gorm.DB, forkID int64) ([]*Repository, error) { repos := make([]*Repository, 0, 10) - return repos, e.Where("fork_id=?", forkID).Find(&repos) + return repos, e.Where("fork_id = ?", forkID).Find(&repos).Error } // GetRepositoriesByForkID returns all repositories with given fork ID. func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) { - return getRepositoriesByForkID(x, forkID) + return getRepositoriesByForkID(db, forkID) } -func getNonMirrorRepositories(e Engine) ([]*Repository, error) { +func getNonMirrorRepositories(e *gorm.DB) ([]*Repository, error) { repos := make([]*Repository, 0, 10) - return repos, e.Where("is_mirror = ?", false).Find(&repos) + return repos, e.Where("is_mirror = ?", false).Find(&repos).Error } // GetRepositoriesMirror returns only mirror repositories with user. func GetNonMirrorRepositories() ([]*Repository, error) { - return getNonMirrorRepositories(x) + return getNonMirrorRepositories(db) } -func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { +func updateRepository(e *gorm.DB, repo *Repository, visibilityChanged bool) (err error) { repo.LowerName = strings.ToLower(repo.Name) if len(repo.Description) > 512 { @@ -1576,7 +1566,7 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e repo.Website = repo.Website[:255] } - if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { + if err = e.Model(&Repository{}).Where("id = ?", repo.ID).Updates(repo).Error; err != nil { return errors.Newf("update: %v", err) } @@ -1618,7 +1608,7 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e } // Change visibility of generated actions - if _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{IsPrivate: repo.IsPrivate || repo.IsUnlisted}); err != nil { + if err = e.Model(&Action{}).Where("repo_id = ?", repo.ID).Update("is_private", repo.IsPrivate || repo.IsUnlisted).Error; err != nil { return errors.Newf("change action visibility of repository: %v", err) } } @@ -1627,27 +1617,23 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e } func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = updateRepository(x, repo, visibilityChanged); err != nil { - return errors.Newf("updateRepository: %v", err) - } - - return sess.Commit() + return db.Transaction(func(tx *gorm.DB) error { + if err := updateRepository(db, repo, visibilityChanged); err != nil { + return errors.Newf("updateRepository: %v", err) + } + return nil + }) } // DeleteRepository deletes a repository for a user or organization. func DeleteRepository(ownerID, repoID int64) error { repo := &Repository{ID: repoID, OwnerID: ownerID} - has, err := x.Get(repo) + err := db.First(repo).Error if err != nil { + if err == gorm.ErrRecordNotFound { + return ErrRepoNotExist{args: map[string]any{"ownerID": ownerID, "repoID": repoID}} + } return err - } else if !has { - return ErrRepoNotExist{args: map[string]any{"ownerID": ownerID, "repoID": repoID}} } // In case is a organization. @@ -1661,103 +1647,95 @@ func DeleteRepository(ownerID, repoID int64) error { } } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } + return db.Transaction(func(tx *gorm.DB) error { + if org.IsOrganization() { + for _, t := range org.Teams { + if !t.hasRepository(tx, repoID) { + continue + } else if err = t.removeRepository(tx, repo, false); err != nil { + return err + } + } + } - if org.IsOrganization() { - for _, t := range org.Teams { - if !t.hasRepository(sess, repoID) { - continue - } else if err = t.removeRepository(sess, repo, false); err != nil { + if err = deleteBeans(tx, + &Repository{ID: repoID}, + &Access{RepoID: repo.ID}, + &Action{RepoID: repo.ID}, + &Watch{RepoID: repoID}, + &Star{RepoID: repoID}, + &Mirror{RepoID: repoID}, + &IssueUser{RepoID: repoID}, + &Milestone{RepoID: repoID}, + &Release{RepoID: repoID}, + &Collaboration{RepoID: repoID}, + &PullRequest{BaseRepoID: repoID}, + &ProtectBranch{RepoID: repoID}, + &ProtectBranchWhitelist{RepoID: repoID}, + &Webhook{RepoID: repoID}, + &HookTask{RepoID: repoID}, + &LFSObject{RepoID: repoID}, + ); err != nil { + return errors.Newf("deleteBeans: %v", err) + } + + // Delete comments and attachments. + issues := make([]*Issue, 0, 25) + attachmentPaths := make([]string, 0, len(issues)) + if err = tx.Where("repo_id = ?", repoID).Find(&issues).Error; err != nil { + return err + } + for i := range issues { + if err = tx.Where("issue_id = ?", issues[i].ID).Delete(&Comment{}).Error; err != nil { + return err + } + + attachments := make([]*Attachment, 0, 5) + if err = tx.Where("issue_id = ?", issues[i].ID).Find(&attachments).Error; err != nil { + return err + } + for j := range attachments { + attachmentPaths = append(attachmentPaths, attachments[j].LocalPath()) + } + + if err = tx.Where("issue_id = ?", issues[i].ID).Delete(&Attachment{}).Error; err != nil { return err } } - } - if err = deleteBeans(sess, - &Repository{ID: repoID}, - &Access{RepoID: repo.ID}, - &Action{RepoID: repo.ID}, - &Watch{RepoID: repoID}, - &Star{RepoID: repoID}, - &Mirror{RepoID: repoID}, - &IssueUser{RepoID: repoID}, - &Milestone{RepoID: repoID}, - &Release{RepoID: repoID}, - &Collaboration{RepoID: repoID}, - &PullRequest{BaseRepoID: repoID}, - &ProtectBranch{RepoID: repoID}, - &ProtectBranchWhitelist{RepoID: repoID}, - &Webhook{RepoID: repoID}, - &HookTask{RepoID: repoID}, - &LFSObject{RepoID: repoID}, - ); err != nil { - return errors.Newf("deleteBeans: %v", err) - } - - // Delete comments and attachments. - issues := make([]*Issue, 0, 25) - attachmentPaths := make([]string, 0, len(issues)) - if err = sess.Where("repo_id=?", repoID).Find(&issues); err != nil { - return err - } - for i := range issues { - if _, err = sess.Delete(&Comment{IssueID: issues[i].ID}); err != nil { + if err = tx.Where("repo_id = ?", repoID).Delete(&Issue{}).Error; err != nil { return err } - attachments := make([]*Attachment, 0, 5) - if err = sess.Where("issue_id=?", issues[i].ID).Find(&attachments); err != nil { + if repo.IsFork { + if err = tx.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID).Error; err != nil { + return errors.Newf("decrease fork count: %v", err) + } + } + + if err = tx.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", ownerID).Error; err != nil { return err } - for j := range attachments { - attachmentPaths = append(attachmentPaths, attachments[j].LocalPath()) + + // Remove repository files. + repoPath := repo.RepoPath() + RemoveAllWithNotice("Delete repository files", repoPath) + + repo.DeleteWiki() + + // Remove attachment files. + for i := range attachmentPaths { + RemoveAllWithNotice("Delete attachment", attachmentPaths[i]) } - if _, err = sess.Delete(&Attachment{IssueID: issues[i].ID}); err != nil { - return err + if repo.NumForks > 0 { + if err = db.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID).Error; err != nil { + log.Error("reset 'fork_id' and 'is_fork': %v", err) + } } - } - if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil { - return err - } - - if repo.IsFork { - if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { - return errors.Newf("decrease fork count: %v", err) - } - } - - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", ownerID); err != nil { - return err - } - - if err = sess.Commit(); err != nil { - return errors.Newf("commit: %v", err) - } - - // Remove repository files. - repoPath := repo.RepoPath() - RemoveAllWithNotice("Delete repository files", repoPath) - - repo.DeleteWiki() - - // Remove attachment files. - for i := range attachmentPaths { - RemoveAllWithNotice("Delete attachment", attachmentPaths[i]) - } - - if repo.NumForks > 0 { - if _, err = x.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { - log.Error("reset 'fork_id' and 'is_fork': %v", err) - } - } - - return nil + return nil + }) } // GetRepositoryByRef returns a Repository specified by a GFM reference. @@ -1784,29 +1762,31 @@ func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { OwnerID: ownerID, LowerName: strings.ToLower(name), } - has, err := x.Get(repo) + err := db.Where("owner_id = ? AND lower_name = ?", ownerID, strings.ToLower(name)).First(repo).Error if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, ErrRepoNotExist{args: map[string]any{"ownerID": ownerID, "name": name}} + } return nil, err - } else if !has { - return nil, ErrRepoNotExist{args: map[string]any{"ownerID": ownerID, "name": name}} } return repo, repo.LoadAttributes() } -func getRepositoryByID(e Engine, id int64) (*Repository, error) { +func getRepositoryByID(e *gorm.DB, id int64) (*Repository, error) { repo := new(Repository) - has, err := e.ID(id).Get(repo) + err := e.First(repo, id).Error if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, ErrRepoNotExist{args: map[string]any{"repoID": id}} + } return nil, err - } else if !has { - return nil, ErrRepoNotExist{args: map[string]any{"repoID": id}} } return repo, repo.loadAttributes(e) } // GetRepositoryByID returns the repository by given id if exists. func GetRepositoryByID(id int64) (*Repository, error) { - return getRepositoryByID(x, id) + return getRepositoryByID(db, id) } type UserRepoOptions struct { @@ -1818,45 +1798,44 @@ type UserRepoOptions struct { // GetUserRepositories returns a list of repositories of given user. func GetUserRepositories(opts *UserRepoOptions) ([]*Repository, error) { - sess := x.Where("owner_id=?", opts.UserID).Desc("updated_unix") + query := db.Where("owner_id = ?", opts.UserID).Order("updated_unix DESC") if !opts.Private { - sess.And("is_private=?", false) - sess.And("is_unlisted=?", false) + query = query.Where("is_private = ?", false).Where("is_unlisted = ?", false) } if opts.Page <= 0 { opts.Page = 1 } - sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + query = query.Limit(opts.PageSize).Offset((opts.Page - 1) * opts.PageSize) repos := make([]*Repository, 0, opts.PageSize) - return repos, sess.Find(&repos) + return repos, query.Find(&repos).Error } // GetUserRepositories returns a list of mirror repositories of given user. func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { repos := make([]*Repository, 0, 10) - return repos, x.Where("owner_id = ?", userID).And("is_mirror = ?", true).Find(&repos) + return repos, db.Where("owner_id = ?", userID).Where("is_mirror = ?", true).Find(&repos).Error } // GetRecentUpdatedRepositories returns the list of repositories that are recently updated. func GetRecentUpdatedRepositories(page, pageSize int) (repos []*Repository, err error) { - return repos, x.Limit(pageSize, (page-1)*pageSize). - Where("is_private=?", false).Limit(pageSize).Desc("updated_unix").Find(&repos) + return repos, db.Limit(pageSize).Offset((page-1)*pageSize). + Where("is_private = ?", false).Order("updated_unix DESC").Find(&repos).Error } // GetUserAndCollaborativeRepositories returns list of repositories the user owns and collaborates. func GetUserAndCollaborativeRepositories(userID int64) ([]*Repository, error) { repos := make([]*Repository, 0, 10) - if err := x.Alias("repo"). - Join("INNER", "collaboration", "collaboration.repo_id = repo.id"). + if err := db.Table("repository AS repo"). + Joins("INNER JOIN collaboration ON collaboration.repo_id = repo.id"). Where("collaboration.user_id = ?", userID). - Find(&repos); err != nil { + Find(&repos).Error; err != nil { return nil, errors.Newf("select collaborative repositories: %v", err) } ownRepos := make([]*Repository, 0, 10) - if err := x.Where("owner_id = ?", userID).Find(&ownRepos); err != nil { + if err := db.Where("owner_id = ?", userID).Find(&ownRepos).Error; err != nil { return nil, errors.Newf("select own repositories: %v", err) } @@ -1881,35 +1860,35 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, count } repos = make([]*Repository, 0, opts.PageSize) - sess := x.Alias("repo") + query := db.Table("repository AS repo") // Attempt to find repositories that opts.UserID has access to, // this does not include other people's private repositories even if opts.UserID is an admin. if !opts.Private && opts.UserID > 0 { - sess.Join("LEFT", "access", "access.repo_id = repo.id"). + query = query.Joins("LEFT JOIN access ON access.repo_id = repo.id"). Where("repo.owner_id = ? OR access.user_id = ? OR (repo.is_private = ? AND repo.is_unlisted = ?) OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", opts.UserID, opts.UserID, false, false, true, true, true) } else { // Only return public repositories if opts.Private is not set if !opts.Private { - sess.And("(repo.is_private = ? AND repo.is_unlisted = ?) OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", false, false, true, true, true) + query = query.Where("(repo.is_private = ? AND repo.is_unlisted = ?) OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", false, false, true, true, true) } } if len(opts.Keyword) > 0 { - sess.And("repo.lower_name LIKE ? OR repo.description LIKE ?", "%"+strings.ToLower(opts.Keyword)+"%", "%"+strings.ToLower(opts.Keyword)+"%") + query = query.Where("repo.lower_name LIKE ? OR repo.description LIKE ?", "%"+strings.ToLower(opts.Keyword)+"%", "%"+strings.ToLower(opts.Keyword)+"%") } if opts.OwnerID > 0 { - sess.And("repo.owner_id = ?", opts.OwnerID) + query = query.Where("repo.owner_id = ?", opts.OwnerID) } // We need all fields (repo.*) in final list but only ID (repo.id) is good enough for counting. - count, err = sess.Clone().Distinct("repo.id").Count(new(Repository)) + err = query.Session(&gorm.Session{}).Distinct("repo.id").Count(&count).Error if err != nil { return nil, 0, errors.Newf("Count: %v", err) } if len(opts.OrderBy) > 0 { - sess.OrderBy("repo." + opts.OrderBy) + query = query.Order("repo." + opts.OrderBy) } - return repos, count, sess.Distinct("repo.*").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&repos) + return repos, count, query.Distinct("repo.*").Limit(opts.PageSize).Offset((opts.Page - 1) * opts.PageSize).Find(&repos).Error } func DeleteOldRepositoryArchives() { @@ -1923,9 +1902,12 @@ func DeleteOldRepositoryArchives() { formats := []string{"zip", "targz"} oldestTime := time.Now().Add(-conf.Cron.RepoArchiveCleanup.OlderThan) - if err := x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean any) error { - repo := bean.(*Repository) + err := db.Where("id > 0").FindInBatches(&[]Repository{}, 100, func(tx *gorm.DB, batch int) error { + var repos []Repository + if err := tx.Find(&repos).Error; err != nil { + return err + } + for _, repo := range repos { basePath := filepath.Join(repo.RepoPath(), "archives") for _, format := range formats { dirPath := filepath.Join(basePath, format) @@ -1965,9 +1947,10 @@ func DeleteOldRepositoryArchives() { } } } - - return nil - }); err != nil { + } + return nil + }).Error + if err != nil { log.Error("DeleteOldRepositoryArchives: %v", err) } } @@ -1980,23 +1963,35 @@ func DeleteRepositoryArchives() error { taskStatusTable.Start(taskNameCleanOldArchives) defer taskStatusTable.Stop(taskNameCleanOldArchives) - return x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean any) error { - repo := bean.(*Repository) - return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")) - }) + return db.Where("id > 0").FindInBatches(&[]Repository{}, 100, func(tx *gorm.DB, batch int) error { + var repos []Repository + if err := tx.Find(&repos).Error; err != nil { + return err + } + for _, repo := range repos { + if err := os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")); err != nil { + return err + } + } + return nil + }).Error } func gatherMissingRepoRecords() ([]*Repository, error) { repos := make([]*Repository, 0, 10) - if err := x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean any) error { - repo := bean.(*Repository) - if !com.IsDir(repo.RepoPath()) { - repos = append(repos, repo) + err := db.Where("id > 0").FindInBatches(&[]Repository{}, 100, func(tx *gorm.DB, batch int) error { + var batchRepos []Repository + if err := tx.Find(&batchRepos).Error; err != nil { + return err + } + for i := range batchRepos { + if !com.IsDir(batchRepos[i].RepoPath()) { + repos = append(repos, &batchRepos[i]) } - return nil - }); err != nil { + } + return nil + }).Error + if err != nil { if err2 := Handle.Notices().Create(context.TODO(), NoticeTypeRepository, fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { return nil, errors.Newf("CreateRepositoryNotice: %v", err) } @@ -2051,18 +2046,24 @@ func ReinitMissingRepositories() error { // SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks // to make sure the binary and custom conf path are up-to-date. func SyncRepositoryHooks() error { - return x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean any) error { - repo := bean.(*Repository) + return db.Where("id > 0").FindInBatches(&[]Repository{}, 100, func(tx *gorm.DB, batch int) error { + var repos []Repository + if err := tx.Find(&repos).Error; err != nil { + return err + } + for _, repo := range repos { if err := createDelegateHooks(repo.RepoPath()); err != nil { return err } if repo.HasWiki() { - return createDelegateHooks(repo.WikiPath()) + if err := createDelegateHooks(repo.WikiPath()); err != nil { + return err + } } - return nil - }) + } + return nil + }).Error } // Prevent duplicate running tasks. @@ -2085,9 +2086,12 @@ func GitFsck() { log.Trace("Doing: GitFsck") - if err := x.Where("id>0").Iterate(new(Repository), - func(idx int, bean any) error { - repo := bean.(*Repository) + err := db.Where("id > 0").FindInBatches(&[]Repository{}, 100, func(tx *gorm.DB, batch int) error { + var repos []Repository + if err := tx.Find(&repos).Error; err != nil { + return err + } + for _, repo := range repos { repoPath := repo.RepoPath() err := git.Fsck(repoPath, git.FsckOptions{ CommandOptions: git.CommandOptions{ @@ -2106,17 +2110,22 @@ func GitFsck() { log.Error("CreateRepositoryNotice: %v", err) } } - return nil - }); err != nil { + } + return nil + }).Error + if err != nil { log.Error("GitFsck: %v", err) } } func GitGcRepos() error { args := append([]string{"gc"}, conf.Git.GCArgs...) - return x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean any) error { - repo := bean.(*Repository) + return db.Where("id > 0").FindInBatches(&[]Repository{}, 100, func(tx *gorm.DB, batch int) error { + var repos []Repository + if err := tx.Find(&repos).Error; err != nil { + return err + } + for _, repo := range repos { if err := repo.GetOwner(); err != nil { return err } @@ -2127,8 +2136,9 @@ func GitGcRepos() error { if err != nil { return errors.Newf("%v: %v", err, stderr) } - return nil - }) + } + return nil + }).Error } type repoChecker struct { @@ -2137,17 +2147,20 @@ type repoChecker struct { } func repoStatsCheck(checker *repoChecker) { - results, err := x.Query(checker.querySQL) + type Result struct { + ID int64 `gorm:"column:id"` + } + var results []Result + err := db.Raw(checker.querySQL).Scan(&results).Error if err != nil { log.Error("Select %s: %v", checker.desc, err) return } for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", checker.desc, id) - _, err = x.Exec(checker.correctSQL, id, id) + log.Trace("Updating %s: %d", checker.desc, result.ID) + err = db.Exec(checker.correctSQL, result.ID, result.ID).Error if err != nil { - log.Error("Update %s[%d]: %v", checker.desc, id, err) + log.Error("Update %s[%d]: %v", checker.desc, result.ID, err) } } } @@ -2199,16 +2212,19 @@ func CheckRepoStats() { // ***** START: Repository.NumClosedIssues ***** desc := "repository count 'num_closed_issues'" - results, err := x.Query("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) + type Result struct { + ID int64 `gorm:"column:id"` + } + var results []Result + err := db.Raw("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).Scan(&results).Error if err != nil { log.Error("Select %s: %v", desc, err) } else { for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", desc, id) - _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) + log.Trace("Updating %s: %d", desc, result.ID) + err = db.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", result.ID, true, false, result.ID).Error if err != nil { - log.Error("Update %s[%d]: %v", desc, id, err) + log.Error("Update %s[%d]: %v", desc, result.ID, err) } } } @@ -2216,29 +2232,30 @@ func CheckRepoStats() { // FIXME: use checker when stop supporting old fork repo format. // ***** START: Repository.NumForks ***** - results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") + results = nil + err = db.Raw("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)").Scan(&results).Error if err != nil { log.Error("Select repository count 'num_forks': %v", err) } else { for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating repository count 'num_forks': %d", id) + log.Trace("Updating repository count 'num_forks': %d", result.ID) - repo, err := GetRepositoryByID(id) + repo, err := GetRepositoryByID(result.ID) if err != nil { - log.Error("GetRepositoryByID[%d]: %v", id, err) + log.Error("GetRepositoryByID[%d]: %v", result.ID, err) continue } - rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) + var count int64 + err = db.Model(&Repository{}).Where("fork_id = ?", repo.ID).Count(&count).Error if err != nil { log.Error("Select count of forks[%d]: %v", repo.ID, err) continue } - repo.NumForks = int(parseCountResult(rawResult)) + repo.NumForks = int(count) if err = UpdateRepository(repo, false); err != nil { - log.Error("UpdateRepository[%d]: %v", id, err) + log.Error("UpdateRepository[%d]: %v", result.ID, err) continue } } @@ -2248,7 +2265,7 @@ func CheckRepoStats() { type RepositoryList []*Repository -func (repos RepositoryList) loadAttributes(e Engine) error { +func (repos RepositoryList) loadAttributes(e *gorm.DB) error { if len(repos) == 0 { return nil } @@ -2263,7 +2280,7 @@ func (repos RepositoryList) loadAttributes(e Engine) error { userIDs = append(userIDs, userID) } users := make([]*User, 0, len(userIDs)) - if err := e.Where("id > 0").In("id", userIDs).Find(&users); err != nil { + if err := e.Where("id > 0").Where("id IN ?", userIDs).Find(&users).Error; err != nil { return errors.Newf("find users: %v", err) } for i := range users { @@ -2285,7 +2302,7 @@ func (repos RepositoryList) loadAttributes(e Engine) error { baseIDs = append(baseIDs, baseID) } baseRepos := make([]*Repository, 0, len(baseIDs)) - if err := e.Where("id > 0").In("id", baseIDs).Find(&baseRepos); err != nil { + if err := e.Where("id > 0").Where("id IN ?", baseIDs).Find(&baseRepos).Error; err != nil { return errors.Newf("find base repositories: %v", err) } for i := range baseRepos { @@ -2301,12 +2318,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error { } func (repos RepositoryList) LoadAttributes() error { - return repos.loadAttributes(x) + return repos.loadAttributes(db) } type MirrorRepositoryList []*Repository -func (repos MirrorRepositoryList) loadAttributes(e Engine) error { +func (repos MirrorRepositoryList) loadAttributes(e *gorm.DB) error { if len(repos) == 0 { return nil } @@ -2321,7 +2338,7 @@ func (repos MirrorRepositoryList) loadAttributes(e Engine) error { repoIDs = append(repoIDs, repos[i].ID) } mirrors := make([]*Mirror, 0, len(repoIDs)) - if err := e.Where("id > 0").In("repo_id", repoIDs).Find(&mirrors); err != nil { + if err := e.Where("id > 0").Where("repo_id IN ?", repoIDs).Find(&mirrors).Error; err != nil { return errors.Newf("find mirrors: %v", err) } @@ -2336,7 +2353,7 @@ func (repos MirrorRepositoryList) loadAttributes(e Engine) error { } func (repos MirrorRepositoryList) LoadAttributes() error { - return repos.loadAttributes(x) + return repos.loadAttributes(db) } // __ __ __ .__ @@ -2353,33 +2370,34 @@ type Watch struct { RepoID int64 `xorm:"UNIQUE(watch)" gorm:"uniqueIndex:watch_user_repo_unique;not null"` } -func isWatching(e Engine, userID, repoID int64) bool { - has, _ := e.Get(&Watch{0, userID, repoID}) - return has +func isWatching(e *gorm.DB, userID, repoID int64) bool { + var watch Watch + err := e.Where("user_id = ? AND repo_id = ?", userID, repoID).First(&watch).Error + return err == nil } // IsWatching checks if user has watched given repository. func IsWatching(userID, repoID int64) bool { - return isWatching(x, userID, repoID) + return isWatching(db, userID, repoID) } -func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) { +func watchRepo(e *gorm.DB, userID, repoID int64, watch bool) (err error) { if watch { if isWatching(e, userID, repoID) { return nil } - if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil { + if err = e.Create(&Watch{RepoID: repoID, UserID: userID}).Error; err != nil { return err } - _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID) + err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID).Error } else { if !isWatching(e, userID, repoID) { return nil } - if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil { + if err = e.Where("user_id = ? AND repo_id = ?", userID, repoID).Delete(&Watch{}).Error; err != nil { return err } - _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID) + err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID).Error } return err } @@ -2388,36 +2406,36 @@ func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) { // // Deprecated: Use Watches.Watch instead. func WatchRepo(userID, repoID int64, watch bool) (err error) { - return watchRepo(x, userID, repoID, watch) + return watchRepo(db, userID, repoID, watch) } // Deprecated: Use Repos.ListByRepo instead. -func getWatchers(e Engine, repoID int64) ([]*Watch, error) { +func getWatchers(e *gorm.DB, repoID int64) ([]*Watch, error) { watches := make([]*Watch, 0, 10) - return watches, e.Find(&watches, &Watch{RepoID: repoID}) + return watches, e.Where("repo_id = ?", repoID).Find(&watches).Error } // GetWatchers returns all watchers of given repository. // // Deprecated: Use Repos.ListByRepo instead. func GetWatchers(repoID int64) ([]*Watch, error) { - return getWatchers(x, repoID) + return getWatchers(db, repoID) } // Repository.GetWatchers returns range of users watching given repository. func (r *Repository) GetWatchers(page int) ([]*User, error) { users := make([]*User, 0, ItemsPerPage) - sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("watch.repo_id=?", r.ID) + query := db.Limit(ItemsPerPage).Offset((page - 1) * ItemsPerPage).Where("watch.repo_id = ?", r.ID) if conf.UsePostgreSQL { - sess = sess.Join("LEFT", "watch", `"user".id=watch.user_id`) + query = query.Table(`"user"`).Joins("LEFT JOIN watch ON \"user\".id = watch.user_id") } else { - sess = sess.Join("LEFT", "watch", "user.id=watch.user_id") + query = query.Table("user").Joins("LEFT JOIN watch ON user.id = watch.user_id") } - return users, sess.Find(&users) + return users, query.Find(&users).Error } // Deprecated: Use Actions.notifyWatchers instead. -func notifyWatchers(e Engine, act *Action) error { +func notifyWatchers(e *gorm.DB, act *Action) error { if act.CreatedUnix <= 0 { act.CreatedUnix = time.Now().Unix() } @@ -2433,7 +2451,7 @@ func notifyWatchers(e Engine, act *Action) error { // Add feed for actioner. act.UserID = act.ActUserID - if _, err = e.Insert(act); err != nil { + if err = e.Create(act).Error; err != nil { return errors.Newf("insert new action: %v", err) } @@ -2444,7 +2462,7 @@ func notifyWatchers(e Engine, act *Action) error { act.ID = 0 act.UserID = watchers[i].UserID - if _, err = e.Insert(act); err != nil { + if err = e.Create(act).Error; err != nil { return errors.Newf("insert new action: %v", err) } } @@ -2455,7 +2473,7 @@ func notifyWatchers(e Engine, act *Action) error { // // Deprecated: Use Actions.notifyWatchers instead. func NotifyWatchers(act *Action) error { - return notifyWatchers(x, act) + return notifyWatchers(db, act) } // _________ __ @@ -2479,41 +2497,42 @@ func StarRepo(userID, repoID int64, star bool) (err error) { if IsStaring(userID, repoID) { return nil } - if _, err = x.Insert(&Star{UserID: userID, RepoID: repoID}); err != nil { + if err = db.Create(&Star{UserID: userID, RepoID: repoID}).Error; err != nil { return err - } else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoID); err != nil { + } else if err = db.Exec("UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoID).Error; err != nil { return err } - _, err = x.Exec("UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", userID) + err = db.Exec("UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", userID).Error } else { if !IsStaring(userID, repoID) { return nil } - if _, err = x.Delete(&Star{0, userID, repoID}); err != nil { + if err = db.Where("uid = ? AND repo_id = ?", userID, repoID).Delete(&Star{}).Error; err != nil { return err - } else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil { + } else if err = db.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID).Error; err != nil { return err } - _, err = x.Exec("UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", userID) + err = db.Exec("UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", userID).Error } return err } // IsStaring checks if user has starred given repository. func IsStaring(userID, repoID int64) bool { - has, _ := x.Get(&Star{0, userID, repoID}) - return has + var star Star + err := db.Where("uid = ? AND repo_id = ?", userID, repoID).First(&star).Error + return err == nil } func (r *Repository) GetStargazers(page int) ([]*User, error) { users := make([]*User, 0, ItemsPerPage) - sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("star.repo_id=?", r.ID) + query := db.Limit(ItemsPerPage).Offset((page - 1) * ItemsPerPage).Where("star.repo_id = ?", r.ID) if conf.UsePostgreSQL { - sess = sess.Join("LEFT", "star", `"user".id=star.uid`) + query = query.Table(`"user"`).Joins("LEFT JOIN star ON \"user\".id = star.uid") } else { - sess = sess.Join("LEFT", "star", "user.id=star.uid") + query = query.Table("user").Joins("LEFT JOIN star ON user.id = star.uid") } - return users, sess.Find(&users) + return users, query.Find(&users).Error } // ___________ __ @@ -2527,11 +2546,12 @@ func (r *Repository) GetStargazers(page int) ([]*User, error) { // When user has already forked, it returns true along with the repository. func HasForkedRepo(ownerID, repoID int64) (*Repository, bool, error) { repo := new(Repository) - has, err := x.Where("owner_id = ? AND fork_id = ?", ownerID, repoID).Get(repo) + err := db.Where("owner_id = ? AND fork_id = ?", ownerID, repoID).First(repo).Error if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, false, nil + } return nil, false, err - } else if !has { - return nil, false, nil } return repo, true, repo.LoadAttributes() } @@ -2555,40 +2575,37 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string) ForkID: baseRepo.ID, } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } + err = db.Transaction(func(tx *gorm.DB) error { + if err := createRepository(tx, doer, owner, repo); err != nil { + return err + } else if err := tx.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", baseRepo.ID).Error; err != nil { + return err + } - if err = createRepository(sess, doer, owner, repo); err != nil { - return nil, err - } else if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", baseRepo.ID); err != nil { - return nil, err - } + repoPath := repo.repoPath(tx) + RemoveAllWithNotice("Repository path erase before creation", repoPath) - repoPath := repo.repoPath(sess) - RemoveAllWithNotice("Repository path erase before creation", repoPath) + _, stderr, err := process.ExecTimeout(10*time.Minute, + fmt.Sprintf("ForkRepository 'git clone': %s/%s", owner.Name, repo.Name), + "git", "clone", "--bare", baseRepo.RepoPath(), repoPath) + if err != nil { + return errors.Newf("git clone: %v - %s", err, stderr) + } - _, stderr, err := process.ExecTimeout(10*time.Minute, - fmt.Sprintf("ForkRepository 'git clone': %s/%s", owner.Name, repo.Name), - "git", "clone", "--bare", baseRepo.RepoPath(), repoPath) + _, stderr, err = process.ExecDir(-1, + repoPath, fmt.Sprintf("ForkRepository 'git update-server-info': %s", repoPath), + "git", "update-server-info") + if err != nil { + return errors.Newf("git update-server-info: %v - %s", err, stderr) + } + + if err = createDelegateHooks(repoPath); err != nil { + return errors.Newf("createDelegateHooks: %v", err) + } + + return nil + }) if err != nil { - return nil, errors.Newf("git clone: %v - %s", err, stderr) - } - - _, stderr, err = process.ExecDir(-1, - repoPath, fmt.Sprintf("ForkRepository 'git update-server-info': %s", repoPath), - "git", "update-server-info") - if err != nil { - return nil, errors.Newf("git update-server-info: %v - %s", err, stderr) - } - - if err = createDelegateHooks(repoPath); err != nil { - return nil, errors.Newf("createDelegateHooks: %v", err) - } - - if err = sess.Commit(); err != nil { return nil, errors.Newf("commit: %v", err) } @@ -2613,7 +2630,7 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string) func (r *Repository) GetForks() ([]*Repository, error) { forks := make([]*Repository, 0, r.NumForks) - if err := x.Find(&forks, &Repository{ForkID: r.ID}); err != nil { + if err := db.Where("fork_id = ?", r.ID).Find(&forks).Error; err != nil { return nil, err } @@ -2655,7 +2672,7 @@ func (r *Repository) CreateNewBranch(oldBranch, newBranch string) (err error) { } // Deprecated: Use Perms.SetRepoPerms instead. -func (r *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) { +func (r *Repository) refreshAccesses(e *gorm.DB, accessMap map[int64]AccessMode) (err error) { newAccesses := make([]Access, 0, len(accessMap)) for userID, mode := range accessMap { newAccesses = append(newAccesses, Access{ @@ -2666,16 +2683,16 @@ func (r *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) ( } // Delete old accesses and insert new ones for repository. - if _, err = e.Delete(&Access{RepoID: r.ID}); err != nil { + if err = e.Where("repo_id = ?", r.ID).Delete(&Access{}).Error; err != nil { return errors.Newf("delete old accesses: %v", err) - } else if _, err = e.Insert(newAccesses); err != nil { + } else if err = e.Create(newAccesses).Error; err != nil { return errors.Newf("insert new accesses: %v", err) } return nil } // refreshCollaboratorAccesses retrieves repository collaborations with their access modes. -func (r *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { +func (r *Repository) refreshCollaboratorAccesses(e *gorm.DB, accessMap map[int64]AccessMode) error { collaborations, err := r.getCollaborations(e) if err != nil { return errors.Newf("getCollaborations: %v", err) @@ -2689,7 +2706,7 @@ func (r *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]A // recalculateTeamAccesses recalculates new accesses for teams of an organization // except the team whose ID is given. It is used to assign a team ID when // remove repository from that team. -func (r *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) { +func (r *Repository) recalculateTeamAccesses(e *gorm.DB, ignTeamID int64) (err error) { accessMap := make(map[int64]AccessMode, 20) if err = r.getOwner(e); err != nil { @@ -2740,7 +2757,7 @@ func (r *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err err return r.refreshAccesses(e, accessMap) } -func (r *Repository) recalculateAccesses(e Engine) error { +func (r *Repository) recalculateAccesses(e *gorm.DB) error { if r.Owner.IsOrganization() { return r.recalculateTeamAccesses(e, 0) } @@ -2754,5 +2771,5 @@ func (r *Repository) recalculateAccesses(e Engine) error { // RecalculateAccesses recalculates all accesses for repository. func (r *Repository) RecalculateAccesses() error { - return r.recalculateAccesses(x) + return r.recalculateAccesses(db) } diff --git a/internal/database/repo_collaboration.go b/internal/database/repo_collaboration.go index 8c59f0742..52adf4c8a 100644 --- a/internal/database/repo_collaboration.go +++ b/internal/database/repo_collaboration.go @@ -5,6 +5,7 @@ import ( "github.com/cockroachdb/errors" api "github.com/gogs/go-gogs-client" + "gorm.io/gorm" ) // Collaboration represent the relation between an individual and a repository. @@ -76,9 +77,9 @@ func (r *Repository) AddCollaborator(u *User) error { return sess.Commit() } -func (r *Repository) getCollaborations(e Engine) ([]*Collaboration, error) { +func (r *Repository) getCollaborations(e *gorm.DB) ([]*Collaboration, error) { collaborations := make([]*Collaboration, 0) - return collaborations, e.Find(&collaborations, &Collaboration{RepoID: r.ID}) + return collaborations, e.Where("repo_id = ?", r.ID).Find(&collaborations).Error } // Collaborator represents a user with collaboration details. @@ -98,7 +99,7 @@ func (c *Collaborator) APIFormat() *api.Collaborator { } } -func (r *Repository) getCollaborators(e Engine) ([]*Collaborator, error) { +func (r *Repository) getCollaborators(e *gorm.DB) ([]*Collaborator, error) { collaborations, err := r.getCollaborations(e) if err != nil { return nil, errors.Newf("getCollaborations: %v", err) diff --git a/internal/database/repositories.go b/internal/database/repositories.go index 3e092d2aa..420e7d6b0 100644 --- a/internal/database/repositories.go +++ b/internal/database/repositories.go @@ -28,13 +28,6 @@ func (r *Repository) BeforeUpdate(tx *gorm.DB) error { return nil } -// AfterFind implements the GORM query hook. -func (r *Repository) AfterFind(_ *gorm.DB) error { - r.Created = time.Unix(r.CreatedUnix, 0).Local() - r.Updated = time.Unix(r.UpdatedUnix, 0).Local() - return nil -} - type RepositoryAPIFormatOptions struct { Permission *api.Permission Parent *api.Repository diff --git a/internal/database/two_factor.go b/internal/database/two_factor.go index 90eef9b92..b186d8474 100644 --- a/internal/database/two_factor.go +++ b/internal/database/two_factor.go @@ -8,6 +8,7 @@ import ( "github.com/cockroachdb/errors" "github.com/pquerna/otp/totp" "github.com/unknwon/com" + "gorm.io/gorm" "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/cryptoutil" @@ -67,9 +68,8 @@ func GetRecoveryCodesByUserID(userID int64) ([]*TwoFactorRecoveryCode, error) { return recoveryCodes, x.Where("user_id = ?", userID).Find(&recoveryCodes) } -func deleteRecoveryCodesByUserID(e Engine, userID int64) error { - _, err := e.Where("user_id = ?", userID).Delete(new(TwoFactorRecoveryCode)) - return err +func deleteRecoveryCodesByUserID(e *gorm.DB, userID int64) error { + return e.Where("user_id = ?", userID).Delete(&TwoFactorRecoveryCode{}).Error } // RegenerateRecoveryCodes regenerates new set of recovery codes for given user.