Some repository refactors (#17950)
* some repository refactors * remove unnecessary code * Fix test * Remove unnecessary banner
This commit is contained in:
@ -102,7 +102,7 @@ func migrateAvatars(dstStorage storage.ObjectStorage) error {
|
||||
}
|
||||
|
||||
func migrateRepoAvatars(dstStorage storage.ObjectStorage) error {
|
||||
return models.IterateRepository(func(repo *repo_model.Repository) error {
|
||||
return repo_model.IterateRepository(func(repo *repo_model.Repository) error {
|
||||
_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
|
||||
return err
|
||||
})
|
||||
|
@ -24,7 +24,7 @@ func assertUserDeleted(t *testing.T, userID int64) {
|
||||
unittest.AssertNotExistsBean(t, &models.OrgUser{UID: userID})
|
||||
unittest.AssertNotExistsBean(t, &models.IssueUser{UID: userID})
|
||||
unittest.AssertNotExistsBean(t, &models.TeamUser{UID: userID})
|
||||
unittest.AssertNotExistsBean(t, &models.Star{UID: userID})
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID})
|
||||
}
|
||||
|
||||
func TestUserDeleteAccount(t *testing.T) {
|
||||
|
@ -89,7 +89,7 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *models.Pul
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, baseRepo)
|
||||
|
||||
headRepo, err := repo_service.ForkRepository(actor, forkOrg, models.ForkRepoOptions{
|
||||
headRepo, err := repo_service.ForkRepository(actor, forkOrg, repo_service.ForkRepoOptions{
|
||||
BaseRepo: baseRepo,
|
||||
Name: "repo-pr-update",
|
||||
Description: "desc",
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@ -18,8 +18,8 @@ func TestRepoWatch(t *testing.T) {
|
||||
// Test round-trip auto-watch
|
||||
setting.Service.AutoWatchOnChanges = true
|
||||
session := loginUser(t, "user2")
|
||||
unittest.AssertNotExistsBean(t, &models.Watch{UserID: 2, RepoID: 3})
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Watch{UserID: 2, RepoID: 3})
|
||||
testEditFile(t, session, "user3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n")
|
||||
unittest.AssertExistsAndLoadBean(t, &models.Watch{UserID: 2, RepoID: 3, Mode: models.RepoWatchModeAuto})
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{UserID: 2, RepoID: 3, Mode: repo_model.WatchModeAuto})
|
||||
})
|
||||
}
|
||||
|
125
models/action.go
125
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()
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
303
models/repo.go
303
models/repo.go
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
|
69
models/repo/fork.go
Normal file
69
models/repo/fork.go
Normal file
@ -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})
|
||||
}
|
32
models/repo/fork_test.go
Normal file
32
models/repo/fork_test.go
Normal file
@ -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)
|
||||
}
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
82
models/repo/redirect.go
Normal file
82
models/repo/redirect.go
Normal file
@ -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
|
||||
}
|
77
models/repo/redirect_test.go
Normal file
77
models/repo/redirect_test.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {
|
@ -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)
|
@ -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
|
||||
}
|
@ -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"
|
179
models/repo/update.go
Normal file
179
models/repo/update.go
Normal file
@ -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()
|
||||
}
|
196
models/repo/watch.go
Normal file
196
models/repo/watch.go
Normal file
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user