diff options
Diffstat (limited to 'models')
38 files changed, 1091 insertions, 1308 deletions
diff --git a/models/action.go b/models/action.go index 16d6c42aa5..da9e6776b1 100644 --- a/models/action.go +++ b/models/action.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" @@ -414,3 +415,127 @@ func DeleteOldActions(olderThan time.Duration) (err error) { _, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{}) return } + +func notifyWatchers(ctx context.Context, actions ...*Action) error { + var watchers []*repo_model.Watch + var repo *repo_model.Repository + var err error + var permCode []bool + var permIssue []bool + var permPR []bool + + e := db.GetEngine(ctx) + + for _, act := range actions { + repoChanged := repo == nil || repo.ID != act.RepoID + + if repoChanged { + // Add feeds for user self and all watchers. + watchers, err = repo_model.GetWatchers(ctx, act.RepoID) + if err != nil { + return fmt.Errorf("get watchers: %v", err) + } + } + + // Add feed for actioner. + act.UserID = act.ActUserID + if _, err = e.Insert(act); err != nil { + return fmt.Errorf("insert new actioner: %v", err) + } + + if repoChanged { + act.loadRepo() + repo = act.Repo + + // check repo owner exist. + if err := act.Repo.GetOwner(ctx); err != nil { + return fmt.Errorf("can't get repo owner: %v", err) + } + } else if act.Repo == nil { + act.Repo = repo + } + + // Add feed for organization + if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID { + act.ID = 0 + act.UserID = act.Repo.Owner.ID + if _, err = e.InsertOne(act); err != nil { + return fmt.Errorf("insert new actioner: %v", err) + } + } + + if repoChanged { + permCode = make([]bool, len(watchers)) + permIssue = make([]bool, len(watchers)) + permPR = make([]bool, len(watchers)) + for i, watcher := range watchers { + user, err := user_model.GetUserByIDEngine(e, watcher.UserID) + if err != nil { + permCode[i] = false + permIssue[i] = false + permPR[i] = false + continue + } + perm, err := getUserRepoPermission(ctx, repo, user) + if err != nil { + permCode[i] = false + permIssue[i] = false + permPR[i] = false + continue + } + permCode[i] = perm.CanRead(unit.TypeCode) + permIssue[i] = perm.CanRead(unit.TypeIssues) + permPR[i] = perm.CanRead(unit.TypePullRequests) + } + } + + for i, watcher := range watchers { + if act.ActUserID == watcher.UserID { + continue + } + act.ID = 0 + act.UserID = watcher.UserID + act.Repo.Units = nil + + switch act.OpType { + case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionPublishRelease, ActionDeleteBranch: + if !permCode[i] { + continue + } + case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue: + if !permIssue[i] { + continue + } + case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest: + if !permPR[i] { + continue + } + } + + if _, err = e.InsertOne(act); err != nil { + return fmt.Errorf("insert new action: %v", err) + } + } + } + return nil +} + +// NotifyWatchers creates batch of actions for every watcher. +func NotifyWatchers(actions ...*Action) error { + return notifyWatchers(db.DefaultContext, actions...) +} + +// NotifyWatchersActions creates batch of actions for every watcher. +func NotifyWatchersActions(acts []*Action) error { + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + for _, act := range acts { + if err := notifyWatchers(ctx, act); err != nil { + return err + } + } + return committer.Commit() +} diff --git a/models/action_test.go b/models/action_test.go index 02edae2df7..306d382364 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -92,3 +92,40 @@ func TestGetFeeds2(t *testing.T) { assert.NoError(t, err) assert.Len(t, actions, 0) } + +func TestNotifyWatchers(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + action := &Action{ + ActUserID: 8, + RepoID: 1, + OpType: ActionStarRepo, + } + assert.NoError(t, NotifyWatchers(action)) + + // One watchers are inactive, thus action is only created for user 8, 1, 4, 11 + unittest.AssertExistsAndLoadBean(t, &Action{ + ActUserID: action.ActUserID, + UserID: 8, + RepoID: action.RepoID, + OpType: action.OpType, + }) + unittest.AssertExistsAndLoadBean(t, &Action{ + ActUserID: action.ActUserID, + UserID: 1, + RepoID: action.RepoID, + OpType: action.OpType, + }) + unittest.AssertExistsAndLoadBean(t, &Action{ + ActUserID: action.ActUserID, + UserID: 4, + RepoID: action.RepoID, + OpType: action.OpType, + }) + unittest.AssertExistsAndLoadBean(t, &Action{ + ActUserID: action.ActUserID, + UserID: 11, + RepoID: action.RepoID, + OpType: action.OpType, + }) +} diff --git a/models/error.go b/models/error.go index 54556fd787..f0e8751d75 100644 --- a/models/error.go +++ b/models/error.go @@ -71,21 +71,6 @@ func (err ErrUserNotAllowedCreateOrg) Error() string { return "user is not allowed to create organizations" } -// ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error. -type ErrReachLimitOfRepo struct { - Limit int -} - -// IsErrReachLimitOfRepo checks if an error is a ErrReachLimitOfRepo. -func IsErrReachLimitOfRepo(err error) bool { - _, ok := err.(ErrReachLimitOfRepo) - return ok -} - -func (err ErrReachLimitOfRepo) Error() string { - return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit) -} - // __ __.__ __ .__ // / \ / \__| | _|__| // \ \/\/ / | |/ / | @@ -322,38 +307,6 @@ func (err ErrRepoTransferInProgress) Error() string { return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name) } -// ErrRepoAlreadyExist represents a "RepoAlreadyExist" kind of error. -type ErrRepoAlreadyExist struct { - Uname string - Name string -} - -// IsErrRepoAlreadyExist checks if an error is a ErrRepoAlreadyExist. -func IsErrRepoAlreadyExist(err error) bool { - _, ok := err.(ErrRepoAlreadyExist) - return ok -} - -func (err ErrRepoAlreadyExist) Error() string { - return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name) -} - -// ErrRepoFilesAlreadyExist represents a "RepoFilesAlreadyExist" kind of error. -type ErrRepoFilesAlreadyExist struct { - Uname string - Name string -} - -// IsErrRepoFilesAlreadyExist checks if an error is a ErrRepoAlreadyExist. -func IsErrRepoFilesAlreadyExist(err error) bool { - _, ok := err.(ErrRepoFilesAlreadyExist) - return ok -} - -func (err ErrRepoFilesAlreadyExist) Error() string { - return fmt.Sprintf("repository files already exist [uname: %s, name: %s]", err.Uname, err.Name) -} - // ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error. type ErrForkAlreadyExist struct { Uname string @@ -371,22 +324,6 @@ func (err ErrForkAlreadyExist) Error() string { return fmt.Sprintf("repository is already forked by user [uname: %s, repo path: %s, fork path: %s]", err.Uname, err.RepoName, err.ForkName) } -// ErrRepoRedirectNotExist represents a "RepoRedirectNotExist" kind of error. -type ErrRepoRedirectNotExist struct { - OwnerID int64 - RepoName string -} - -// IsErrRepoRedirectNotExist check if an error is an ErrRepoRedirectNotExist. -func IsErrRepoRedirectNotExist(err error) bool { - _, ok := err.(ErrRepoRedirectNotExist) - return ok -} - -func (err ErrRepoRedirectNotExist) Error() string { - return fmt.Sprintf("repository redirect does not exist [uid: %d, name: %s]", err.OwnerID, err.RepoName) -} - // ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error. type ErrInvalidCloneAddr struct { Host string diff --git a/models/issue_watch.go b/models/issue_watch.go index bf5c2593a3..181cd23433 100644 --- a/models/issue_watch.go +++ b/models/issue_watch.go @@ -6,6 +6,7 @@ package models import ( "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" ) @@ -80,11 +81,11 @@ func CheckIssueWatch(user *user_model.User, issue *Issue) (bool, error) { if exist { return iw.IsWatching, nil } - w, err := getWatch(db.GetEngine(db.DefaultContext), user.ID, issue.RepoID) + w, err := repo_model.GetWatch(db.DefaultContext, user.ID, issue.RepoID) if err != nil { return false, err } - return isWatchMode(w.Mode) || IsUserParticipantsOfIssue(user, issue), nil + return repo_model.IsWatchMode(w.Mode) || IsUserParticipantsOfIssue(user, issue), nil } // GetIssueWatchersIDs returns IDs of subscribers or explicit unsubscribers to a given issue id diff --git a/models/notification.go b/models/notification.go index b71973823a..0be0144924 100644 --- a/models/notification.go +++ b/models/notification.go @@ -225,7 +225,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n toNotify[id] = struct{}{} } if !(issue.IsPull && HasWorkInProgressPrefix(issue.Title)) { - repoWatches, err := getRepoWatchersIDs(e, issue.RepoID) + repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) if err != nil { return err } diff --git a/models/org.go b/models/org.go index e5cd80ab78..c135bb9d3c 100644 --- a/models/org.go +++ b/models/org.go @@ -794,7 +794,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { return fmt.Errorf("GetUserRepositories [%d]: %v", userID, err) } for _, repoID := range repoIDs { - if err = watchRepo(sess, userID, repoID, false); err != nil { + if err = repo_model.WatchRepoCtx(ctx, userID, repoID, false); err != nil { return err } } diff --git a/models/org_team.go b/models/org_team.go index c42312323c..3d4a2882c7 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -238,7 +238,7 @@ func (t *Team) addRepository(ctx context.Context, repo *repo_model.Repository) ( return fmt.Errorf("getMembers: %v", err) } for _, u := range t.Members { - if err = watchRepo(e, u.ID, repo.ID, true); err != nil { + if err = repo_model.WatchRepoCtx(ctx, u.ID, repo.ID, true); err != nil { return fmt.Errorf("watchRepo: %v", err) } } @@ -341,7 +341,7 @@ func (t *Team) removeAllRepositories(ctx context.Context) (err error) { continue } - if err = watchRepo(e, user.ID, repo.ID, false); err != nil { + if err = repo_model.WatchRepoCtx(ctx, user.ID, repo.ID, false); err != nil { return err } @@ -399,7 +399,7 @@ func (t *Team) removeRepository(ctx context.Context, repo *repo_model.Repository continue } - if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil { + if err = repo_model.WatchRepoCtx(ctx, teamUser.UID, repo.ID, false); err != nil { return err } @@ -857,7 +857,7 @@ func AddTeamMember(team *Team, userID int64) error { return err } if setting.Service.AutoWatchNewRepos { - if err = watchRepo(sess, userID, repo.ID, true); err != nil { + if err = repo_model.WatchRepoCtx(ctx, userID, repo.ID, true); err != nil { return err } } diff --git a/models/release.go b/models/release.go index a19d4f937f..51ac0426ac 100644 --- a/models/release.go +++ b/models/release.go @@ -370,3 +370,89 @@ func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, origi }) return err } + +// PushUpdateDeleteTagsContext updates a number of delete tags with context +func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repository, tags []string) error { + return pushUpdateDeleteTags(db.GetEngine(ctx), repo, tags) +} + +func pushUpdateDeleteTags(e db.Engine, repo *repo_model.Repository, tags []string) error { + if len(tags) == 0 { + return nil + } + lowerTags := make([]string, 0, len(tags)) + for _, tag := range tags { + lowerTags = append(lowerTags, strings.ToLower(tag)) + } + + if _, err := e. + Where("repo_id = ? AND is_tag = ?", repo.ID, true). + In("lower_tag_name", lowerTags). + Delete(new(Release)); err != nil { + return fmt.Errorf("Delete: %v", err) + } + + if _, err := e. + Where("repo_id = ? AND is_tag = ?", repo.ID, false). + In("lower_tag_name", lowerTags). + Cols("is_draft", "num_commits", "sha1"). + Update(&Release{ + IsDraft: true, + }); err != nil { + return fmt.Errorf("Update: %v", err) + } + + return nil +} + +// PushUpdateDeleteTag must be called for any push actions to delete tag +func PushUpdateDeleteTag(repo *repo_model.Repository, tagName string) error { + rel, err := GetRelease(repo.ID, tagName) + if err != nil { + if IsErrReleaseNotExist(err) { + return nil + } + return fmt.Errorf("GetRelease: %v", err) + } + if rel.IsTag { + if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil { + return fmt.Errorf("Delete: %v", err) + } + } else { + rel.IsDraft = true + rel.NumCommits = 0 + rel.Sha1 = "" + if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { + return fmt.Errorf("Update: %v", err) + } + } + + return nil +} + +// SaveOrUpdateTag must be called for any push actions to add tag +func SaveOrUpdateTag(repo *repo_model.Repository, newRel *Release) error { + rel, err := GetRelease(repo.ID, newRel.TagName) + if err != nil && !IsErrReleaseNotExist(err) { + return fmt.Errorf("GetRelease: %v", err) + } + + if rel == nil { + rel = newRel + if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil { + return fmt.Errorf("InsertOne: %v", err) + } + } else { + rel.Sha1 = newRel.Sha1 + rel.CreatedUnix = newRel.CreatedUnix + rel.NumCommits = newRel.NumCommits + rel.IsDraft = false + if rel.IsTag && newRel.PublisherID > 0 { + rel.PublisherID = newRel.PublisherID + } + if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { + return fmt.Errorf("Update: %v", err) + } + } + return nil +} diff --git a/models/repo.go b/models/repo.go index adc62c9528..397b4380d6 100644 --- a/models/repo.go +++ b/models/repo.go @@ -14,7 +14,6 @@ import ( "sort" "strconv" "strings" - "time" "unicode/utf8" _ "image/jpeg" // Needed for jpeg support @@ -218,7 +217,7 @@ func getReviewers(ctx context.Context, repo *repo_model.Repository, doerID, post "SELECT uid AS user_id FROM `org_user` WHERE org_id = ? "+ ") AND id NOT IN (?, ?) ORDER BY name", repo.ID, perm.AccessModeRead, - repo.ID, RepoWatchModeNormal, RepoWatchModeAuto, + repo.ID, repo_model.WatchModeNormal, repo_model.WatchModeAuto, repo.OwnerID, doerID, posterID). Find(&users); err != nil { @@ -280,7 +279,7 @@ func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, if user == nil { return false, nil } - if repo.OwnerID != user.ID && !HasForkedRepo(user.ID, repo.ID) { + if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { return true, nil } ownedOrgs, err := GetOrgsCanCreateRepoByUserID(user.ID) @@ -288,7 +287,7 @@ func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, return false, err } for _, org := range ownedOrgs { - if repo.OwnerID != org.ID && !HasForkedRepo(org.ID, repo.ID) { + if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) { return true, nil } } @@ -317,24 +316,6 @@ func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, er return false, nil } -// GetRepoReaders returns all users that have explicit read access or higher to the repository. -func GetRepoReaders(repo *repo_model.Repository) (_ []*user_model.User, err error) { - return getUsersWithAccessMode(db.DefaultContext, repo, perm.AccessModeRead) -} - -// GetRepoWriters returns all users that have write access to the repository. -func GetRepoWriters(repo *repo_model.Repository) (_ []*user_model.User, err error) { - return getUsersWithAccessMode(db.DefaultContext, repo, perm.AccessModeWrite) -} - -// IsRepoReader returns true if user has explicit read access or higher to the repository. -func IsRepoReader(repo *repo_model.Repository, userID int64) (bool, error) { - if repo.OwnerID == userID { - return true, nil - } - return db.GetEngine(db.DefaultContext).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm.AccessModeRead).Get(&Access{}) -} - // getUsersWithAccessMode returns users that have at least given access mode to the repository. func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm.AccessMode) (_ []*user_model.User, err error) { if err = repo.GetOwner(ctx); err != nil { @@ -372,35 +353,6 @@ func SetRepoReadBy(repoID, userID int64) error { return setRepoNotificationStatusReadIfUnread(db.GetEngine(db.DefaultContext), userID, repoID) } -// CheckCreateRepository check if could created a repository -func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdopt bool) error { - if !doer.CanCreateRepo() { - return ErrReachLimitOfRepo{u.MaxRepoCreation} - } - - if err := IsUsableRepoName(name); err != nil { - return err - } - - has, err := repo_model.IsRepositoryExist(u, name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{u.Name, name} - } - - repoPath := repo_model.RepoPath(u.Name, name) - isExist, err := util.IsExist(repoPath) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repoPath, err) - return err - } - if !overwriteOrAdopt && isExist { - return ErrRepoFilesAlreadyExist{u.Name, name} - } - return nil -} - // CreateRepoOptions contains the create repository options type CreateRepoOptions struct { Name string @@ -421,13 +373,6 @@ type CreateRepoOptions struct { MirrorInterval string } -// ForkRepoOptions contains the fork repository options -type ForkRepoOptions struct { - BaseRepo *repo_model.Repository - Name string - Description string -} - // GetRepoInitFile returns repository init files func GetRepoInitFile(tp, name string) ([]byte, error) { cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") @@ -457,23 +402,9 @@ func GetRepoInitFile(tp, name string) ([]byte, error) { } } -var ( - reservedRepoNames = []string{".", ".."} - reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} -) - -// IsUsableRepoName returns true when repository is usable -func IsUsableRepoName(name string) error { - if db.AlphaDashDotPattern.MatchString(name) { - // Note: usually this error is normally caught up earlier in the UI - return db.ErrNameCharsNotAllowed{Name: name} - } - return db.IsUsableName(reservedRepoNames, reservedRepoPatterns, name) -} - // CreateRepository creates a repository for the user/organization. func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt bool) (err error) { - if err = IsUsableRepoName(repo.Name); err != nil { + if err = repo_model.IsUsableRepoName(repo.Name); err != nil { return err } @@ -481,7 +412,10 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{u.Name, repo.Name} + return repo_model.ErrRepoAlreadyExist{ + Uname: u.Name, + Name: repo.Name, + } } repoPath := repo_model.RepoPath(u.Name, repo.Name) @@ -492,7 +426,7 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ } if !overwriteOrAdopt && isExist { log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath) - return ErrRepoFilesAlreadyExist{ + return repo_model.ErrRepoFilesAlreadyExist{ Uname: u.Name, Name: repo.Name, } @@ -501,7 +435,7 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ if err = db.Insert(ctx, repo); err != nil { return err } - if err = deleteRepoRedirect(db.GetEngine(ctx), u.ID, repo.Name); err != nil { + if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil { return err } @@ -578,7 +512,7 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ } if setting.Service.AutoWatchNewRepos { - if err = watchRepo(db.GetEngine(ctx), doer.ID, repo.ID, true); err != nil { + if err = repo_model.WatchRepoCtx(ctx, doer.ID, repo.ID, true); err != nil { return fmt.Errorf("watchRepo: %v", err) } } @@ -633,67 +567,6 @@ func DecrementRepoForkNum(ctx context.Context, repoID int64) error { return err } -// ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(doer *user_model.User, repo *repo_model.Repository, newRepoName string) (err error) { - oldRepoName := repo.Name - newRepoName = strings.ToLower(newRepoName) - if err = IsUsableRepoName(newRepoName); err != nil { - return err - } - - if err := repo.GetOwner(db.DefaultContext); err != nil { - return err - } - - has, err := repo_model.IsRepositoryExist(repo.Owner, newRepoName) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{repo.Owner.Name, newRepoName} - } - - newRepoPath := repo_model.RepoPath(repo.Owner.Name, newRepoName) - if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil { - return fmt.Errorf("rename repository directory: %v", err) - } - - wikiPath := repo.WikiPath() - isExist, err := util.IsExist(wikiPath) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) - return err - } - if isExist { - if err = util.Rename(wikiPath, repo_model.WikiPath(repo.Owner.Name, newRepoName)); err != nil { - return fmt.Errorf("rename repository wiki: %v", err) - } - } - - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - if err := newRepoRedirect(db.GetEngine(ctx), repo.Owner.ID, repo.ID, oldRepoName, newRepoName); err != nil { - return err - } - - return committer.Commit() -} - -func getRepositoriesByForkID(e db.Engine, forkID int64) ([]*repo_model.Repository, error) { - repos := make([]*repo_model.Repository, 0, 10) - return repos, e. - Where("fork_id=?", forkID). - Find(&repos) -} - -// GetRepositoriesByForkID returns all repositories with given fork ID. -func GetRepositoriesByForkID(forkID int64) ([]*repo_model.Repository, error) { - return getRepositoriesByForkID(db.GetEngine(db.DefaultContext), forkID) -} - func updateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { repo.LowerName = strings.ToLower(repo.Name) @@ -740,7 +613,7 @@ func updateRepository(ctx context.Context, repo *repo_model.Repository, visibili return err } - forkRepos, err := getRepositoriesByForkID(e, repo.ID) + forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) if err != nil { return fmt.Errorf("getRepositoriesByForkID: %v", err) } @@ -775,58 +648,6 @@ func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err return committer.Commit() } -// UpdateRepositoryOwnerNames updates repository owner_names (this should only be used when the ownerName has changed case) -func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error { - if ownerID == 0 { - return nil - } - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").Update(&repo_model.Repository{ - OwnerName: ownerName, - }); err != nil { - return err - } - - return committer.Commit() -} - -// UpdateRepositoryUpdatedTime updates a repository's updated time -func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { - _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) - return err -} - -// UpdateRepositoryUnits updates a repository's units -func UpdateRepositoryUnits(repo *repo_model.Repository, units []repo_model.RepoUnit, deleteUnitTypes []unit.Type) (err error) { - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - // Delete existing settings of units before adding again - for _, u := range units { - deleteUnitTypes = append(deleteUnitTypes, u.Type) - } - - if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(repo_model.RepoUnit)); err != nil { - return err - } - - if len(units) > 0 { - if err = db.Insert(ctx, units); err != nil { - return err - } - } - - return committer.Commit() -} - // DeleteRepository deletes a repository for a user or organization. // make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) func DeleteRepository(doer *user_model.User, uid, repoID int64) error { @@ -927,11 +748,11 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { &repo_model.PushMirror{RepoID: repoID}, &Release{RepoID: repoID}, &repo_model.RepoIndexerStatus{RepoID: repoID}, - &RepoRedirect{RedirectRepoID: repoID}, + &repo_model.Redirect{RedirectRepoID: repoID}, &repo_model.RepoUnit{RepoID: repoID}, - &Star{RepoID: repoID}, + &repo_model.Star{RepoID: repoID}, &Task{RepoID: repoID}, - &Watch{RepoID: repoID}, + &repo_model.Watch{RepoID: repoID}, &webhook.Webhook{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %v", err) @@ -964,7 +785,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { } if len(repo.Topics) > 0 { - if err := removeTopicsFromRepo(sess, repo.ID); err != nil { + if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil { return err } } @@ -1261,76 +1082,6 @@ func CheckRepoStats(ctx context.Context) error { return nil } -// SetArchiveRepoState sets if a repo is archived -func SetArchiveRepoState(repo *repo_model.Repository, isArchived bool) (err error) { - repo.IsArchived = isArchived - _, err = db.GetEngine(db.DefaultContext).Where("id = ?", repo.ID).Cols("is_archived").NoAutoTime().Update(repo) - return -} - -// ___________ __ -// \_ _____/__________| | __ -// | __)/ _ \_ __ \ |/ / -// | \( <_> ) | \/ < -// \___ / \____/|__| |__|_ \ -// \/ \/ - -// GetForkedRepo checks if given user has already forked a repository with given ID. -func GetForkedRepo(ownerID, repoID int64) *repo_model.Repository { - repo := new(repo_model.Repository) - has, _ := db.GetEngine(db.DefaultContext). - Where("owner_id=? AND fork_id=?", ownerID, repoID). - Get(repo) - if has { - return repo - } - return nil -} - -// HasForkedRepo checks if given user has already forked a repository with given ID. -func HasForkedRepo(ownerID, repoID int64) bool { - has, _ := db.GetEngine(db.DefaultContext). - Table("repository"). - Where("owner_id=? AND fork_id=?", ownerID, repoID). - Exist() - return has -} - -// GetForks returns all the forks of the repository -func GetForks(repo *repo_model.Repository, listOptions db.ListOptions) ([]*repo_model.Repository, error) { - if listOptions.Page == 0 { - forks := make([]*repo_model.Repository, 0, repo.NumForks) - return forks, db.GetEngine(db.DefaultContext).Find(&forks, &repo_model.Repository{ForkID: repo.ID}) - } - - sess := db.GetPaginatedSession(&listOptions) - forks := make([]*repo_model.Repository, 0, listOptions.PageSize) - return forks, sess.Find(&forks, &repo_model.Repository{ForkID: repo.ID}) -} - -// GetUserFork return user forked repository from this repository, if not forked return nil -func GetUserFork(repoID, userID int64) (*repo_model.Repository, error) { - var forkedRepo repo_model.Repository - has, err := db.GetEngine(db.DefaultContext).Where("fork_id = ?", repoID).And("owner_id = ?", userID).Get(&forkedRepo) - if err != nil { - return nil, err - } - if !has { - return nil, nil - } - return &forkedRepo, nil -} - -func updateRepositoryCols(e db.Engine, repo *repo_model.Repository, cols ...string) error { - _, err := e.ID(repo.ID).Cols(cols...).Update(repo) - return err -} - -// UpdateRepositoryCols updates repository's columns -func UpdateRepositoryCols(repo *repo_model.Repository, cols ...string) error { - return updateRepositoryCols(db.GetEngine(db.DefaultContext), repo, cols...) -} - func updateUserStarNumbers(users []user_model.User) error { ctx, committer, err := db.TxContext() if err != nil { @@ -1370,28 +1121,6 @@ func DoctorUserStarNum() (err error) { return } -// IterateRepository iterate repositories -func IterateRepository(f func(repo *repo_model.Repository) error) error { - var start int - batchSize := setting.Database.IterateBufferSize - for { - repos := make([]*repo_model.Repository, 0, batchSize) - if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&repos); err != nil { - return err - } - if len(repos) == 0 { - return nil - } - start += len(repos) - - for _, repo := range repos { - if err := f(repo); err != nil { - return err - } - } - } -} - // LinkedRepository returns the linked repo if any func LinkedRepository(a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { if a.IssueID != 0 { diff --git a/models/repo/archiver.go b/models/repo/archiver.go index cee6013ca3..c29891397f 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -107,3 +107,10 @@ func FindRepoArchives(opts FindRepoArchiversOption) ([]*RepoArchiver, error) { Find(&archivers) return archivers, err } + +// SetArchiveRepoState sets if a repo is archived +func SetArchiveRepoState(repo *Repository, isArchived bool) (err error) { + repo.IsArchived = isArchived + _, err = db.GetEngine(db.DefaultContext).Where("id = ?", repo.ID).Cols("is_archived").NoAutoTime().Update(repo) + return +} diff --git a/models/repo/fork.go b/models/repo/fork.go new file mode 100644 index 0000000000..570a5b68ab --- /dev/null +++ b/models/repo/fork.go @@ -0,0 +1,69 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "context" + + "code.gitea.io/gitea/models/db" +) + +func getRepositoriesByForkID(e db.Engine, forkID int64) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, e. + Where("fork_id=?", forkID). + Find(&repos) +} + +// GetRepositoriesByForkID returns all repositories with given fork ID. +func GetRepositoriesByForkID(ctx context.Context, forkID int64) ([]*Repository, error) { + return getRepositoriesByForkID(db.GetEngine(ctx), forkID) +} + +// GetForkedRepo checks if given user has already forked a repository with given ID. +func GetForkedRepo(ownerID, repoID int64) *Repository { + repo := new(Repository) + has, _ := db.GetEngine(db.DefaultContext). + Where("owner_id=? AND fork_id=?", ownerID, repoID). + Get(repo) + if has { + return repo + } + return nil +} + +// HasForkedRepo checks if given user has already forked a repository with given ID. +func HasForkedRepo(ownerID, repoID int64) bool { + has, _ := db.GetEngine(db.DefaultContext). + Table("repository"). + Where("owner_id=? AND fork_id=?", ownerID, repoID). + Exist() + return has +} + +// GetUserFork return user forked repository from this repository, if not forked return nil +func GetUserFork(repoID, userID int64) (*Repository, error) { + var forkedRepo Repository + has, err := db.GetEngine(db.DefaultContext).Where("fork_id = ?", repoID).And("owner_id = ?", userID).Get(&forkedRepo) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return &forkedRepo, nil +} + +// GetForks returns all the forks of the repository +func GetForks(repo *Repository, listOptions db.ListOptions) ([]*Repository, error) { + if listOptions.Page == 0 { + forks := make([]*Repository, 0, repo.NumForks) + return forks, db.GetEngine(db.DefaultContext).Find(&forks, &Repository{ForkID: repo.ID}) + } + + sess := db.GetPaginatedSession(&listOptions) + forks := make([]*Repository, 0, listOptions.PageSize) + return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) +} diff --git a/models/repo/fork_test.go b/models/repo/fork_test.go new file mode 100644 index 0000000000..bf6b90b388 --- /dev/null +++ b/models/repo/fork_test.go @@ -0,0 +1,32 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestGetUserFork(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // User13 has repo 11 forked from repo10 + repo, err := GetRepositoryByID(10) + assert.NoError(t, err) + assert.NotNil(t, repo) + repo, err = GetUserFork(repo.ID, 13) + assert.NoError(t, err) + assert.NotNil(t, repo) + + repo, err = GetRepositoryByID(9) + assert.NoError(t, err) + assert.NotNil(t, repo) + repo, err = GetUserFork(repo.ID, 13) + assert.NoError(t, err) + assert.Nil(t, repo) +} diff --git a/models/repo/main_test.go b/models/repo/main_test.go index f40a976281..fdd6c3f4d3 100644 --- a/models/repo/main_test.go +++ b/models/repo/main_test.go @@ -18,5 +18,11 @@ func TestMain(m *testing.M) { "repository.yml", "repo_unit.yml", "repo_indexer_status.yml", + "repo_redirect.yml", + "watch.yml", + "star.yml", + "topic.yml", + "repo_topic.yml", + "user.yml", ) } diff --git a/models/repo/redirect.go b/models/repo/redirect.go new file mode 100644 index 0000000000..88fad6f3e3 --- /dev/null +++ b/models/repo/redirect.go @@ -0,0 +1,82 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "context" + "fmt" + "strings" + + "code.gitea.io/gitea/models/db" +) + +// ErrRedirectNotExist represents a "RedirectNotExist" kind of error. +type ErrRedirectNotExist struct { + OwnerID int64 + RepoName string +} + +// IsErrRedirectNotExist check if an error is an ErrRepoRedirectNotExist. +func IsErrRedirectNotExist(err error) bool { + _, ok := err.(ErrRedirectNotExist) + return ok +} + +func (err ErrRedirectNotExist) Error() string { + return fmt.Sprintf("repository redirect does not exist [uid: %d, name: %s]", err.OwnerID, err.RepoName) +} + +// Redirect represents that a repo name should be redirected to another +type Redirect struct { + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"UNIQUE(s)"` + LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` + RedirectRepoID int64 // repoID to redirect to +} + +// TableName represents real table name in database +func (Redirect) TableName() string { + return "repo_redirect" +} + +func init() { + db.RegisterModel(new(Redirect)) +} + +// LookupRedirect look up if a repository has a redirect name +func LookupRedirect(ownerID int64, repoName string) (int64, error) { + repoName = strings.ToLower(repoName) + redirect := &Redirect{OwnerID: ownerID, LowerName: repoName} + if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil { + return 0, err + } else if !has { + return 0, ErrRedirectNotExist{OwnerID: ownerID, RepoName: repoName} + } + return redirect.RedirectRepoID, nil +} + +// NewRedirect create a new repo redirect +func NewRedirect(ctx context.Context, ownerID, repoID int64, oldRepoName, newRepoName string) error { + oldRepoName = strings.ToLower(oldRepoName) + newRepoName = strings.ToLower(newRepoName) + + if err := DeleteRedirect(ctx, ownerID, newRepoName); err != nil { + return err + } + + return db.Insert(ctx, &Redirect{ + OwnerID: ownerID, + LowerName: oldRepoName, + RedirectRepoID: repoID, + }) +} + +// DeleteRedirect delete any redirect from the specified repo name to +// anything else +func DeleteRedirect(ctx context.Context, ownerID int64, repoName string) error { + repoName = strings.ToLower(repoName) + _, err := db.GetEngine(ctx).Delete(&Redirect{OwnerID: ownerID, LowerName: repoName}) + return err +} diff --git a/models/repo/redirect_test.go b/models/repo/redirect_test.go new file mode 100644 index 0000000000..2dca2cbbfd --- /dev/null +++ b/models/repo/redirect_test.go @@ -0,0 +1,77 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestLookupRedirect(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repoID, err := LookupRedirect(2, "oldrepo1") + assert.NoError(t, err) + assert.EqualValues(t, 1, repoID) + + _, err = LookupRedirect(unittest.NonexistentID, "doesnotexist") + assert.True(t, IsErrRedirectNotExist(err)) +} + +func TestNewRedirect(t *testing.T) { + // redirect to a completely new name + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) + + unittest.AssertExistsAndLoadBean(t, &Redirect{ + OwnerID: repo.OwnerID, + LowerName: repo.LowerName, + RedirectRepoID: repo.ID, + }) + unittest.AssertExistsAndLoadBean(t, &Redirect{ + OwnerID: repo.OwnerID, + LowerName: "oldrepo1", + RedirectRepoID: repo.ID, + }) +} + +func TestNewRedirect2(t *testing.T) { + // redirect to previously used name + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) + + unittest.AssertExistsAndLoadBean(t, &Redirect{ + OwnerID: repo.OwnerID, + LowerName: repo.LowerName, + RedirectRepoID: repo.ID, + }) + unittest.AssertNotExistsBean(t, &Redirect{ + OwnerID: repo.OwnerID, + LowerName: "oldrepo1", + RedirectRepoID: repo.ID, + }) +} + +func TestNewRedirect3(t *testing.T) { + // redirect for a previously-unredirected repo + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + assert.NoError(t, NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) + + unittest.AssertExistsAndLoadBean(t, &Redirect{ + OwnerID: repo.OwnerID, + LowerName: repo.LowerName, + RedirectRepoID: repo.ID, + }) +} diff --git a/models/repo/repo.go b/models/repo/repo.go index 9353e813bc..8907691dde 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -25,6 +25,20 @@ import ( "code.gitea.io/gitea/modules/util" ) +var ( + reservedRepoNames = []string{".", ".."} + reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} +) + +// IsUsableRepoName returns true when repository is usable +func IsUsableRepoName(name string) error { + if db.AlphaDashDotPattern.MatchString(name) { + // Note: usually this error is normally caught up earlier in the UI + return db.ErrNameCharsNotAllowed{Name: name} + } + return db.IsUsableName(reservedRepoNames, reservedRepoPatterns, name) +} + // TrustModelType defines the types of trust model for this repository type TrustModelType int @@ -734,3 +748,25 @@ func GetPublicRepositoryCount(u *user_model.User) (int64, error) { func GetPrivateRepositoryCount(u *user_model.User) (int64, error) { return getPrivateRepositoryCount(db.GetEngine(db.DefaultContext), u) } + +// IterateRepository iterate repositories +func IterateRepository(f func(repo *Repository) error) error { + var start int + batchSize := setting.Database.IterateBufferSize + for { + repos := make([]*Repository, 0, batchSize) + if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&repos); err != nil { + return err + } + if len(repos) == 0 { + return nil + } + start += len(repos) + + for _, repo := range repos { + if err := f(repo); err != nil { + return err + } + } + } +} diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index 6f48a22e49..92b95f1d41 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -42,3 +42,10 @@ func TestGetPrivateRepositoryCount(t *testing.T) { assert.NoError(t, err) assert.Equal(t, int64(2), count) } + +func TestRepoAPIURL(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) + + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL()) +} diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 5f6c43f02f..1957f88ff3 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -242,3 +242,29 @@ func UpdateRepoUnit(unit *RepoUnit) error { _, err := db.GetEngine(db.DefaultContext).ID(unit.ID).Update(unit) return err } + +// UpdateRepositoryUnits updates a repository's units +func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) { + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + // Delete existing settings of units before adding again + for _, u := range units { + deleteUnitTypes = append(deleteUnitTypes, u.Type) + } + + if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil { + return err + } + + if len(units) > 0 { + if err = db.Insert(ctx, units); err != nil { + return err + } + } + + return committer.Commit() +} diff --git a/models/star.go b/models/repo/star.go index de3207797e..8db297e3b4 100644 --- a/models/star.go +++ b/models/repo/star.go @@ -2,11 +2,10 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package repo import ( "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" ) @@ -76,7 +75,7 @@ func isStaring(e db.Engine, userID, repoID int64) bool { } // GetStargazers returns the users that starred the repo. -func GetStargazers(repo *repo_model.Repository, opts db.ListOptions) ([]*user_model.User, error) { +func GetStargazers(repo *Repository, opts db.ListOptions) ([]*user_model.User, error) { sess := db.GetEngine(db.DefaultContext).Where("star.repo_id = ?", repo.ID). Join("LEFT", "star", "`user`.id = star.uid") if opts.Page > 0 { diff --git a/models/star_test.go b/models/repo/star_test.go index 8da83661c9..20c4b6bef4 100644 --- a/models/star_test.go +++ b/models/repo/star_test.go @@ -2,13 +2,12 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package repo import ( "testing" "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -36,7 +35,7 @@ func TestIsStaring(t *testing.T) { func TestRepository_GetStargazers(t *testing.T) { // repo with stargazers assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository) + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) gazers, err := GetStargazers(repo, db.ListOptions{Page: 0}) assert.NoError(t, err) if assert.Len(t, gazers, 1) { @@ -47,7 +46,7 @@ func TestRepository_GetStargazers(t *testing.T) { func TestRepository_GetStargazers2(t *testing.T) { // repo with stargazers assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) gazers, err := GetStargazers(repo, db.ListOptions{Page: 0}) assert.NoError(t, err) assert.Len(t, gazers, 0) diff --git a/models/topic.go b/models/repo/topic.go index 2767d6c58b..121863519b 100644 --- a/models/topic.go +++ b/models/repo/topic.go @@ -2,15 +2,15 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package repo import ( + "context" "fmt" "regexp" "strings" "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" @@ -33,7 +33,7 @@ type Topic struct { } // RepoTopic represents associated repositories and topics -type RepoTopic struct { +type RepoTopic struct { //revive:disable-line:exported RepoID int64 `xorm:"pk"` TopicID int64 `xorm:"pk"` } @@ -145,8 +145,9 @@ func removeTopicFromRepo(e db.Engine, repoID int64, topic *Topic) error { return nil } -// removeTopicsFromRepo remove all topics from the repo and decrements respective topics repo count -func removeTopicsFromRepo(e db.Engine, repoID int64) error { +// RemoveTopicsFromRepo remove all topics from the repo and decrements respective topics repo count +func RemoveTopicsFromRepo(ctx context.Context, repoID int64) error { + e := db.GetEngine(ctx) _, err := e.Where( builder.In("id", builder.Select("topic_id").From("repo_topic").Where(builder.Eq{"repo_id": repoID}), @@ -254,7 +255,7 @@ func AddTopic(repoID int64, topicName string) (*Topic, error) { return nil, err } - if _, err := sess.ID(repoID).Cols("topics").Update(&repo_model.Repository{ + if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{ Topics: topicNames, }); err != nil { return nil, err @@ -348,7 +349,7 @@ func SaveTopics(repoID int64, topicNames ...string) error { return err } - if _, err := sess.ID(repoID).Cols("topics").Update(&repo_model.Repository{ + if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{ Topics: topicNames, }); err != nil { return err @@ -356,3 +357,13 @@ func SaveTopics(repoID int64, topicNames ...string) error { return committer.Commit() } + +// GenerateTopics generates topics from a template repository +func GenerateTopics(ctx context.Context, templateRepo, generateRepo *Repository) error { + for _, topic := range templateRepo.Topics { + if _, err := addTopicByNameToRepo(db.GetEngine(ctx), generateRepo.ID, topic); err != nil { + return err + } + } + return nil +} diff --git a/models/topic_test.go b/models/repo/topic_test.go index 0219bdded5..353d96ef3e 100644 --- a/models/topic_test.go +++ b/models/repo/topic_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package repo import ( "testing" diff --git a/models/repo/update.go b/models/repo/update.go new file mode 100644 index 0000000000..efc562a405 --- /dev/null +++ b/models/repo/update.go @@ -0,0 +1,179 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "context" + "fmt" + "strings" + "time" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" +) + +// UpdateRepositoryOwnerNames updates repository owner_names (this should only be used when the ownerName has changed case) +func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error { + if ownerID == 0 { + return nil + } + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").Update(&Repository{ + OwnerName: ownerName, + }); err != nil { + return err + } + + return committer.Commit() +} + +// UpdateRepositoryUpdatedTime updates a repository's updated time +func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { + _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) + return err +} + +// UpdateRepositoryColsCtx updates repository's columns +func UpdateRepositoryColsCtx(ctx context.Context, repo *Repository, cols ...string) error { + _, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo) + return err +} + +// UpdateRepositoryCols updates repository's columns +func UpdateRepositoryCols(repo *Repository, cols ...string) error { + return UpdateRepositoryColsCtx(db.DefaultContext, repo, cols...) +} + +// ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error. +type ErrReachLimitOfRepo struct { + Limit int +} + +// IsErrReachLimitOfRepo checks if an error is a ErrReachLimitOfRepo. +func IsErrReachLimitOfRepo(err error) bool { + _, ok := err.(ErrReachLimitOfRepo) + return ok +} + +func (err ErrReachLimitOfRepo) Error() string { + return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit) +} + +// ErrRepoAlreadyExist represents a "RepoAlreadyExist" kind of error. +type ErrRepoAlreadyExist struct { + Uname string + Name string +} + +// IsErrRepoAlreadyExist checks if an error is a ErrRepoAlreadyExist. +func IsErrRepoAlreadyExist(err error) bool { + _, ok := err.(ErrRepoAlreadyExist) + return ok +} + +func (err ErrRepoAlreadyExist) Error() string { + return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name) +} + +// ErrRepoFilesAlreadyExist represents a "RepoFilesAlreadyExist" kind of error. +type ErrRepoFilesAlreadyExist struct { + Uname string + Name string +} + +// IsErrRepoFilesAlreadyExist checks if an error is a ErrRepoAlreadyExist. +func IsErrRepoFilesAlreadyExist(err error) bool { + _, ok := err.(ErrRepoFilesAlreadyExist) + return ok +} + +func (err ErrRepoFilesAlreadyExist) Error() string { + return fmt.Sprintf("repository files already exist [uname: %s, name: %s]", err.Uname, err.Name) +} + +// CheckCreateRepository check if could created a repository +func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdopt bool) error { + if !doer.CanCreateRepo() { + return ErrReachLimitOfRepo{u.MaxRepoCreation} + } + + if err := IsUsableRepoName(name); err != nil { + return err + } + + has, err := IsRepositoryExist(u, name) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{u.Name, name} + } + + repoPath := RepoPath(u.Name, name) + isExist, err := util.IsExist(repoPath) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repoPath, err) + return err + } + if !overwriteOrAdopt && isExist { + return ErrRepoFilesAlreadyExist{u.Name, name} + } + return nil +} + +// ChangeRepositoryName changes all corresponding setting from old repository name to new one. +func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName string) (err error) { + oldRepoName := repo.Name + newRepoName = strings.ToLower(newRepoName) + if err = IsUsableRepoName(newRepoName); err != nil { + return err + } + + if err := repo.GetOwner(db.DefaultContext); err != nil { + return err + } + + has, err := IsRepositoryExist(repo.Owner, newRepoName) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{repo.Owner.Name, newRepoName} + } + + newRepoPath := RepoPath(repo.Owner.Name, newRepoName) + if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil { + return fmt.Errorf("rename repository directory: %v", err) + } + + wikiPath := repo.WikiPath() + isExist, err := util.IsExist(wikiPath) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) + return err + } + if isExist { + if err = util.Rename(wikiPath, WikiPath(repo.Owner.Name, newRepoName)); err != nil { + return fmt.Errorf("rename repository wiki: %v", err) + } + } + + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if err := NewRedirect(ctx, repo.Owner.ID, repo.ID, oldRepoName, newRepoName); err != nil { + return err + } + + return committer.Commit() +} diff --git a/models/repo/watch.go b/models/repo/watch.go new file mode 100644 index 0000000000..8e54f0970d --- /dev/null +++ b/models/repo/watch.go @@ -0,0 +1,196 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" +) + +// WatchMode specifies what kind of watch the user has on a repository +type WatchMode int8 + +const ( + // WatchModeNone don't watch + WatchModeNone WatchMode = iota // 0 + // WatchModeNormal watch repository (from other sources) + WatchModeNormal // 1 + // WatchModeDont explicit don't auto-watch + WatchModeDont // 2 + // WatchModeAuto watch repository (from AutoWatchOnChanges) + WatchModeAuto // 3 +) + +// Watch is connection request for receiving repository notification. +type Watch struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(watch)"` + RepoID int64 `xorm:"UNIQUE(watch)"` + Mode WatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` +} + +func init() { + db.RegisterModel(new(Watch)) +} + +// GetWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found +func GetWatch(ctx context.Context, userID, repoID int64) (Watch, error) { + watch := Watch{UserID: userID, RepoID: repoID} + has, err := db.GetEngine(ctx).Get(&watch) + if err != nil { + return watch, err + } + if !has { + watch.Mode = WatchModeNone + } + return watch, nil +} + +// IsWatchMode Decodes watchability of WatchMode +func IsWatchMode(mode WatchMode) bool { + return mode != WatchModeNone && mode != WatchModeDont +} + +// IsWatching checks if user has watched given repository. +func IsWatching(userID, repoID int64) bool { + watch, err := GetWatch(db.DefaultContext, userID, repoID) + return err == nil && IsWatchMode(watch.Mode) +} + +func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) { + if watch.Mode == mode { + return nil + } + if mode == WatchModeAuto && (watch.Mode == WatchModeDont || IsWatchMode(watch.Mode)) { + // Don't auto watch if already watching or deliberately not watching + return nil + } + + hadrec := watch.Mode != WatchModeNone + needsrec := mode != WatchModeNone + repodiff := 0 + + if IsWatchMode(mode) && !IsWatchMode(watch.Mode) { + repodiff = 1 + } else if !IsWatchMode(mode) && IsWatchMode(watch.Mode) { + repodiff = -1 + } + + watch.Mode = mode + + e := db.GetEngine(ctx) + + if !hadrec && needsrec { + watch.Mode = mode + if _, err = e.Insert(watch); err != nil { + return err + } + } else if needsrec { + watch.Mode = mode + if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil { + return err + } + } else if _, err = e.Delete(Watch{ID: watch.ID}); err != nil { + return err + } + if repodiff != 0 { + _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID) + } + return err +} + +// WatchRepoMode watch repository in specific mode. +func WatchRepoMode(userID, repoID int64, mode WatchMode) (err error) { + var watch Watch + if watch, err = GetWatch(db.DefaultContext, userID, repoID); err != nil { + return err + } + return watchRepoMode(db.DefaultContext, watch, mode) +} + +// WatchRepoCtx watch or unwatch repository. +func WatchRepoCtx(ctx context.Context, userID, repoID int64, doWatch bool) (err error) { + var watch Watch + if watch, err = GetWatch(ctx, userID, repoID); err != nil { + return err + } + if !doWatch && watch.Mode == WatchModeAuto { + err = watchRepoMode(ctx, watch, WatchModeDont) + } else if !doWatch { + err = watchRepoMode(ctx, watch, WatchModeNone) + } else { + err = watchRepoMode(ctx, watch, WatchModeNormal) + } + return err +} + +// WatchRepo watch or unwatch repository. +func WatchRepo(userID, repoID int64, watch bool) (err error) { + return WatchRepoCtx(db.DefaultContext, userID, repoID, watch) +} + +// GetWatchers returns all watchers of given repository. +func GetWatchers(ctx context.Context, repoID int64) ([]*Watch, error) { + watches := make([]*Watch, 0, 10) + return watches, db.GetEngine(ctx).Where("`watch`.repo_id=?", repoID). + And("`watch`.mode<>?", WatchModeDont). + And("`user`.is_active=?", true). + And("`user`.prohibit_login=?", false). + Join("INNER", "`user`", "`user`.id = `watch`.user_id"). + Find(&watches) +} + +// GetRepoWatchersIDs returns IDs of watchers for a given repo ID +// but avoids joining with `user` for performance reasons +// User permissions must be verified elsewhere if required +func GetRepoWatchersIDs(ctx context.Context, repoID int64) ([]int64, error) { + ids := make([]int64, 0, 64) + return ids, db.GetEngine(ctx).Table("watch"). + Where("watch.repo_id=?", repoID). + And("watch.mode<>?", WatchModeDont). + Select("user_id"). + Find(&ids) +} + +// GetRepoWatchers returns range of users watching given repository. +func GetRepoWatchers(repoID int64, opts db.ListOptions) ([]*user_model.User, error) { + sess := db.GetEngine(db.DefaultContext).Where("watch.repo_id=?", repoID). + Join("LEFT", "watch", "`user`.id=`watch`.user_id"). + And("`watch`.mode<>?", WatchModeDont) + if opts.Page > 0 { + sess = db.SetSessionPagination(sess, &opts) + users := make([]*user_model.User, 0, opts.PageSize) + + return users, sess.Find(&users) + } + + users := make([]*user_model.User, 0, 8) + return users, sess.Find(&users) +} + +func watchIfAuto(ctx context.Context, userID, repoID int64, isWrite bool) error { + if !isWrite || !setting.Service.AutoWatchOnChanges { + return nil + } + watch, err := GetWatch(ctx, userID, repoID) + if err != nil { + return err + } + if watch.Mode != WatchModeNone { + return nil + } + return watchRepoMode(ctx, watch, WatchModeAuto) +} + +// WatchIfAuto subscribes to repo if AutoWatchOnChanges is set +func WatchIfAuto(userID, repoID int64, isWrite bool) error { + return watchIfAuto(db.DefaultContext, userID, repoID, isWrite) +} diff --git a/models/repo_watch_test.go b/models/repo/watch_test.go index 1a60521396..2ff3ced2dc 100644 --- a/models/repo_watch_test.go +++ b/models/repo/watch_test.go @@ -2,13 +2,12 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package repo import ( "testing" "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" @@ -27,25 +26,11 @@ func TestIsWatching(t *testing.T) { assert.False(t, IsWatching(unittest.NonexistentID, unittest.NonexistentID)) } -func TestWatchRepo(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - const repoID = 3 - const userID = 2 - - assert.NoError(t, WatchRepo(userID, repoID, true)) - unittest.AssertExistsAndLoadBean(t, &Watch{RepoID: repoID, UserID: userID}) - unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}) - - assert.NoError(t, WatchRepo(userID, repoID, false)) - unittest.AssertNotExistsBean(t, &Watch{RepoID: repoID, UserID: userID}) - unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}) -} - func TestGetWatchers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) - watches, err := GetWatchers(repo.ID) + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + watches, err := GetWatchers(db.DefaultContext, repo.ID) assert.NoError(t, err) // One watchers are inactive, thus minus 1 assert.Len(t, watches, repo.NumWatches-1) @@ -53,7 +38,7 @@ func TestGetWatchers(t *testing.T) { assert.EqualValues(t, repo.ID, watch.RepoID) } - watches, err = GetWatchers(unittest.NonexistentID) + watches, err = GetWatchers(db.DefaultContext, unittest.NonexistentID) assert.NoError(t, err) assert.Len(t, watches, 0) } @@ -61,7 +46,7 @@ func TestGetWatchers(t *testing.T) { func TestRepository_GetWatchers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) watchers, err := GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) @@ -69,53 +54,16 @@ func TestRepository_GetWatchers(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID}) } - repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}).(*repo_model.Repository) + repo = unittest.AssertExistsAndLoadBean(t, &Repository{ID: 9}).(*Repository) watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, 0) } -func TestNotifyWatchers(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - action := &Action{ - ActUserID: 8, - RepoID: 1, - OpType: ActionStarRepo, - } - assert.NoError(t, NotifyWatchers(action)) - - // One watchers are inactive, thus action is only created for user 8, 1, 4, 11 - unittest.AssertExistsAndLoadBean(t, &Action{ - ActUserID: action.ActUserID, - UserID: 8, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &Action{ - ActUserID: action.ActUserID, - UserID: 1, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &Action{ - ActUserID: action.ActUserID, - UserID: 4, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &Action{ - ActUserID: action.ActUserID, - UserID: 11, - RepoID: action.RepoID, - OpType: action.OpType, - }) -} - func TestWatchIfAuto(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) watchers, err := GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) @@ -174,18 +122,18 @@ func TestWatchRepoMode(t *testing.T) { unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) - assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeAuto)) + assert.NoError(t, WatchRepoMode(12, 1, WatchModeAuto)) unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeAuto}, 1) + unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: WatchModeAuto}, 1) - assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNormal)) + assert.NoError(t, WatchRepoMode(12, 1, WatchModeNormal)) unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeNormal}, 1) + unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: WatchModeNormal}, 1) - assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeDont)) + assert.NoError(t, WatchRepoMode(12, 1, WatchModeDont)) unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeDont}, 1) + unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: WatchModeDont}, 1) - assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNone)) + assert.NoError(t, WatchRepoMode(12, 1, WatchModeNone)) unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) } diff --git a/models/repo_archiver.go b/models/repo_archiver.go deleted file mode 100644 index 1ac05da043..0000000000 --- a/models/repo_archiver.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" -) - -// LoadArchiverRepo loads repository -func LoadArchiverRepo(archiver *repo_model.RepoArchiver) (*repo_model.Repository, error) { - var repo repo_model.Repository - has, err := db.GetEngine(db.DefaultContext).ID(archiver.RepoID).Get(&repo) - if err != nil { - return nil, err - } - if !has { - return nil, repo_model.ErrRepoNotExist{ - ID: archiver.RepoID, - } - } - return &repo, nil -} diff --git a/models/repo_avatar.go b/models/repo_avatar.go deleted file mode 100644 index 27af911a7e..0000000000 --- a/models/repo_avatar.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "context" - "crypto/md5" - "fmt" - "image/png" - "io" - "strconv" - - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/avatar" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/storage" -) - -// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories -func RemoveRandomAvatars(ctx context.Context) error { - return db.GetEngine(db.DefaultContext). - Where("id > 0").BufferSize(setting.Database.IterateBufferSize). - Iterate(new(repo_model.Repository), - func(idx int, bean interface{}) error { - repository := bean.(*repo_model.Repository) - select { - case <-ctx.Done(): - return db.ErrCancelledf("before random avatars removed for %s", repository.FullName()) - default: - } - stringifiedID := strconv.FormatInt(repository.ID, 10) - if repository.Avatar == stringifiedID { - return DeleteRepoAvatar(repository) - } - return nil - }) -} - -// UploadRepoAvatar saves custom avatar for repository. -// FIXME: split uploads to different subdirs in case we have massive number of repos. -func UploadRepoAvatar(repo *repo_model.Repository, data []byte) error { - m, err := avatar.Prepare(data) - if err != nil { - return err - } - - newAvatar := fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data)) - if repo.Avatar == newAvatar { // upload the same picture - return nil - } - - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - oldAvatarPath := repo.CustomAvatarRelativePath() - - // Users can upload the same image to other repo - prefix it with ID - // Then repo will be removed - only it avatar file will be removed - repo.Avatar = newAvatar - if _, err := db.GetEngine(ctx).ID(repo.ID).Cols("avatar").Update(repo); err != nil { - return fmt.Errorf("UploadAvatar: Update repository avatar: %v", err) - } - - if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { - if err := png.Encode(w, *m); err != nil { - log.Error("Encode: %v", err) - } - return err - }); err != nil { - return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %v", repo.RepoPath(), newAvatar, err) - } - - if len(oldAvatarPath) > 0 { - if err := storage.RepoAvatars.Delete(oldAvatarPath); err != nil { - return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %v", oldAvatarPath, err) - } - } - - return committer.Commit() -} - -// DeleteRepoAvatar deletes the repos's custom avatar. -func DeleteRepoAvatar(repo *repo_model.Repository) error { - // Avatar not exists - if len(repo.Avatar) == 0 { - return nil - } - - avatarPath := repo.CustomAvatarRelativePath() - log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath) - - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - repo.Avatar = "" - if _, err := db.GetEngine(ctx).ID(repo.ID).Cols("avatar").Update(repo); err != nil { - return fmt.Errorf("DeleteAvatar: Update repository avatar: %v", err) - } - - if err := storage.RepoAvatars.Delete(avatarPath); err != nil { - return fmt.Errorf("DeleteAvatar: Failed to remove %s: %v", avatarPath, err) - } - - return committer.Commit() -} diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index ab6a3bafbe..3aca1023e6 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -207,15 +207,13 @@ func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) { } defer committer.Close() - sess := db.GetEngine(ctx) - - if has, err := sess.Delete(collaboration); err != nil || has == 0 { + if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil || has == 0 { return err } else if err = recalculateAccesses(ctx, repo); err != nil { return err } - if err = watchRepo(sess, uid, repo.ID, false); err != nil { + if err = repo_model.WatchRepoCtx(ctx, uid, repo.ID, false); err != nil { return err } @@ -253,13 +251,12 @@ func reconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int if has, err := hasAccess(ctx, uid, repo); err != nil || has { return err } - e := db.GetEngine(ctx) - if err := watchRepo(e, uid, repo.ID, false); err != nil { + if err := repo_model.WatchRepoCtx(ctx, uid, repo.ID, false); err != nil { return err } // Remove all IssueWatches a user has subscribed to in the repository - return removeIssueWatchersByRepoID(e, uid, repo.ID) + return removeIssueWatchersByRepoID(db.GetEngine(ctx), uid, repo.ID) } func getRepoTeams(e db.Engine, repo *repo_model.Repository) (teams []*Team, err error) { diff --git a/models/repo_generate.go b/models/repo_generate.go index 6b5b8e5bc1..fc749f1120 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -8,15 +8,12 @@ import ( "bufio" "bytes" "context" - "strconv" "strings" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/webhook" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/storage" "github.com/gobwas/glob" ) @@ -70,49 +67,6 @@ func (gt GiteaTemplate) Globs() []glob.Glob { return gt.globs } -// GenerateTopics generates topics from a template repository -func GenerateTopics(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { - for _, topic := range templateRepo.Topics { - if _, err := addTopicByNameToRepo(db.GetEngine(ctx), generateRepo.ID, topic); err != nil { - return err - } - } - return nil -} - -// GenerateGitHooks generates git hooks from a template repository -func GenerateGitHooks(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { - generateGitRepo, err := git.OpenRepository(generateRepo.RepoPath()) - if err != nil { - return err - } - defer generateGitRepo.Close() - - templateGitRepo, err := git.OpenRepository(templateRepo.RepoPath()) - if err != nil { - return err - } - defer templateGitRepo.Close() - - templateHooks, err := templateGitRepo.Hooks() - if err != nil { - return err - } - - for _, templateHook := range templateHooks { - generateHook, err := generateGitRepo.GetHook(templateHook.Name()) - if err != nil { - return err - } - - generateHook.Content = templateHook.Content - if err := generateHook.Update(); err != nil { - return err - } - } - return nil -} - // GenerateWebhooks generates webhooks from a template repository func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { templateWebhooks, err := webhook.ListWebhooksByOpts(&webhook.ListWebhookOptions{RepoID: templateRepo.ID}) @@ -141,16 +95,6 @@ func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_mode return nil } -// GenerateAvatar generates the avatar from a template repository -func GenerateAvatar(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { - generateRepo.Avatar = strings.Replace(templateRepo.Avatar, strconv.FormatInt(templateRepo.ID, 10), strconv.FormatInt(generateRepo.ID, 10), 1) - if _, err := storage.Copy(storage.RepoAvatars, generateRepo.CustomAvatarRelativePath(), storage.RepoAvatars, templateRepo.CustomAvatarRelativePath()); err != nil { - return err - } - - return updateRepositoryCols(db.GetEngine(ctx), generateRepo, "avatar") -} - // GenerateIssueLabels generates issue labels from a template repository func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { templateLabels, err := getLabelsByRepoID(db.GetEngine(ctx), templateRepo.ID, "", db.ListOptions{}) diff --git a/models/repo_permission.go b/models/repo_permission.go index 3dc8db92b8..45878c8ba4 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -419,3 +419,21 @@ func FilterOutRepoIdsWithoutUnitAccess(u *user_model.User, repoIDs []int64, unit } return repoIDs[:i], nil } + +// GetRepoReaders returns all users that have explicit read access or higher to the repository. +func GetRepoReaders(repo *repo_model.Repository) (_ []*user_model.User, err error) { + return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeRead) +} + +// GetRepoWriters returns all users that have write access to the repository. +func GetRepoWriters(repo *repo_model.Repository) (_ []*user_model.User, err error) { + return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeWrite) +} + +// IsRepoReader returns true if user has explicit read access or higher to the repository. +func IsRepoReader(repo *repo_model.Repository, userID int64) (bool, error) { + if repo.OwnerID == userID { + return true, nil + } + return db.GetEngine(db.DefaultContext).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{}) +} diff --git a/models/repo_redirect.go b/models/repo_redirect.go deleted file mode 100644 index 18422f9d18..0000000000 --- a/models/repo_redirect.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "strings" - - "code.gitea.io/gitea/models/db" -) - -// RepoRedirect represents that a repo name should be redirected to another -type RepoRedirect struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"UNIQUE(s)"` - LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` - RedirectRepoID int64 // repoID to redirect to -} - -func init() { - db.RegisterModel(new(RepoRedirect)) -} - -// LookupRepoRedirect look up if a repository has a redirect name -func LookupRepoRedirect(ownerID int64, repoName string) (int64, error) { - repoName = strings.ToLower(repoName) - redirect := &RepoRedirect{OwnerID: ownerID, LowerName: repoName} - if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil { - return 0, err - } else if !has { - return 0, ErrRepoRedirectNotExist{OwnerID: ownerID, RepoName: repoName} - } - return redirect.RedirectRepoID, nil -} - -// newRepoRedirect create a new repo redirect -func newRepoRedirect(e db.Engine, ownerID, repoID int64, oldRepoName, newRepoName string) error { - oldRepoName = strings.ToLower(oldRepoName) - newRepoName = strings.ToLower(newRepoName) - - if err := deleteRepoRedirect(e, ownerID, newRepoName); err != nil { - return err - } - - if _, err := e.Insert(&RepoRedirect{ - OwnerID: ownerID, - LowerName: oldRepoName, - RedirectRepoID: repoID, - }); err != nil { - return err - } - return nil -} - -// deleteRepoRedirect delete any redirect from the specified repo name to -// anything else -func deleteRepoRedirect(e db.Engine, ownerID int64, repoName string) error { - repoName = strings.ToLower(repoName) - _, err := e.Delete(&RepoRedirect{OwnerID: ownerID, LowerName: repoName}) - return err -} diff --git a/models/repo_redirect_test.go b/models/repo_redirect_test.go deleted file mode 100644 index c6d471448e..0000000000 --- a/models/repo_redirect_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "testing" - - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" - - "github.com/stretchr/testify/assert" -) - -func TestLookupRepoRedirect(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repoID, err := LookupRepoRedirect(2, "oldrepo1") - assert.NoError(t, err) - assert.EqualValues(t, 1, repoID) - - _, err = LookupRepoRedirect(unittest.NonexistentID, "doesnotexist") - assert.True(t, IsErrRepoRedirectNotExist(err)) -} - -func TestNewRepoRedirect(t *testing.T) { - // redirect to a completely new name - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) - assert.NoError(t, newRepoRedirect(db.GetEngine(db.DefaultContext), repo.OwnerID, repo.ID, repo.Name, "newreponame")) - - unittest.AssertExistsAndLoadBean(t, &RepoRedirect{ - OwnerID: repo.OwnerID, - LowerName: repo.LowerName, - RedirectRepoID: repo.ID, - }) - unittest.AssertExistsAndLoadBean(t, &RepoRedirect{ - OwnerID: repo.OwnerID, - LowerName: "oldrepo1", - RedirectRepoID: repo.ID, - }) -} - -func TestNewRepoRedirect2(t *testing.T) { - // redirect to previously used name - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) - assert.NoError(t, newRepoRedirect(db.GetEngine(db.DefaultContext), repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) - - unittest.AssertExistsAndLoadBean(t, &RepoRedirect{ - OwnerID: repo.OwnerID, - LowerName: repo.LowerName, - RedirectRepoID: repo.ID, - }) - unittest.AssertNotExistsBean(t, &RepoRedirect{ - OwnerID: repo.OwnerID, - LowerName: "oldrepo1", - RedirectRepoID: repo.ID, - }) -} - -func TestNewRepoRedirect3(t *testing.T) { - // redirect for a previously-unredirected repo - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) - assert.NoError(t, newRepoRedirect(db.GetEngine(db.DefaultContext), repo.OwnerID, repo.ID, repo.Name, "newreponame")) - - unittest.AssertExistsAndLoadBean(t, &RepoRedirect{ - OwnerID: repo.OwnerID, - LowerName: repo.LowerName, - RedirectRepoID: repo.ID, - }) -} diff --git a/models/repo_test.go b/models/repo_test.go index 72a2977343..45e016a8fc 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -5,11 +5,6 @@ package models import ( - "bytes" - "crypto/md5" - "fmt" - "image" - "image/png" "testing" "code.gitea.io/gitea/models/db" @@ -22,6 +17,20 @@ import ( "github.com/stretchr/testify/assert" ) +func TestWatchRepo(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + const repoID = 3 + const userID = 2 + + assert.NoError(t, repo_model.WatchRepo(userID, repoID, true)) + unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{RepoID: repoID, UserID: userID}) + unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}) + + assert.NoError(t, repo_model.WatchRepo(userID, repoID, false)) + unittest.AssertNotExistsBean(t, &repo_model.Watch{RepoID: repoID, UserID: userID}) + unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}) +} + func TestMetas(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) @@ -90,77 +99,6 @@ func TestUpdateRepositoryVisibilityChanged(t *testing.T) { assert.True(t, act.IsPrivate) } -func TestGetUserFork(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - // User13 has repo 11 forked from repo10 - repo, err := repo_model.GetRepositoryByID(10) - assert.NoError(t, err) - assert.NotNil(t, repo) - repo, err = GetUserFork(repo.ID, 13) - assert.NoError(t, err) - assert.NotNil(t, repo) - - repo, err = repo_model.GetRepositoryByID(9) - assert.NoError(t, err) - assert.NotNil(t, repo) - repo, err = GetUserFork(repo.ID, 13) - assert.NoError(t, err) - assert.Nil(t, repo) -} - -func TestRepoAPIURL(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}).(*repo_model.Repository) - - assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL()) -} - -func TestUploadAvatar(t *testing.T) { - // Generate image - myImage := image.NewRGBA(image.Rect(0, 0, 1, 1)) - var buff bytes.Buffer - png.Encode(&buff, myImage) - - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}).(*repo_model.Repository) - - err := UploadRepoAvatar(repo, buff.Bytes()) - assert.NoError(t, err) - assert.Equal(t, fmt.Sprintf("%d-%x", 10, md5.Sum(buff.Bytes())), repo.Avatar) -} - -func TestUploadBigAvatar(t *testing.T) { - // Generate BIG image - myImage := image.NewRGBA(image.Rect(0, 0, 5000, 1)) - var buff bytes.Buffer - png.Encode(&buff, myImage) - - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}).(*repo_model.Repository) - - err := UploadRepoAvatar(repo, buff.Bytes()) - assert.Error(t, err) -} - -func TestDeleteAvatar(t *testing.T) { - // Generate image - myImage := image.NewRGBA(image.Rect(0, 0, 1, 1)) - var buff bytes.Buffer - png.Encode(&buff, myImage) - - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}).(*repo_model.Repository) - - err := UploadRepoAvatar(repo, buff.Bytes()) - assert.NoError(t, err) - - err = DeleteRepoAvatar(repo) - assert.NoError(t, err) - - assert.Equal(t, "", repo.Avatar) -} - func TestDoctorUserStarNum(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/models/repo_transfer.go b/models/repo_transfer.go index 398ed0755a..f7d5e20990 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -5,6 +5,7 @@ package models import ( + "context" "fmt" "os" @@ -112,8 +113,8 @@ func GetPendingRepositoryTransfer(repo *repo_model.Repository) (*RepoTransfer, e return transfer, nil } -func deleteRepositoryTransfer(e db.Engine, repoID int64) error { - _, err := e.Where("repo_id = ?", repoID).Delete(&RepoTransfer{}) +func deleteRepositoryTransfer(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(&RepoTransfer{}) return err } @@ -125,14 +126,13 @@ func CancelRepositoryTransfer(repo *repo_model.Repository) error { return err } defer committer.Close() - sess := db.GetEngine(ctx) repo.Status = repo_model.RepositoryReady - if err := updateRepositoryCols(sess, repo, "status"); err != nil { + if err := repo_model.UpdateRepositoryColsCtx(ctx, repo, "status"); err != nil { return err } - if err := deleteRepositoryTransfer(sess, repo.ID); err != nil { + if err := deleteRepositoryTransfer(ctx, repo.ID); err != nil { return err } @@ -158,7 +158,6 @@ func CreatePendingRepositoryTransfer(doer, newOwner *user_model.User, repoID int return err } defer committer.Close() - sess := db.GetEngine(ctx) repo, err := repo_model.GetRepositoryByIDCtx(ctx, repoID) if err != nil { @@ -171,7 +170,7 @@ func CreatePendingRepositoryTransfer(doer, newOwner *user_model.User, repoID int } repo.Status = repo_model.RepositoryPendingTransfer - if err := updateRepositoryCols(sess, repo, "status"); err != nil { + if err := repo_model.UpdateRepositoryColsCtx(ctx, repo, "status"); err != nil { return err } @@ -179,7 +178,10 @@ func CreatePendingRepositoryTransfer(doer, newOwner *user_model.User, repoID int if has, err := repo_model.IsRepositoryExistCtx(ctx, newOwner, repo.Name); err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{newOwner.LowerName, repo.Name} + return repo_model.ErrRepoAlreadyExist{ + Uname: newOwner.LowerName, + Name: repo.Name, + } } transfer := &RepoTransfer{ @@ -256,7 +258,10 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo if has, err := repo_model.IsRepositoryExistCtx(ctx, newOwner, repo.Name); err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{newOwnerName, repo.Name} + return repo_model.ErrRepoAlreadyExist{ + Uname: newOwnerName, + Name: repo.Name, + } } oldOwner := repo.Owner @@ -336,13 +341,13 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo return fmt.Errorf("decrease old owner repository count: %v", err) } - if err := watchRepo(sess, doer.ID, repo.ID, true); err != nil { + if err := repo_model.WatchRepoCtx(ctx, doer.ID, repo.ID, true); err != nil { return fmt.Errorf("watchRepo: %v", err) } // Remove watch for organization. if oldOwner.IsOrganization() { - if err := watchRepo(sess, oldOwner.ID, repo.ID, false); err != nil { + if err := repo_model.WatchRepoCtx(ctx, oldOwner.ID, repo.ID, false); err != nil { return fmt.Errorf("watchRepo [false]: %v", err) } } @@ -399,21 +404,21 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo wikiRenamed = true } - if err := deleteRepositoryTransfer(sess, repo.ID); err != nil { + if err := deleteRepositoryTransfer(ctx, repo.ID); err != nil { return fmt.Errorf("deleteRepositoryTransfer: %v", err) } repo.Status = repo_model.RepositoryReady - if err := updateRepositoryCols(sess, repo, "status"); err != nil { + if err := repo_model.UpdateRepositoryColsCtx(ctx, repo, "status"); err != nil { return err } // If there was previously a redirect at this location, remove it. - if err := deleteRepoRedirect(sess, newOwner.ID, repo.Name); err != nil { + if err := repo_model.DeleteRedirect(ctx, newOwner.ID, repo.Name); err != nil { return fmt.Errorf("delete repo redirect: %v", err) } - if err := newRepoRedirect(sess, oldOwner.ID, repo.ID, repo.Name, repo.Name); err != nil { - return fmt.Errorf("newRepoRedirect: %v", err) + if err := repo_model.NewRedirect(ctx, oldOwner.ID, repo.ID, repo.Name, repo.Name); err != nil { + return fmt.Errorf("repo_model.NewRedirect: %v", err) } return committer.Commit() diff --git a/models/repo_watch.go b/models/repo_watch.go deleted file mode 100644 index 6ae478d65f..0000000000 --- a/models/repo_watch.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "context" - "fmt" - - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unit" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" -) - -// RepoWatchMode specifies what kind of watch the user has on a repository -type RepoWatchMode int8 - -const ( - // RepoWatchModeNone don't watch - RepoWatchModeNone RepoWatchMode = iota // 0 - // RepoWatchModeNormal watch repository (from other sources) - RepoWatchModeNormal // 1 - // RepoWatchModeDont explicit don't auto-watch - RepoWatchModeDont // 2 - // RepoWatchModeAuto watch repository (from AutoWatchOnChanges) - RepoWatchModeAuto // 3 -) - -// Watch is connection request for receiving repository notification. -type Watch struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(watch)"` - RepoID int64 `xorm:"UNIQUE(watch)"` - Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` -} - -func init() { - db.RegisterModel(new(Watch)) -} - -// getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found -func getWatch(e db.Engine, userID, repoID int64) (Watch, error) { - watch := Watch{UserID: userID, RepoID: repoID} - has, err := e.Get(&watch) - if err != nil { - return watch, err - } - if !has { - watch.Mode = RepoWatchModeNone - } - return watch, nil -} - -// Decodes watchability of RepoWatchMode -func isWatchMode(mode RepoWatchMode) bool { - return mode != RepoWatchModeNone && mode != RepoWatchModeDont -} - -// IsWatching checks if user has watched given repository. -func IsWatching(userID, repoID int64) bool { - watch, err := getWatch(db.GetEngine(db.DefaultContext), userID, repoID) - return err == nil && isWatchMode(watch.Mode) -} - -func watchRepoMode(e db.Engine, watch Watch, mode RepoWatchMode) (err error) { - if watch.Mode == mode { - return nil - } - if mode == RepoWatchModeAuto && (watch.Mode == RepoWatchModeDont || isWatchMode(watch.Mode)) { - // Don't auto watch if already watching or deliberately not watching - return nil - } - - hadrec := watch.Mode != RepoWatchModeNone - needsrec := mode != RepoWatchModeNone - repodiff := 0 - - if isWatchMode(mode) && !isWatchMode(watch.Mode) { - repodiff = 1 - } else if !isWatchMode(mode) && isWatchMode(watch.Mode) { - repodiff = -1 - } - - watch.Mode = mode - - if !hadrec && needsrec { - watch.Mode = mode - if _, err = e.Insert(watch); err != nil { - return err - } - } else if needsrec { - watch.Mode = mode - if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil { - return err - } - } else if _, err = e.Delete(Watch{ID: watch.ID}); err != nil { - return err - } - if repodiff != 0 { - _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID) - } - return err -} - -// WatchRepoMode watch repository in specific mode. -func WatchRepoMode(userID, repoID int64, mode RepoWatchMode) (err error) { - var watch Watch - if watch, err = getWatch(db.GetEngine(db.DefaultContext), userID, repoID); err != nil { - return err - } - return watchRepoMode(db.GetEngine(db.DefaultContext), watch, mode) -} - -func watchRepo(e db.Engine, userID, repoID int64, doWatch bool) (err error) { - var watch Watch - if watch, err = getWatch(e, userID, repoID); err != nil { - return err - } - if !doWatch && watch.Mode == RepoWatchModeAuto { - err = watchRepoMode(e, watch, RepoWatchModeDont) - } else if !doWatch { - err = watchRepoMode(e, watch, RepoWatchModeNone) - } else { - err = watchRepoMode(e, watch, RepoWatchModeNormal) - } - return err -} - -// WatchRepo watch or unwatch repository. -func WatchRepo(userID, repoID int64, watch bool) (err error) { - return watchRepo(db.GetEngine(db.DefaultContext), userID, repoID, watch) -} - -func getWatchers(e db.Engine, repoID int64) ([]*Watch, error) { - watches := make([]*Watch, 0, 10) - return watches, e.Where("`watch`.repo_id=?", repoID). - And("`watch`.mode<>?", RepoWatchModeDont). - And("`user`.is_active=?", true). - And("`user`.prohibit_login=?", false). - Join("INNER", "`user`", "`user`.id = `watch`.user_id"). - Find(&watches) -} - -// GetWatchers returns all watchers of given repository. -func GetWatchers(repoID int64) ([]*Watch, error) { - return getWatchers(db.GetEngine(db.DefaultContext), repoID) -} - -// GetRepoWatchersIDs returns IDs of watchers for a given repo ID -// but avoids joining with `user` for performance reasons -// User permissions must be verified elsewhere if required -func GetRepoWatchersIDs(repoID int64) ([]int64, error) { - return getRepoWatchersIDs(db.GetEngine(db.DefaultContext), repoID) -} - -func getRepoWatchersIDs(e db.Engine, repoID int64) ([]int64, error) { - ids := make([]int64, 0, 64) - return ids, e.Table("watch"). - Where("watch.repo_id=?", repoID). - And("watch.mode<>?", RepoWatchModeDont). - Select("user_id"). - Find(&ids) -} - -// GetRepoWatchers returns range of users watching given repository. -func GetRepoWatchers(repoID int64, opts db.ListOptions) ([]*user_model.User, error) { - sess := db.GetEngine(db.DefaultContext).Where("watch.repo_id=?", repoID). - Join("LEFT", "watch", "`user`.id=`watch`.user_id"). - And("`watch`.mode<>?", RepoWatchModeDont) - if opts.Page > 0 { - sess = db.SetSessionPagination(sess, &opts) - users := make([]*user_model.User, 0, opts.PageSize) - - return users, sess.Find(&users) - } - - users := make([]*user_model.User, 0, 8) - return users, sess.Find(&users) -} - -func notifyWatchers(ctx context.Context, actions ...*Action) error { - var watchers []*Watch - var repo *repo_model.Repository - var err error - var permCode []bool - var permIssue []bool - var permPR []bool - - e := db.GetEngine(ctx) - - for _, act := range actions { - repoChanged := repo == nil || repo.ID != act.RepoID - - if repoChanged { - // Add feeds for user self and all watchers. - watchers, err = getWatchers(e, act.RepoID) - if err != nil { - return fmt.Errorf("get watchers: %v", err) - } - } - - // Add feed for actioner. - act.UserID = act.ActUserID - if _, err = e.InsertOne(act); err != nil { - return fmt.Errorf("insert new actioner: %v", err) - } - - if repoChanged { - act.loadRepo() - repo = act.Repo - - // check repo owner exist. - if err := act.Repo.GetOwner(ctx); err != nil { - return fmt.Errorf("can't get repo owner: %v", err) - } - } else if act.Repo == nil { - act.Repo = repo - } - - // Add feed for organization - if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID { - act.ID = 0 - act.UserID = act.Repo.Owner.ID - if _, err = e.InsertOne(act); err != nil { - return fmt.Errorf("insert new actioner: %v", err) - } - } - - if repoChanged { - permCode = make([]bool, len(watchers)) - permIssue = make([]bool, len(watchers)) - permPR = make([]bool, len(watchers)) - for i, watcher := range watchers { - user, err := user_model.GetUserByIDEngine(e, watcher.UserID) - if err != nil { - permCode[i] = false - permIssue[i] = false - permPR[i] = false - continue - } - perm, err := getUserRepoPermission(ctx, repo, user) - if err != nil { - permCode[i] = false - permIssue[i] = false - permPR[i] = false - continue - } - permCode[i] = perm.CanRead(unit.TypeCode) - permIssue[i] = perm.CanRead(unit.TypeIssues) - permPR[i] = perm.CanRead(unit.TypePullRequests) - } - } - - for i, watcher := range watchers { - if act.ActUserID == watcher.UserID { - continue - } - act.ID = 0 - act.UserID = watcher.UserID - act.Repo.Units = nil - - switch act.OpType { - case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionPublishRelease, ActionDeleteBranch: - if !permCode[i] { - continue - } - case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue: - if !permIssue[i] { - continue - } - case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest: - if !permPR[i] { - continue - } - } - - if _, err = e.InsertOne(act); err != nil { - return fmt.Errorf("insert new action: %v", err) - } - } - } - return nil -} - -// NotifyWatchers creates batch of actions for every watcher. -func NotifyWatchers(actions ...*Action) error { - return notifyWatchers(db.DefaultContext, actions...) -} - -// NotifyWatchersActions creates batch of actions for every watcher. -func NotifyWatchersActions(acts []*Action) error { - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - for _, act := range acts { - if err := notifyWatchers(ctx, act); err != nil { - return err - } - } - return committer.Commit() -} - -func watchIfAuto(e db.Engine, userID, repoID int64, isWrite bool) error { - if !isWrite || !setting.Service.AutoWatchOnChanges { - return nil - } - watch, err := getWatch(e, userID, repoID) - if err != nil { - return err - } - if watch.Mode != RepoWatchModeNone { - return nil - } - return watchRepoMode(e, watch, RepoWatchModeAuto) -} - -// WatchIfAuto subscribes to repo if AutoWatchOnChanges is set -func WatchIfAuto(userID, repoID int64, isWrite bool) error { - return watchIfAuto(db.GetEngine(db.DefaultContext), userID, repoID, isWrite) -} diff --git a/models/statistic.go b/models/statistic.go index 175815081f..f39cdd5eb7 100644 --- a/models/statistic.go +++ b/models/statistic.go @@ -50,8 +50,8 @@ func GetStatistic() (stats Statistic) { stats.Counter.Org = CountOrganizations() stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) stats.Counter.Repo = repo_model.CountRepositories(true) - stats.Counter.Watch, _ = e.Count(new(Watch)) - stats.Counter.Star, _ = e.Count(new(Star)) + stats.Counter.Watch, _ = e.Count(new(repo_model.Watch)) + stats.Counter.Star, _ = e.Count(new(repo_model.Star)) stats.Counter.Action, _ = e.Count(new(Action)) stats.Counter.Access, _ = e.Count(new(Access)) diff --git a/models/update.go b/models/update.go deleted file mode 100644 index 14333ed985..0000000000 --- a/models/update.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "context" - "fmt" - "strings" - - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" -) - -// PushUpdateDeleteTagsContext updates a number of delete tags with context -func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repository, tags []string) error { - return pushUpdateDeleteTags(db.GetEngine(ctx), repo, tags) -} - -func pushUpdateDeleteTags(e db.Engine, repo *repo_model.Repository, tags []string) error { - if len(tags) == 0 { - return nil - } - lowerTags := make([]string, 0, len(tags)) - for _, tag := range tags { - lowerTags = append(lowerTags, strings.ToLower(tag)) - } - - if _, err := e. - Where("repo_id = ? AND is_tag = ?", repo.ID, true). - In("lower_tag_name", lowerTags). - Delete(new(Release)); err != nil { - return fmt.Errorf("Delete: %v", err) - } - - if _, err := e. - Where("repo_id = ? AND is_tag = ?", repo.ID, false). - In("lower_tag_name", lowerTags). - Cols("is_draft", "num_commits", "sha1"). - Update(&Release{ - IsDraft: true, - }); err != nil { - return fmt.Errorf("Update: %v", err) - } - - return nil -} - -// PushUpdateDeleteTag must be called for any push actions to delete tag -func PushUpdateDeleteTag(repo *repo_model.Repository, tagName string) error { - rel, err := GetRelease(repo.ID, tagName) - if err != nil { - if IsErrReleaseNotExist(err) { - return nil - } - return fmt.Errorf("GetRelease: %v", err) - } - if rel.IsTag { - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil { - return fmt.Errorf("Delete: %v", err) - } - } else { - rel.IsDraft = true - rel.NumCommits = 0 - rel.Sha1 = "" - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { - return fmt.Errorf("Update: %v", err) - } - } - - return nil -} - -// SaveOrUpdateTag must be called for any push actions to add tag -func SaveOrUpdateTag(repo *repo_model.Repository, newRel *Release) error { - rel, err := GetRelease(repo.ID, newRel.TagName) - if err != nil && !IsErrReleaseNotExist(err) { - return fmt.Errorf("GetRelease: %v", err) - } - - if rel == nil { - rel = newRel - if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil { - return fmt.Errorf("InsertOne: %v", err) - } - } else { - rel.Sha1 = newRel.Sha1 - rel.CreatedUnix = newRel.CreatedUnix - rel.NumCommits = newRel.NumCommits - rel.IsDraft = false - if rel.IsTag && newRel.PublisherID > 0 { - rel.PublisherID = newRel.PublisherID - } - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { - return fmt.Errorf("Update: %v", err) - } - } - return nil -} diff --git a/models/user.go b/models/user.go index 2a727dd124..ddd63bf5fe 100644 --- a/models/user.go +++ b/models/user.go @@ -150,7 +150,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) { // ***** START: Watch ***** watchedRepoIDs := make([]int64, 0, 10) if err = e.Table("watch").Cols("watch.repo_id"). - Where("watch.user_id = ?", u.ID).And("watch.mode <>?", RepoWatchModeDont).Find(&watchedRepoIDs); err != nil { + Where("watch.user_id = ?", u.ID).And("watch.mode <>?", repo_model.WatchModeDont).Find(&watchedRepoIDs); err != nil { return fmt.Errorf("get all watches: %v", err) } if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(repo_model.Repository)); err != nil { @@ -190,8 +190,8 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) { &AccessToken{UID: u.ID}, &Collaboration{UserID: u.ID}, &Access{UserID: u.ID}, - &Watch{UserID: u.ID}, - &Star{UID: u.ID}, + &repo_model.Watch{UserID: u.ID}, + &repo_model.Star{UID: u.ID}, &user_model.Follow{UserID: u.ID}, &user_model.Follow{FollowID: u.ID}, &Action{UserID: u.ID}, @@ -296,7 +296,7 @@ func GetStarredRepos(userID int64, private bool, listOptions db.ListOptions) ([] // GetWatchedRepos returns the repos watched by a particular user func GetWatchedRepos(userID int64, private bool, listOptions db.ListOptions) ([]*repo_model.Repository, int64, error) { sess := db.GetEngine(db.DefaultContext).Where("watch.user_id=?", userID). - And("`watch`.mode<>?", RepoWatchModeDont). + And("`watch`.mode<>?", repo_model.WatchModeDont). Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") if !private { sess = sess.And("is_private=?", false) |