* Move some repository related code into sub package * Move more repository functions out of models * Fix lint * Some performance optimization for webhooks and others * some refactors * Fix lint * Fix * Update modules/repository/delete.go Co-authored-by: delvh <dev.lh@web.de> * Fix test * Merge * Fix test * Fix test * Fix test * Fix test Co-authored-by: delvh <dev.lh@web.de>tags/v1.18.0-dev
@@ -17,6 +17,7 @@ import ( | |||
asymkey_model "code.gitea.io/gitea/models/asymkey" | |||
"code.gitea.io/gitea/models/auth" | |||
"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/git" | |||
"code.gitea.io/gitea/modules/graceful" | |||
@@ -722,9 +723,9 @@ func runRepoSyncReleases(_ *cli.Context) error { | |||
log.Trace("Synchronizing repository releases (this may take a while)") | |||
for page := 1; ; page++ { | |||
repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ | |||
repos, count, err := repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
PageSize: models.RepositoryListDefaultPageSize, | |||
PageSize: repo_model.RepositoryListDefaultPageSize, | |||
Page: page, | |||
}, | |||
Private: true, |
@@ -393,7 +393,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { | |||
// check readable repositories by doer/actor | |||
if opts.Actor == nil || !opts.Actor.IsAdmin { | |||
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor))) | |||
cond = cond.And(builder.In("repo_id", repo_model.AccessibleRepoIDsQuery(opts.Actor))) | |||
} | |||
if opts.RequestedRepo != nil { |
@@ -190,6 +190,13 @@ func GetDeployKeyByRepo(ctx context.Context, keyID, repoID int64) (*DeployKey, e | |||
return key, nil | |||
} | |||
// IsDeployKeyExistByKeyID return true if there is at least one deploykey with the key id | |||
func IsDeployKeyExistByKeyID(ctx context.Context, keyID int64) (bool, error) { | |||
return db.GetEngine(ctx). | |||
Where("key_id = ?", keyID). | |||
Get(new(DeployKey)) | |||
} | |||
// UpdateDeployKeyCols updates deploy key information in the specified columns. | |||
func UpdateDeployKeyCols(key *DeployKey, cols ...string) error { | |||
_, err := db.GetEngine(db.DefaultContext).ID(key.ID).Cols(cols...).Update(key) |
@@ -271,11 +271,6 @@ func MaxBatchInsertSize(bean interface{}) int { | |||
return 999 / len(t.ColumnsSeq()) | |||
} | |||
// Count returns records number according struct's fields as database query conditions | |||
func Count(bean interface{}) (int64, error) { | |||
return x.Count(bean) | |||
} | |||
// IsTableNotEmpty returns true if table has at least one record | |||
func IsTableNotEmpty(tableName string) (bool, error) { | |||
return x.Table(tableName).Exist() |
@@ -1343,6 +1343,48 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { | |||
} | |||
} | |||
// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access | |||
func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Type) builder.Cond { | |||
return builder.In(id, | |||
builder.Select("repo_id").From("team_repo").Where( | |||
builder.Eq{ | |||
"team_id": teamID, | |||
}.And( | |||
builder.Or( | |||
// Check if the user is member of the team. | |||
builder.In( | |||
"team_id", builder.Select("team_id").From("team_user").Where( | |||
builder.Eq{ | |||
"uid": userID, | |||
}, | |||
), | |||
), | |||
// Check if the user is in the owner team of the organisation. | |||
builder.Exists(builder.Select("team_id").From("team_user"). | |||
Where(builder.Eq{ | |||
"org_id": orgID, | |||
"team_id": builder.Select("id").From("team").Where( | |||
builder.Eq{ | |||
"org_id": orgID, | |||
"lower_name": strings.ToLower(organization.OwnerTeamName), | |||
}), | |||
"uid": userID, | |||
}), | |||
), | |||
)).And( | |||
builder.In( | |||
"team_id", builder.Select("team_id").From("team_unit").Where( | |||
builder.Eq{ | |||
"`team_unit`.org_id": orgID, | |||
}.And( | |||
builder.In("`team_unit`.type", units), | |||
), | |||
), | |||
), | |||
), | |||
)) | |||
} | |||
// issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table | |||
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organization.Organization, team *organization.Team, isPull bool) builder.Cond { | |||
cond := builder.NewCond() | |||
@@ -1356,19 +1398,19 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati | |||
} else { | |||
cond = cond.And( | |||
builder.Or( | |||
userOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos | |||
userOrgPublicUnitRepoCond(userID, org.ID), // user org public non-member repos, TODO: check repo has issues | |||
repo_model.UserOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos | |||
repo_model.UserOrgPublicUnitRepoCond(userID, org.ID), // user org public non-member repos, TODO: check repo has issues | |||
), | |||
) | |||
} | |||
} else { | |||
cond = cond.And( | |||
builder.Or( | |||
userOwnedRepoCond(userID), // owned repos | |||
userCollaborationRepoCond(repoIDstr, userID), // collaboration repos | |||
userAssignedRepoCond(repoIDstr, userID), // user has been assigned accessible public repos | |||
userMentionedRepoCond(repoIDstr, userID), // user has been mentioned accessible public repos | |||
userCreateIssueRepoCond(repoIDstr, userID, isPull), // user has created issue/pr accessible public repos | |||
repo_model.UserOwnedRepoCond(userID), // owned repos | |||
repo_model.UserCollaborationRepoCond(repoIDstr, userID), // collaboration repos | |||
repo_model.UserAssignedRepoCond(repoIDstr, userID), // user has been assigned accessible public repos | |||
repo_model.UserMentionedRepoCond(repoIDstr, userID), // user has been mentioned accessible public repos | |||
repo_model.UserCreateIssueRepoCond(repoIDstr, userID, isPull), // user has created issue/pr accessible public repos | |||
), | |||
) | |||
} | |||
@@ -1434,7 +1476,7 @@ func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *user_model.User) ([]i | |||
opts.setupSessionNoLimit(sess) | |||
accessCond := accessibleRepositoryCondition(user) | |||
accessCond := repo_model.AccessibleRepositoryCondition(user) | |||
if err := sess.Where(accessCond). | |||
Distinct("issue.repo_id"). | |||
Table("issue"). |
@@ -75,7 +75,7 @@ func (issues IssueList) loadRepositories(ctx context.Context) ([]*repo_model.Rep | |||
} | |||
} | |||
} | |||
return valuesRepository(repoMaps), nil | |||
return repo_model.ValuesRepository(repoMaps), nil | |||
} | |||
// LoadRepositories loads issues' all repositories |
@@ -26,7 +26,7 @@ func init() { | |||
} | |||
func newIssueUsers(ctx context.Context, repo *repo_model.Repository, issue *Issue) error { | |||
assignees, err := getRepoAssignees(ctx, repo) | |||
assignees, err := repo_model.GetRepoAssignees(ctx, repo) | |||
if err != nil { | |||
return fmt.Errorf("getAssignees: %v", err) | |||
} |
@@ -142,7 +142,7 @@ func LFSObjectAccessible(user *user_model.User, oid string) (bool, error) { | |||
count, err := db.GetEngine(db.DefaultContext).Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) | |||
return count > 0, err | |||
} | |||
cond := accessibleRepositoryCondition(user) | |||
cond := repo_model.AccessibleRepositoryCondition(user) | |||
count, err := db.GetEngine(db.DefaultContext).Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) | |||
return count > 0, err | |||
} | |||
@@ -173,7 +173,7 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6 | |||
newMetas := make([]*LFSMetaObject, 0, len(metas)) | |||
cond := builder.In( | |||
"`lfs_meta_object`.repository_id", | |||
builder.Select("`repository`.id").From("repository").Where(accessibleRepositoryCondition(user)), | |||
builder.Select("`repository`.id").From("repository").Where(repo_model.AccessibleRepositoryCondition(user)), | |||
) | |||
err = sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas) | |||
if err != nil { | |||
@@ -246,3 +246,12 @@ func CopyLFS(ctx context.Context, newRepo, oldRepo *repo_model.Repository) error | |||
return nil | |||
} | |||
// GetRepoLFSSize return a repository's lfs files size | |||
func GetRepoLFSSize(ctx context.Context, repoID int64) (int64, error) { | |||
lfsSize, err := db.GetEngine(ctx).Where("repository_id = ?", repoID).SumInt(new(LFSMetaObject), "size") | |||
if err != nil { | |||
return 0, fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) | |||
} | |||
return lfsSize, nil | |||
} |
@@ -266,10 +266,10 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n | |||
return err | |||
} | |||
if issue.IsPull && !checkRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { | |||
if issue.IsPull && !CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { | |||
continue | |||
} | |||
if !issue.IsPull && !checkRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { | |||
if !issue.IsPull && !CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { | |||
continue | |||
} | |||
@@ -510,9 +510,9 @@ func (nl NotificationList) getPendingRepoIDs() []int64 { | |||
} | |||
// LoadRepos loads repositories from database | |||
func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) { | |||
func (nl NotificationList) LoadRepos() (repo_model.RepositoryList, []int, error) { | |||
if len(nl) == 0 { | |||
return RepositoryList{}, []int{}, nil | |||
return repo_model.RepositoryList{}, []int{}, nil | |||
} | |||
repoIDs := nl.getPendingRepoIDs() | |||
@@ -548,7 +548,7 @@ func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) { | |||
failed := []int{} | |||
reposList := make(RepositoryList, 0, len(repoIDs)) | |||
reposList := make(repo_model.RepositoryList, 0, len(repoIDs)) | |||
for i, notification := range nl { | |||
if notification.Repository == nil { | |||
notification.Repository = repos[notification.RepoID] |
@@ -54,7 +54,7 @@ func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { | |||
Join("LEFT", builder. | |||
Select("id as repo_id, owner_id as repo_owner_id"). | |||
From("repository"). | |||
Where(accessibleRepositoryCondition(user)), "`repository`.repo_owner_id = `team`.org_id"). | |||
Where(repo_model.AccessibleRepositoryCondition(user)), "`repository`.repo_owner_id = `team`.org_id"). | |||
Where("`team_user`.uid = ?", user.ID). | |||
GroupBy(groupByStr) | |||
@@ -8,11 +8,7 @@ package models | |||
import ( | |||
"context" | |||
"fmt" | |||
"os" | |||
"path" | |||
"strconv" | |||
"strings" | |||
"unicode/utf8" | |||
_ "image/jpeg" // Needed for jpeg support | |||
@@ -47,11 +43,7 @@ func NewRepoContext() { | |||
} | |||
// CheckRepoUnitUser check whether user could visit the unit of this repository | |||
func CheckRepoUnitUser(repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { | |||
return checkRepoUnitUser(db.DefaultContext, repo, user, unitType) | |||
} | |||
func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { | |||
func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { | |||
if user != nil && user.IsAdmin { | |||
return true | |||
} | |||
@@ -64,241 +56,6 @@ func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u | |||
return perm.CanRead(unitType) | |||
} | |||
func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { | |||
if err = repo.GetOwner(ctx); err != nil { | |||
return nil, err | |||
} | |||
e := db.GetEngine(ctx) | |||
userIDs := make([]int64, 0, 10) | |||
if err = e.Table("access"). | |||
Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite). | |||
Select("user_id"). | |||
Find(&userIDs); err != nil { | |||
return nil, err | |||
} | |||
additionalUserIDs := make([]int64, 0, 10) | |||
if err = e.Table("team_user"). | |||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). | |||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). | |||
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). | |||
Distinct("`team_user`.uid"). | |||
Select("`team_user`.uid"). | |||
Find(&additionalUserIDs); err != nil { | |||
return nil, err | |||
} | |||
uidMap := map[int64]bool{} | |||
i := 0 | |||
for _, uid := range userIDs { | |||
if uidMap[uid] { | |||
continue | |||
} | |||
uidMap[uid] = true | |||
userIDs[i] = uid | |||
i++ | |||
} | |||
userIDs = userIDs[:i] | |||
userIDs = append(userIDs, additionalUserIDs...) | |||
for _, uid := range additionalUserIDs { | |||
if uidMap[uid] { | |||
continue | |||
} | |||
userIDs[i] = uid | |||
i++ | |||
} | |||
userIDs = userIDs[:i] | |||
// Leave a seat for owner itself to append later, but if owner is an organization | |||
// and just waste 1 unit is cheaper than re-allocate memory once. | |||
users := make([]*user_model.User, 0, len(userIDs)+1) | |||
if len(userIDs) > 0 { | |||
if err = e.In("id", userIDs).Find(&users); err != nil { | |||
return nil, err | |||
} | |||
} | |||
if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] { | |||
users = append(users, repo.Owner) | |||
} | |||
return users, nil | |||
} | |||
// GetRepoAssignees returns all users that have write access and can be assigned to issues | |||
// of the repository, | |||
func GetRepoAssignees(repo *repo_model.Repository) (_ []*user_model.User, err error) { | |||
return getRepoAssignees(db.DefaultContext, repo) | |||
} | |||
func getReviewers(ctx context.Context, repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) { | |||
// Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries | |||
if err := repo.GetOwner(ctx); err != nil { | |||
return nil, err | |||
} | |||
cond := builder.And(builder.Neq{"`user`.id": posterID}) | |||
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate { | |||
// This a private repository: | |||
// Anyone who can read the repository is a requestable reviewer | |||
cond = cond.And(builder.In("`user`.id", | |||
builder.Select("user_id").From("access").Where( | |||
builder.Eq{"repo_id": repo.ID}. | |||
And(builder.Gte{"mode": perm.AccessModeRead}), | |||
), | |||
)) | |||
if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID { | |||
// as private *user* repos don't generate an entry in the `access` table, | |||
// the owner of a private repo needs to be explicitly added. | |||
cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) | |||
} | |||
} else { | |||
// This is a "public" repository: | |||
// Any user that has read access, is a watcher or organization member can be requested to review | |||
cond = cond.And(builder.And(builder.In("`user`.id", | |||
builder.Select("user_id").From("access"). | |||
Where(builder.Eq{"repo_id": repo.ID}. | |||
And(builder.Gte{"mode": perm.AccessModeRead})), | |||
).Or(builder.In("`user`.id", | |||
builder.Select("user_id").From("watch"). | |||
Where(builder.Eq{"repo_id": repo.ID}. | |||
And(builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))), | |||
).Or(builder.In("`user`.id", | |||
builder.Select("uid").From("org_user"). | |||
Where(builder.Eq{"org_id": repo.OwnerID}), | |||
))))) | |||
} | |||
users := make([]*user_model.User, 0, 8) | |||
return users, db.GetEngine(ctx).Where(cond).OrderBy("name").Find(&users) | |||
} | |||
// GetReviewers get all users can be requested to review: | |||
// * for private repositories this returns all users that have read access or higher to the repository. | |||
// * for public repositories this returns all users that have read access or higher to the repository, | |||
// all repo watchers and all organization members. | |||
// TODO: may be we should have a busy choice for users to block review request to them. | |||
func GetReviewers(repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) { | |||
return getReviewers(db.DefaultContext, repo, doerID, posterID) | |||
} | |||
// GetReviewerTeams get all teams can be requested to review | |||
func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) { | |||
if err := repo.GetOwner(db.DefaultContext); err != nil { | |||
return nil, err | |||
} | |||
if !repo.Owner.IsOrganization() { | |||
return nil, nil | |||
} | |||
teams, err := organization.GetTeamsWithAccessToRepo(db.DefaultContext, repo.OwnerID, repo.ID, perm.AccessModeRead) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return teams, err | |||
} | |||
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize | |||
func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { | |||
size, err := util.GetDirectorySize(repo.RepoPath()) | |||
if err != nil { | |||
return fmt.Errorf("updateSize: %v", err) | |||
} | |||
lfsSize, err := db.GetEngine(ctx).Where("repository_id = ?", repo.ID).SumInt(new(LFSMetaObject), "size") | |||
if err != nil { | |||
return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) | |||
} | |||
repo.Size = size + lfsSize | |||
_, err = db.GetEngine(ctx).ID(repo.ID).Cols("size").NoAutoTime().Update(repo) | |||
return err | |||
} | |||
// CanUserForkRepo returns true if specified user can fork repository. | |||
func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, error) { | |||
if user == nil { | |||
return false, nil | |||
} | |||
if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { | |||
return true, nil | |||
} | |||
ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(user.ID) | |||
if err != nil { | |||
return false, err | |||
} | |||
for _, org := range ownedOrgs { | |||
if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) { | |||
return true, nil | |||
} | |||
} | |||
return false, nil | |||
} | |||
// FindUserOrgForks returns the forked repositories for one user from a repository | |||
func FindUserOrgForks(ctx context.Context, repoID, userID int64) ([]*repo_model.Repository, error) { | |||
cond := builder.And( | |||
builder.Eq{"fork_id": repoID}, | |||
builder.In("owner_id", | |||
builder.Select("org_id"). | |||
From("org_user"). | |||
Where(builder.Eq{"uid": userID}), | |||
), | |||
) | |||
var repos []*repo_model.Repository | |||
return repos, db.GetEngine(ctx).Table("repository").Where(cond).Find(&repos) | |||
} | |||
// GetForksByUserAndOrgs return forked repos of the user and owned orgs | |||
func GetForksByUserAndOrgs(ctx context.Context, user *user_model.User, repo *repo_model.Repository) ([]*repo_model.Repository, error) { | |||
var repoList []*repo_model.Repository | |||
if user == nil { | |||
return repoList, nil | |||
} | |||
forkedRepo, err := repo_model.GetUserFork(ctx, repo.ID, user.ID) | |||
if err != nil { | |||
return repoList, err | |||
} | |||
if forkedRepo != nil { | |||
repoList = append(repoList, forkedRepo) | |||
} | |||
orgForks, err := FindUserOrgForks(ctx, repo.ID, user.ID) | |||
if err != nil { | |||
return nil, err | |||
} | |||
repoList = append(repoList, orgForks...) | |||
return repoList, nil | |||
} | |||
// CanUserDelete returns true if user could delete the repository | |||
func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) { | |||
if user.IsAdmin || user.ID == repo.OwnerID { | |||
return true, nil | |||
} | |||
if err := repo.GetOwner(db.DefaultContext); err != nil { | |||
return false, err | |||
} | |||
if repo.Owner.IsOrganization() { | |||
isOwner, err := organization.OrgFromUser(repo.Owner).IsOwnedBy(user.ID) | |||
if err != nil { | |||
return false, err | |||
} else if isOwner { | |||
return true, nil | |||
} | |||
} | |||
return false, nil | |||
} | |||
// CreateRepoOptions contains the create repository options | |||
type CreateRepoOptions struct { | |||
Name string | |||
@@ -441,126 +198,6 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ | |||
return nil | |||
} | |||
// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... | |||
func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { | |||
if err := repo.GetOwner(ctx); err != nil { | |||
return err | |||
} | |||
// Create/Remove git-daemon-export-ok for git-daemon... | |||
daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`) | |||
isExist, err := util.IsExist(daemonExportFile) | |||
if err != nil { | |||
log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) | |||
return err | |||
} | |||
isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic | |||
if !isPublic && isExist { | |||
if err = util.Remove(daemonExportFile); err != nil { | |||
log.Error("Failed to remove %s: %v", daemonExportFile, err) | |||
} | |||
} else if isPublic && !isExist { | |||
if f, err := os.Create(daemonExportFile); err != nil { | |||
log.Error("Failed to create %s: %v", daemonExportFile, err) | |||
} else { | |||
f.Close() | |||
} | |||
} | |||
return nil | |||
} | |||
// IncrementRepoForkNum increment repository fork number | |||
func IncrementRepoForkNum(ctx context.Context, repoID int64) error { | |||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) | |||
return err | |||
} | |||
// DecrementRepoForkNum decrement repository fork number | |||
func DecrementRepoForkNum(ctx context.Context, repoID int64) error { | |||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repoID) | |||
return err | |||
} | |||
// UpdateRepositoryCtx updates a repository with db context | |||
func UpdateRepositoryCtx(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { | |||
repo.LowerName = strings.ToLower(repo.Name) | |||
if utf8.RuneCountInString(repo.Description) > 255 { | |||
repo.Description = string([]rune(repo.Description)[:255]) | |||
} | |||
if utf8.RuneCountInString(repo.Website) > 255 { | |||
repo.Website = string([]rune(repo.Website)[:255]) | |||
} | |||
e := db.GetEngine(ctx) | |||
if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { | |||
return fmt.Errorf("update: %v", err) | |||
} | |||
if err = UpdateRepoSize(ctx, repo); err != nil { | |||
log.Error("Failed to update size for repository: %v", err) | |||
} | |||
if visibilityChanged { | |||
if err = repo.GetOwner(ctx); err != nil { | |||
return fmt.Errorf("getOwner: %v", err) | |||
} | |||
if repo.Owner.IsOrganization() { | |||
// Organization repository need to recalculate access table when visibility is changed. | |||
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { | |||
return fmt.Errorf("recalculateTeamAccesses: %v", err) | |||
} | |||
} | |||
// If repo has become private, we need to set its actions to private. | |||
if repo.IsPrivate { | |||
_, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{ | |||
IsPrivate: true, | |||
}) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
// Create/Remove git-daemon-export-ok for git-daemon... | |||
if err := CheckDaemonExportOK(ctx, repo); err != nil { | |||
return err | |||
} | |||
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) | |||
if err != nil { | |||
return fmt.Errorf("GetRepositoriesByForkID: %v", err) | |||
} | |||
for i := range forkRepos { | |||
forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate | |||
if err = UpdateRepositoryCtx(ctx, forkRepos[i], true); err != nil { | |||
return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
// UpdateRepository updates a repository | |||
func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err error) { | |||
ctx, committer, err := db.TxContext() | |||
if err != nil { | |||
return err | |||
} | |||
defer committer.Close() | |||
if err = UpdateRepositoryCtx(ctx, repo, visibilityChanged); err != nil { | |||
return fmt.Errorf("updateRepository: %v", 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 { | |||
@@ -1052,14 +689,14 @@ func CheckRepoStats(ctx context.Context) error { | |||
continue | |||
} | |||
rawResult, err := db.GetEngine(db.DefaultContext).Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) | |||
rawResult, err := e.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) | |||
if err != nil { | |||
log.Error("Select count of forks[%d]: %v", repo.ID, err) | |||
continue | |||
} | |||
repo.NumForks = int(parseCountResult(rawResult)) | |||
if err = UpdateRepository(repo, false); err != nil { | |||
if _, err = e.ID(repo.ID).Cols("num_forks").Update(repo); err != nil { | |||
log.Error("UpdateRepository[%d]: %v", id, err) | |||
continue | |||
} | |||
@@ -1130,30 +767,6 @@ func DoctorUserStarNum() (err error) { | |||
return | |||
} | |||
// LinkedRepository returns the linked repo if any | |||
func LinkedRepository(a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { | |||
if a.IssueID != 0 { | |||
iss, err := GetIssueByID(a.IssueID) | |||
if err != nil { | |||
return nil, unit.TypeIssues, err | |||
} | |||
repo, err := repo_model.GetRepositoryByID(iss.RepoID) | |||
unitType := unit.TypeIssues | |||
if iss.IsPull { | |||
unitType = unit.TypePullRequests | |||
} | |||
return repo, unitType, err | |||
} else if a.ReleaseID != 0 { | |||
rel, err := GetReleaseByID(db.DefaultContext, a.ReleaseID) | |||
if err != nil { | |||
return nil, unit.TypeReleases, err | |||
} | |||
repo, err := repo_model.GetRepositoryByID(rel.RepoID) | |||
return repo, unit.TypeReleases, err | |||
} | |||
return nil, -1, nil | |||
} | |||
// DeleteDeployKey delete deploy keys | |||
func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error { | |||
key, err := asymkey_model.GetDeployKeyByID(ctx, id) | |||
@@ -1164,8 +777,6 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error | |||
return fmt.Errorf("GetDeployKeyByID: %v", err) | |||
} | |||
sess := db.GetEngine(ctx) | |||
// Check if user has access to delete this key. | |||
if !doer.IsAdmin { | |||
repo, err := repo_model.GetRepositoryByIDCtx(ctx, key.RepoID) | |||
@@ -1184,14 +795,14 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error | |||
} | |||
} | |||
if _, err = sess.ID(key.ID).Delete(new(asymkey_model.DeployKey)); err != nil { | |||
if _, err := db.DeleteByBean(ctx, &asymkey_model.DeployKey{ | |||
ID: key.ID, | |||
}); err != nil { | |||
return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err) | |||
} | |||
// Check if this is the last reference to same key content. | |||
has, err := sess. | |||
Where("key_id = ?", key.KeyID). | |||
Get(new(asymkey_model.DeployKey)) | |||
has, err := asymkey_model.IsDeployKeyExistByKeyID(ctx, key.KeyID) | |||
if err != nil { | |||
return err | |||
} else if !has { |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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" | |||
@@ -16,7 +17,7 @@ import ( | |||
func TestIncreaseDownloadCount(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
attachment, err := GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") | |||
attachment, err := repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") | |||
assert.NoError(t, err) | |||
assert.Equal(t, int64(0), attachment.DownloadCount) | |||
@@ -24,7 +25,7 @@ func TestIncreaseDownloadCount(t *testing.T) { | |||
err = attachment.IncreaseDownloadCount() | |||
assert.NoError(t, err) | |||
attachment, err = GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") | |||
attachment, err = repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") | |||
assert.NoError(t, err) | |||
assert.Equal(t, int64(1), attachment.DownloadCount) | |||
} | |||
@@ -33,11 +34,11 @@ func TestGetByCommentOrIssueID(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
// count of attachments from issue ID | |||
attachments, err := GetAttachmentsByIssueID(db.DefaultContext, 1) | |||
attachments, err := repo_model.GetAttachmentsByIssueID(db.DefaultContext, 1) | |||
assert.NoError(t, err) | |||
assert.Len(t, attachments, 1) | |||
attachments, err = GetAttachmentsByCommentID(db.DefaultContext, 1) | |||
attachments, err = repo_model.GetAttachmentsByCommentID(db.DefaultContext, 1) | |||
assert.NoError(t, err) | |||
assert.Len(t, attachments, 2) | |||
} | |||
@@ -45,33 +46,33 @@ func TestGetByCommentOrIssueID(t *testing.T) { | |||
func TestDeleteAttachments(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
count, err := DeleteAttachmentsByIssue(4, false) | |||
count, err := repo_model.DeleteAttachmentsByIssue(4, false) | |||
assert.NoError(t, err) | |||
assert.Equal(t, 2, count) | |||
count, err = DeleteAttachmentsByComment(2, false) | |||
count, err = repo_model.DeleteAttachmentsByComment(2, false) | |||
assert.NoError(t, err) | |||
assert.Equal(t, 2, count) | |||
err = DeleteAttachment(&Attachment{ID: 8}, false) | |||
err = repo_model.DeleteAttachment(&repo_model.Attachment{ID: 8}, false) | |||
assert.NoError(t, err) | |||
attachment, err := GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18") | |||
attachment, err := repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18") | |||
assert.Error(t, err) | |||
assert.True(t, IsErrAttachmentNotExist(err)) | |||
assert.True(t, repo_model.IsErrAttachmentNotExist(err)) | |||
assert.Nil(t, attachment) | |||
} | |||
func TestGetAttachmentByID(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
attach, err := GetAttachmentByID(db.DefaultContext, 1) | |||
attach, err := repo_model.GetAttachmentByID(db.DefaultContext, 1) | |||
assert.NoError(t, err) | |||
assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID) | |||
} | |||
func TestAttachment_DownloadURL(t *testing.T) { | |||
attach := &Attachment{ | |||
attach := &repo_model.Attachment{ | |||
UUID: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", | |||
ID: 1, | |||
} | |||
@@ -81,20 +82,20 @@ func TestAttachment_DownloadURL(t *testing.T) { | |||
func TestUpdateAttachment(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
attach, err := GetAttachmentByID(db.DefaultContext, 1) | |||
attach, err := repo_model.GetAttachmentByID(db.DefaultContext, 1) | |||
assert.NoError(t, err) | |||
assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID) | |||
attach.Name = "new_name" | |||
assert.NoError(t, UpdateAttachment(db.DefaultContext, attach)) | |||
assert.NoError(t, repo_model.UpdateAttachment(db.DefaultContext, attach)) | |||
unittest.AssertExistsAndLoadBean(t, &Attachment{Name: "new_name"}) | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{Name: "new_name"}) | |||
} | |||
func TestGetAttachmentsByUUIDs(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
attachList, err := GetAttachmentsByUUIDs(db.DefaultContext, []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) | |||
attachList, err := repo_model.GetAttachmentsByUUIDs(db.DefaultContext, []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) | |||
assert.NoError(t, err) | |||
assert.Len(t, attachList, 2) | |||
assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID) |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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" | |||
@@ -16,10 +17,10 @@ import ( | |||
func TestRepository_GetCollaborators(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
test := func(repoID int64) { | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) | |||
collaborators, err := GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{}) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository) | |||
collaborators, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{}) | |||
assert.NoError(t, err) | |||
expectedLen, err := db.GetEngine(db.DefaultContext).Count(&Collaboration{RepoID: repoID}) | |||
expectedLen, err := db.GetEngine(db.DefaultContext).Count(&repo_model.Collaboration{RepoID: repoID}) | |||
assert.NoError(t, err) | |||
assert.Len(t, collaborators, int(expectedLen)) | |||
for _, collaborator := range collaborators { | |||
@@ -37,8 +38,8 @@ func TestRepository_IsCollaborator(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
test := func(repoID, userID int64, expected bool) { | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) | |||
actual, err := IsCollaborator(db.DefaultContext, repo.ID, userID) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository) | |||
actual, err := repo_model.IsCollaborator(db.DefaultContext, repo.ID, userID) | |||
assert.NoError(t, err) | |||
assert.Equal(t, expected, actual) | |||
} |
@@ -8,6 +8,8 @@ import ( | |||
"context" | |||
"code.gitea.io/gitea/models/db" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"xorm.io/builder" | |||
) | |||
// GetRepositoriesByForkID returns all repositories with given fork ID. | |||
@@ -63,3 +65,51 @@ func GetForks(repo *Repository, listOptions db.ListOptions) ([]*Repository, erro | |||
forks := make([]*Repository, 0, listOptions.PageSize) | |||
return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) | |||
} | |||
// IncrementRepoForkNum increment repository fork number | |||
func IncrementRepoForkNum(ctx context.Context, repoID int64) error { | |||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) | |||
return err | |||
} | |||
// DecrementRepoForkNum decrement repository fork number | |||
func DecrementRepoForkNum(ctx context.Context, repoID int64) error { | |||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repoID) | |||
return err | |||
} | |||
// FindUserOrgForks returns the forked repositories for one user from a repository | |||
func FindUserOrgForks(ctx context.Context, repoID, userID int64) ([]*Repository, error) { | |||
cond := builder.And( | |||
builder.Eq{"fork_id": repoID}, | |||
builder.In("owner_id", | |||
builder.Select("org_id"). | |||
From("org_user"). | |||
Where(builder.Eq{"uid": userID}), | |||
), | |||
) | |||
var repos []*Repository | |||
return repos, db.GetEngine(ctx).Table("repository").Where(cond).Find(&repos) | |||
} | |||
// GetForksByUserAndOrgs return forked repos of the user and owned orgs | |||
func GetForksByUserAndOrgs(ctx context.Context, user *user_model.User, repo *Repository) ([]*Repository, error) { | |||
var repoList []*Repository | |||
if user == nil { | |||
return repoList, nil | |||
} | |||
forkedRepo, err := GetUserFork(ctx, repo.ID, user.ID) | |||
if err != nil { | |||
return repoList, err | |||
} | |||
if forkedRepo != nil { | |||
repoList = append(repoList, forkedRepo) | |||
} | |||
orgForks, err := FindUserOrgForks(ctx, repo.ID, user.ID) | |||
if err != nil { | |||
return nil, err | |||
} | |||
repoList = append(repoList, orgForks...) | |||
return repoList, nil | |||
} |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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" | |||
@@ -17,17 +18,17 @@ func TestGetUserFork(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
// User13 has repo 11 forked from repo10 | |||
repo, err := GetRepositoryByID(10) | |||
repo, err := repo_model.GetRepositoryByID(10) | |||
assert.NoError(t, err) | |||
assert.NotNil(t, repo) | |||
repo, err = GetUserFork(db.DefaultContext, repo.ID, 13) | |||
repo, err = repo_model.GetUserFork(db.DefaultContext, repo.ID, 13) | |||
assert.NoError(t, err) | |||
assert.NotNil(t, repo) | |||
repo, err = GetRepositoryByID(9) | |||
repo, err = repo_model.GetRepositoryByID(9) | |||
assert.NoError(t, err) | |||
assert.NotNil(t, repo) | |||
repo, err = GetUserFork(db.DefaultContext, repo.ID, 13) | |||
repo, err = repo_model.GetUserFork(db.DefaultContext, repo.ID, 13) | |||
assert.NoError(t, err) | |||
assert.Nil(t, repo) | |||
} |
@@ -2,31 +2,22 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
import ( | |||
"path/filepath" | |||
"testing" | |||
_ "code.gitea.io/gitea/models" // register table model | |||
_ "code.gitea.io/gitea/models/perm/access" // register table model | |||
_ "code.gitea.io/gitea/models/repo" // register table model | |||
_ "code.gitea.io/gitea/models/user" // register table model | |||
"code.gitea.io/gitea/models/unittest" | |||
) | |||
func TestMain(m *testing.M) { | |||
unittest.MainTest(m, &unittest.TestOptions{ | |||
GiteaRootPath: filepath.Join("..", ".."), | |||
FixtureFiles: []string{ | |||
"attachment.yml", | |||
"repo_archiver.yml", | |||
"repository.yml", | |||
"repo_unit.yml", | |||
"repo_indexer_status.yml", | |||
"repo_redirect.yml", | |||
"watch.yml", | |||
"star.yml", | |||
"topic.yml", | |||
"repo_topic.yml", | |||
"user.yml", | |||
"collaboration.yml", | |||
}, | |||
}) | |||
} |
@@ -123,8 +123,8 @@ func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error { | |||
} | |||
// InsertMirror inserts a mirror to database | |||
func InsertMirror(mirror *Mirror) error { | |||
_, err := db.GetEngine(db.DefaultContext).Insert(mirror) | |||
func InsertMirror(ctx context.Context, mirror *Mirror) error { | |||
_, err := db.GetEngine(ctx).Insert(mirror) | |||
return err | |||
} | |||
@@ -168,3 +168,12 @@ func (repos MirrorRepositoryList) loadAttributes(ctx context.Context) error { | |||
func (repos MirrorRepositoryList) LoadAttributes() error { | |||
return repos.loadAttributes(db.DefaultContext) | |||
} | |||
// GetUserMirrorRepositories returns a list of mirror repositories of given user. | |||
func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { | |||
repos := make([]*Repository, 0, 10) | |||
return repos, db.GetEngine(db.DefaultContext). | |||
Where("owner_id = ?", userID). | |||
And("is_mirror = ?", true). | |||
Find(&repos) | |||
} |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
import ( | |||
"testing" | |||
"time" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
@@ -19,20 +20,20 @@ func TestPushMirrorsIterate(t *testing.T) { | |||
now := timeutil.TimeStampNow() | |||
InsertPushMirror(&PushMirror{ | |||
repo_model.InsertPushMirror(&repo_model.PushMirror{ | |||
RemoteName: "test-1", | |||
LastUpdateUnix: now, | |||
Interval: 1, | |||
}) | |||
long, _ := time.ParseDuration("24h") | |||
InsertPushMirror(&PushMirror{ | |||
repo_model.InsertPushMirror(&repo_model.PushMirror{ | |||
RemoteName: "test-2", | |||
LastUpdateUnix: now, | |||
Interval: long, | |||
}) | |||
InsertPushMirror(&PushMirror{ | |||
repo_model.InsertPushMirror(&repo_model.PushMirror{ | |||
RemoteName: "test-3", | |||
LastUpdateUnix: now, | |||
Interval: 0, | |||
@@ -40,8 +41,8 @@ func TestPushMirrorsIterate(t *testing.T) { | |||
time.Sleep(1 * time.Millisecond) | |||
PushMirrorsIterate(1, func(idx int, bean interface{}) error { | |||
m, ok := bean.(*PushMirror) | |||
repo_model.PushMirrorsIterate(1, func(idx int, bean interface{}) error { | |||
m, ok := bean.(*repo_model.PushMirror) | |||
assert.True(t, ok) | |||
assert.Equal(t, "test-1", m.RemoteName) | |||
assert.Equal(t, m.RemoteName, m.GetRemoteName()) |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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" | |||
@@ -16,27 +17,27 @@ import ( | |||
func TestLookupRedirect(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repoID, err := LookupRedirect(2, "oldrepo1") | |||
repoID, err := repo_model.LookupRedirect(2, "oldrepo1") | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, 1, repoID) | |||
_, err = LookupRedirect(unittest.NonexistentID, "doesnotexist") | |||
assert.True(t, IsErrRedirectNotExist(err)) | |||
_, err = repo_model.LookupRedirect(unittest.NonexistentID, "doesnotexist") | |||
assert.True(t, repo_model.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")) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) | |||
unittest.AssertExistsAndLoadBean(t, &Redirect{ | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ | |||
OwnerID: repo.OwnerID, | |||
LowerName: repo.LowerName, | |||
RedirectRepoID: repo.ID, | |||
}) | |||
unittest.AssertExistsAndLoadBean(t, &Redirect{ | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ | |||
OwnerID: repo.OwnerID, | |||
LowerName: "oldrepo1", | |||
RedirectRepoID: repo.ID, | |||
@@ -47,15 +48,15 @@ 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")) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) | |||
unittest.AssertExistsAndLoadBean(t, &Redirect{ | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ | |||
OwnerID: repo.OwnerID, | |||
LowerName: repo.LowerName, | |||
RedirectRepoID: repo.ID, | |||
}) | |||
unittest.AssertNotExistsBean(t, &Redirect{ | |||
unittest.AssertNotExistsBean(t, &repo_model.Redirect{ | |||
OwnerID: repo.OwnerID, | |||
LowerName: "oldrepo1", | |||
RedirectRepoID: repo.ID, | |||
@@ -66,10 +67,10 @@ 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")) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) | |||
unittest.AssertExistsAndLoadBean(t, &Redirect{ | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ | |||
OwnerID: repo.OwnerID, | |||
LowerName: repo.LowerName, | |||
RedirectRepoID: repo.ID, |
@@ -5,18 +5,21 @@ | |||
package repo | |||
import ( | |||
"context" | |||
"fmt" | |||
"strings" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/perm" | |||
"code.gitea.io/gitea/models/unit" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/container" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
"code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/util" | |||
// GetUserMirrorRepositories returns a list of mirror repositories of given user. | |||
func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { | |||
repos := make([]*Repository, 0, 10) | |||
return repos, db.GetEngine(db.DefaultContext). | |||
Where("owner_id = ?", userID). | |||
And("is_mirror = ?", true). | |||
Find(&repos) | |||
} | |||
"xorm.io/builder" | |||
) | |||
// IterateRepository iterate repositories | |||
func IterateRepository(f func(repo *Repository) error) error { | |||
@@ -45,3 +48,643 @@ func IterateRepository(f func(repo *Repository) error) error { | |||
func FindReposMapByIDs(repoIDs []int64, res map[int64]*Repository) error { | |||
return db.GetEngine(db.DefaultContext).In("id", repoIDs).Find(&res) | |||
} | |||
// RepositoryListDefaultPageSize is the default number of repositories | |||
// to load in memory when running administrative tasks on all (or almost | |||
// all) of them. | |||
// The number should be low enough to avoid filling up all RAM with | |||
// repository data... | |||
const RepositoryListDefaultPageSize = 64 | |||
// RepositoryList contains a list of repositories | |||
type RepositoryList []*Repository | |||
func (repos RepositoryList) Len() int { | |||
return len(repos) | |||
} | |||
func (repos RepositoryList) Less(i, j int) bool { | |||
return repos[i].FullName() < repos[j].FullName() | |||
} | |||
func (repos RepositoryList) Swap(i, j int) { | |||
repos[i], repos[j] = repos[j], repos[i] | |||
} | |||
// ValuesRepository converts a repository map to a list | |||
// FIXME: Remove in favor of maps.values when MIN_GO_VERSION >= 1.18 | |||
func ValuesRepository(m map[int64]*Repository) []*Repository { | |||
values := make([]*Repository, 0, len(m)) | |||
for _, v := range m { | |||
values = append(values, v) | |||
} | |||
return values | |||
} | |||
// RepositoryListOfMap make list from values of map | |||
func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList { | |||
return RepositoryList(ValuesRepository(repoMap)) | |||
} | |||
func (repos RepositoryList) loadAttributes(ctx context.Context) error { | |||
if len(repos) == 0 { | |||
return nil | |||
} | |||
set := make(map[int64]struct{}) | |||
repoIDs := make([]int64, len(repos)) | |||
for i := range repos { | |||
set[repos[i].OwnerID] = struct{}{} | |||
repoIDs[i] = repos[i].ID | |||
} | |||
// Load owners. | |||
users := make(map[int64]*user_model.User, len(set)) | |||
if err := db.GetEngine(ctx). | |||
Where("id > 0"). | |||
In("id", container.KeysInt64(set)). | |||
Find(&users); err != nil { | |||
return fmt.Errorf("find users: %v", err) | |||
} | |||
for i := range repos { | |||
repos[i].Owner = users[repos[i].OwnerID] | |||
} | |||
// Load primary language. | |||
stats := make(LanguageStatList, 0, len(repos)) | |||
if err := db.GetEngine(ctx). | |||
Where("`is_primary` = ? AND `language` != ?", true, "other"). | |||
In("`repo_id`", repoIDs). | |||
Find(&stats); err != nil { | |||
return fmt.Errorf("find primary languages: %v", err) | |||
} | |||
stats.LoadAttributes() | |||
for i := range repos { | |||
for _, st := range stats { | |||
if st.RepoID == repos[i].ID { | |||
repos[i].PrimaryLanguage = st | |||
break | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
// LoadAttributes loads the attributes for the given RepositoryList | |||
func (repos RepositoryList) LoadAttributes() error { | |||
return repos.loadAttributes(db.DefaultContext) | |||
} | |||
// SearchRepoOptions holds the search options | |||
type SearchRepoOptions struct { | |||
db.ListOptions | |||
Actor *user_model.User | |||
Keyword string | |||
OwnerID int64 | |||
PriorityOwnerID int64 | |||
TeamID int64 | |||
OrderBy db.SearchOrderBy | |||
Private bool // Include private repositories in results | |||
StarredByID int64 | |||
WatchedByID int64 | |||
AllPublic bool // Include also all public repositories of users and public organisations | |||
AllLimited bool // Include also all public repositories of limited organisations | |||
// None -> include public and private | |||
// True -> include just private | |||
// False -> include just public | |||
IsPrivate util.OptionalBool | |||
// None -> include collaborative AND non-collaborative | |||
// True -> include just collaborative | |||
// False -> include just non-collaborative | |||
Collaborate util.OptionalBool | |||
// None -> include forks AND non-forks | |||
// True -> include just forks | |||
// False -> include just non-forks | |||
Fork util.OptionalBool | |||
// None -> include templates AND non-templates | |||
// True -> include just templates | |||
// False -> include just non-templates | |||
Template util.OptionalBool | |||
// None -> include mirrors AND non-mirrors | |||
// True -> include just mirrors | |||
// False -> include just non-mirrors | |||
Mirror util.OptionalBool | |||
// None -> include archived AND non-archived | |||
// True -> include just archived | |||
// False -> include just non-archived | |||
Archived util.OptionalBool | |||
// only search topic name | |||
TopicOnly bool | |||
// only search repositories with specified primary language | |||
Language string | |||
// include description in keyword search | |||
IncludeDescription bool | |||
// None -> include has milestones AND has no milestone | |||
// True -> include just has milestones | |||
// False -> include just has no milestone | |||
HasMilestones util.OptionalBool | |||
// LowerNames represents valid lower names to restrict to | |||
LowerNames []string | |||
} | |||
// SearchOrderBy is used to sort the result | |||
type SearchOrderBy string | |||
func (s SearchOrderBy) String() string { | |||
return string(s) | |||
} | |||
// Strings for sorting result | |||
const ( | |||
SearchOrderByAlphabetically SearchOrderBy = "name ASC" | |||
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" | |||
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" | |||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" | |||
SearchOrderByOldest SearchOrderBy = "created_unix ASC" | |||
SearchOrderByNewest SearchOrderBy = "created_unix DESC" | |||
SearchOrderBySize SearchOrderBy = "size ASC" | |||
SearchOrderBySizeReverse SearchOrderBy = "size DESC" | |||
SearchOrderByID SearchOrderBy = "id ASC" | |||
SearchOrderByIDReverse SearchOrderBy = "id DESC" | |||
SearchOrderByStars SearchOrderBy = "num_stars ASC" | |||
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" | |||
SearchOrderByForks SearchOrderBy = "num_forks ASC" | |||
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | |||
) | |||
// UserOwnedRepoCond returns user ownered repositories | |||
func UserOwnedRepoCond(userID int64) builder.Cond { | |||
return builder.Eq{ | |||
"repository.owner_id": userID, | |||
} | |||
} | |||
// UserAssignedRepoCond return user as assignee repositories list | |||
func UserAssignedRepoCond(id string, userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{ | |||
"repository.is_private": false, | |||
}, | |||
builder.In(id, | |||
builder.Select("issue.repo_id").From("issue_assignees"). | |||
InnerJoin("issue", "issue.id = issue_assignees.issue_id"). | |||
Where(builder.Eq{ | |||
"issue_assignees.assignee_id": userID, | |||
}), | |||
), | |||
) | |||
} | |||
// UserCreateIssueRepoCond return user created issues repositories list | |||
func UserCreateIssueRepoCond(id string, userID int64, isPull bool) builder.Cond { | |||
return builder.And( | |||
builder.Eq{ | |||
"repository.is_private": false, | |||
}, | |||
builder.In(id, | |||
builder.Select("issue.repo_id").From("issue"). | |||
Where(builder.Eq{ | |||
"issue.poster_id": userID, | |||
"issue.is_pull": isPull, | |||
}), | |||
), | |||
) | |||
} | |||
// UserMentionedRepoCond return user metinoed repositories list | |||
func UserMentionedRepoCond(id string, userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{ | |||
"repository.is_private": false, | |||
}, | |||
builder.In(id, | |||
builder.Select("issue.repo_id").From("issue_user"). | |||
InnerJoin("issue", "issue.id = issue_user.issue_id"). | |||
Where(builder.Eq{ | |||
"issue_user.is_mentioned": true, | |||
"issue_user.uid": userID, | |||
}), | |||
), | |||
) | |||
} | |||
// UserCollaborationRepoCond returns user as collabrators repositories list | |||
func UserCollaborationRepoCond(idStr string, userID int64) builder.Cond { | |||
return builder.In(idStr, builder.Select("repo_id"). | |||
From("`access`"). | |||
Where(builder.And( | |||
builder.Eq{"`access`.user_id": userID}, | |||
builder.Gt{"`access`.mode": int(perm.AccessModeNone)}, | |||
)), | |||
) | |||
} | |||
// userOrgTeamRepoCond selects repos that the given user has access to through team membership | |||
func userOrgTeamRepoCond(idStr string, userID int64) builder.Cond { | |||
return builder.In(idStr, userOrgTeamRepoBuilder(userID)) | |||
} | |||
// userOrgTeamRepoBuilder returns repo ids where user's teams can access. | |||
func userOrgTeamRepoBuilder(userID int64) *builder.Builder { | |||
return builder.Select("`team_repo`.repo_id"). | |||
From("team_repo"). | |||
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"). | |||
Where(builder.Eq{"`team_user`.uid": userID}) | |||
} | |||
// userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit. | |||
func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder { | |||
return userOrgTeamRepoBuilder(userID). | |||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). | |||
Where(builder.Eq{"`team_unit`.`type`": unitType}) | |||
} | |||
// UserOrgUnitRepoCond selects repos that the given user has access to through org and the special unit | |||
func UserOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) builder.Cond { | |||
return builder.In(idStr, | |||
userOrgTeamUnitRepoBuilder(userID, unitType). | |||
And(builder.Eq{"`team_unit`.org_id": orgID}), | |||
) | |||
} | |||
// userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations | |||
func userOrgPublicRepoCond(userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{"`repository`.is_private": false}, | |||
builder.In("`repository`.owner_id", | |||
builder.Select("`org_user`.org_id"). | |||
From("org_user"). | |||
Where(builder.Eq{"`org_user`.uid": userID}), | |||
), | |||
) | |||
} | |||
// userOrgPublicRepoCondPrivate returns the condition that one user could access all public repositories in private organizations | |||
func userOrgPublicRepoCondPrivate(userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{"`repository`.is_private": false}, | |||
builder.In("`repository`.owner_id", | |||
builder.Select("`org_user`.org_id"). | |||
From("org_user"). | |||
Join("INNER", "`user`", "`user`.id = `org_user`.org_id"). | |||
Where(builder.Eq{ | |||
"`org_user`.uid": userID, | |||
"`user`.`type`": user_model.UserTypeOrganization, | |||
"`user`.visibility": structs.VisibleTypePrivate, | |||
}), | |||
), | |||
) | |||
} | |||
// UserOrgPublicUnitRepoCond returns the condition that one user could access all public repositories in the special organization | |||
func UserOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond { | |||
return userOrgPublicRepoCond(userID). | |||
And(builder.Eq{"`repository`.owner_id": orgID}) | |||
} | |||
// SearchRepositoryCondition creates a query condition according search repository options | |||
func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||
cond := builder.NewCond() | |||
if opts.Private { | |||
if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID { | |||
// OK we're in the context of a User | |||
cond = cond.And(AccessibleRepositoryCondition(opts.Actor)) | |||
} | |||
} else { | |||
// Not looking at private organisations and users | |||
// We should be able to see all non-private repositories that | |||
// isn't in a private or limited organisation. | |||
cond = cond.And( | |||
builder.Eq{"is_private": false}, | |||
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where( | |||
builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}), | |||
))) | |||
} | |||
if opts.IsPrivate != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) | |||
} | |||
if opts.Template != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) | |||
} | |||
// Restrict to starred repositories | |||
if opts.StarredByID > 0 { | |||
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) | |||
} | |||
// Restrict to watched repositories | |||
if opts.WatchedByID > 0 { | |||
cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID}))) | |||
} | |||
// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate | |||
if opts.OwnerID > 0 { | |||
accessCond := builder.NewCond() | |||
if opts.Collaborate != util.OptionalBoolTrue { | |||
accessCond = builder.Eq{"owner_id": opts.OwnerID} | |||
} | |||
if opts.Collaborate != util.OptionalBoolFalse { | |||
// A Collaboration is: | |||
collaborateCond := builder.And( | |||
// 1. Repository we don't own | |||
builder.Neq{"owner_id": opts.OwnerID}, | |||
// 2. But we can see because of: | |||
builder.Or( | |||
// A. We have access | |||
UserCollaborationRepoCond("`repository`.id", opts.OwnerID), | |||
// B. We are in a team for | |||
userOrgTeamRepoCond("`repository`.id", opts.OwnerID), | |||
// C. Public repositories in organizations that we are member of | |||
userOrgPublicRepoCondPrivate(opts.OwnerID), | |||
), | |||
) | |||
if !opts.Private { | |||
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false)) | |||
} | |||
accessCond = accessCond.Or(collaborateCond) | |||
} | |||
if opts.AllPublic { | |||
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})))) | |||
} | |||
if opts.AllLimited { | |||
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})))) | |||
} | |||
cond = cond.And(accessCond) | |||
} | |||
if opts.TeamID > 0 { | |||
cond = cond.And(builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").From("team_repo").Where(builder.Eq{"`team_repo`.team_id": opts.TeamID}))) | |||
} | |||
if opts.Keyword != "" { | |||
// separate keyword | |||
subQueryCond := builder.NewCond() | |||
for _, v := range strings.Split(opts.Keyword, ",") { | |||
if opts.TopicOnly { | |||
subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)}) | |||
} else { | |||
subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)}) | |||
} | |||
} | |||
subQuery := builder.Select("repo_topic.repo_id").From("repo_topic"). | |||
Join("INNER", "topic", "topic.id = repo_topic.topic_id"). | |||
Where(subQueryCond). | |||
GroupBy("repo_topic.repo_id") | |||
keywordCond := builder.In("id", subQuery) | |||
if !opts.TopicOnly { | |||
likes := builder.NewCond() | |||
for _, v := range strings.Split(opts.Keyword, ",") { | |||
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) | |||
// If the string looks like "org/repo", match against that pattern too | |||
if opts.TeamID == 0 && strings.Count(opts.Keyword, "/") == 1 { | |||
pieces := strings.Split(opts.Keyword, "/") | |||
ownerName := pieces[0] | |||
repoName := pieces[1] | |||
likes = likes.Or(builder.And(builder.Like{"owner_name", strings.ToLower(ownerName)}, builder.Like{"lower_name", strings.ToLower(repoName)})) | |||
} | |||
if opts.IncludeDescription { | |||
likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) | |||
} | |||
} | |||
keywordCond = keywordCond.Or(likes) | |||
} | |||
cond = cond.And(keywordCond) | |||
} | |||
if opts.Language != "" { | |||
cond = cond.And(builder.In("id", builder. | |||
Select("repo_id"). | |||
From("language_stat"). | |||
Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true}))) | |||
} | |||
if opts.Fork != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) | |||
} | |||
if opts.Mirror != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) | |||
} | |||
if opts.Actor != nil && opts.Actor.IsRestricted { | |||
cond = cond.And(AccessibleRepositoryCondition(opts.Actor)) | |||
} | |||
if opts.Archived != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue}) | |||
} | |||
switch opts.HasMilestones { | |||
case util.OptionalBoolTrue: | |||
cond = cond.And(builder.Gt{"num_milestones": 0}) | |||
case util.OptionalBoolFalse: | |||
cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) | |||
} | |||
return cond | |||
} | |||
// SearchRepository returns repositories based on search options, | |||
// it returns results in given range and number of total results. | |||
func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { | |||
cond := SearchRepositoryCondition(opts) | |||
return SearchRepositoryByCondition(opts, cond, true) | |||
} | |||
// SearchRepositoryByCondition search repositories by condition | |||
func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) { | |||
ctx := db.DefaultContext | |||
sess, count, err := searchRepositoryByCondition(ctx, opts, cond) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
defaultSize := 50 | |||
if opts.PageSize > 0 { | |||
defaultSize = opts.PageSize | |||
} | |||
repos := make(RepositoryList, 0, defaultSize) | |||
if err := sess.Find(&repos); err != nil { | |||
return nil, 0, fmt.Errorf("Repo: %v", err) | |||
} | |||
if opts.PageSize <= 0 { | |||
count = int64(len(repos)) | |||
} | |||
if loadAttributes { | |||
if err := repos.loadAttributes(ctx); err != nil { | |||
return nil, 0, fmt.Errorf("LoadAttributes: %v", err) | |||
} | |||
} | |||
return repos, count, nil | |||
} | |||
func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) { | |||
if opts.Page <= 0 { | |||
opts.Page = 1 | |||
} | |||
if len(opts.OrderBy) == 0 { | |||
opts.OrderBy = db.SearchOrderByAlphabetically | |||
} | |||
args := make([]interface{}, 0) | |||
if opts.PriorityOwnerID > 0 { | |||
opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", opts.OrderBy)) | |||
args = append(args, opts.PriorityOwnerID) | |||
} else if strings.Count(opts.Keyword, "/") == 1 { | |||
// With "owner/repo" search times, prioritise results which match the owner field | |||
orgName := strings.Split(opts.Keyword, "/")[0] | |||
opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", opts.OrderBy)) | |||
args = append(args, orgName) | |||
} | |||
sess := db.GetEngine(ctx) | |||
var count int64 | |||
if opts.PageSize > 0 { | |||
var err error | |||
count, err = sess. | |||
Where(cond). | |||
Count(new(Repository)) | |||
if err != nil { | |||
return nil, 0, fmt.Errorf("Count: %v", err) | |||
} | |||
} | |||
sess = sess.Where(cond).OrderBy(opts.OrderBy.String(), args...) | |||
if opts.PageSize > 0 { | |||
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) | |||
} | |||
return sess, count, nil | |||
} | |||
// AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible | |||
func AccessibleRepositoryCondition(user *user_model.User) builder.Cond { | |||
cond := builder.NewCond() | |||
if user == nil || !user.IsRestricted || user.ID <= 0 { | |||
orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate} | |||
if user == nil || user.ID <= 0 { | |||
orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited) | |||
} | |||
// 1. Be able to see all non-private repositories that either: | |||
cond = cond.Or(builder.And( | |||
builder.Eq{"`repository`.is_private": false}, | |||
// 2. Aren't in an private organisation or limited organisation if we're not logged in | |||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where( | |||
builder.And( | |||
builder.Eq{"type": user_model.UserTypeOrganization}, | |||
builder.In("visibility", orgVisibilityLimit)), | |||
)))) | |||
} | |||
if user != nil { | |||
cond = cond.Or( | |||
// 2. Be able to see all repositories that we have access to | |||
UserCollaborationRepoCond("`repository`.id", user.ID), | |||
// 3. Repositories that we directly own | |||
builder.Eq{"`repository`.owner_id": user.ID}, | |||
// 4. Be able to see all repositories that we are in a team | |||
userOrgTeamRepoCond("`repository`.id", user.ID), | |||
// 5. Be able to see all public repos in private organizations that we are an org_user of | |||
userOrgPublicRepoCond(user.ID), | |||
) | |||
} | |||
return cond | |||
} | |||
// SearchRepositoryByName takes keyword and part of repository name to search, | |||
// it returns results in given range and number of total results. | |||
func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { | |||
opts.IncludeDescription = false | |||
return SearchRepository(opts) | |||
} | |||
// SearchRepositoryIDs takes keyword and part of repository name to search, | |||
// it returns results in given range and number of total results. | |||
func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { | |||
opts.IncludeDescription = false | |||
cond := SearchRepositoryCondition(opts) | |||
sess, count, err := searchRepositoryByCondition(db.DefaultContext, opts, cond) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
defaultSize := 50 | |||
if opts.PageSize > 0 { | |||
defaultSize = opts.PageSize | |||
} | |||
ids := make([]int64, 0, defaultSize) | |||
err = sess.Select("id").Table("repository").Find(&ids) | |||
if opts.PageSize <= 0 { | |||
count = int64(len(ids)) | |||
} | |||
return ids, count, err | |||
} | |||
// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered. | |||
func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder { | |||
// NB: Please note this code needs to still work if user is nil | |||
return builder.Select("id").From("repository").Where(AccessibleRepositoryCondition(user)) | |||
} | |||
// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id | |||
func FindUserAccessibleRepoIDs(user *user_model.User) ([]int64, error) { | |||
repoIDs := make([]int64, 0, 10) | |||
if err := db.GetEngine(db.DefaultContext). | |||
Table("repository"). | |||
Cols("id"). | |||
Where(AccessibleRepositoryCondition(user)). | |||
Find(&repoIDs); err != nil { | |||
return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err) | |||
} | |||
return repoIDs, nil | |||
} | |||
// GetUserRepositories returns a list of repositories of given user. | |||
func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) { | |||
if len(opts.OrderBy) == 0 { | |||
opts.OrderBy = "updated_unix DESC" | |||
} | |||
cond := builder.NewCond() | |||
cond = cond.And(builder.Eq{"owner_id": opts.Actor.ID}) | |||
if !opts.Private { | |||
cond = cond.And(builder.Eq{"is_private": false}) | |||
} | |||
if opts.LowerNames != nil && len(opts.LowerNames) > 0 { | |||
cond = cond.And(builder.In("lower_name", opts.LowerNames)) | |||
} | |||
sess := db.GetEngine(db.DefaultContext) | |||
count, err := sess.Where(cond).Count(new(Repository)) | |||
if err != nil { | |||
return nil, 0, fmt.Errorf("Count: %v", err) | |||
} | |||
sess = sess.Where(cond).OrderBy(opts.OrderBy.String()) | |||
repos := make(RepositoryList, 0, opts.PageSize) | |||
return repos, count, db.SetSessionPagination(sess, opts).Find(&repos) | |||
} |
@@ -2,13 +2,14 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package models | |||
package repo_test | |||
import ( | |||
"strings" | |||
"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/util" | |||
@@ -19,7 +20,7 @@ func TestSearchRepository(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
// test search public repository on explore page | |||
repos, count, err := SearchRepositoryByName(&SearchRepoOptions{ | |||
repos, count, err := repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: 1, | |||
PageSize: 10, | |||
@@ -34,7 +35,7 @@ func TestSearchRepository(t *testing.T) { | |||
} | |||
assert.Equal(t, int64(1), count) | |||
repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: 1, | |||
PageSize: 10, | |||
@@ -48,7 +49,7 @@ func TestSearchRepository(t *testing.T) { | |||
assert.Len(t, repos, 2) | |||
// test search private repository on explore page | |||
repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: 1, | |||
PageSize: 10, | |||
@@ -64,7 +65,7 @@ func TestSearchRepository(t *testing.T) { | |||
} | |||
assert.Equal(t, int64(1), count) | |||
repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: 1, | |||
PageSize: 10, | |||
@@ -79,14 +80,14 @@ func TestSearchRepository(t *testing.T) { | |||
assert.Len(t, repos, 3) | |||
// Test non existing owner | |||
repos, count, err = SearchRepositoryByName(&SearchRepoOptions{OwnerID: unittest.NonexistentID}) | |||
repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{OwnerID: unittest.NonexistentID}) | |||
assert.NoError(t, err) | |||
assert.Empty(t, repos) | |||
assert.Equal(t, int64(0), count) | |||
// Test search within description | |||
repos, count, err = SearchRepository(&SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: 1, | |||
PageSize: 10, | |||
@@ -103,7 +104,7 @@ func TestSearchRepository(t *testing.T) { | |||
assert.Equal(t, int64(1), count) | |||
// Test NOT search within description | |||
repos, count, err = SearchRepository(&SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: 1, | |||
PageSize: 10, | |||
@@ -119,164 +120,164 @@ func TestSearchRepository(t *testing.T) { | |||
testCases := []struct { | |||
name string | |||
opts *SearchRepoOptions | |||
opts *repo_model.SearchRepoOptions | |||
count int | |||
}{ | |||
{ | |||
name: "PublicRepositoriesByName", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, | |||
count: 7, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesByName", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 14, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 14, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 14, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 14, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 14, | |||
}, | |||
{ | |||
name: "PublicRepositoriesOfUser", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, | |||
count: 2, | |||
}, | |||
{ | |||
name: "PublicRepositoriesOfUser2", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, | |||
count: 0, | |||
}, | |||
{ | |||
name: "PublicRepositoriesOfUser3", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, | |||
count: 2, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesOfUser", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 4, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesOfUser2", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 0, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesOfUser3", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 4, | |||
}, | |||
{ | |||
name: "PublicRepositoriesOfUserIncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15}, | |||
count: 5, | |||
}, | |||
{ | |||
name: "PublicRepositoriesOfUser2IncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18}, | |||
count: 1, | |||
}, | |||
{ | |||
name: "PublicRepositoriesOfUser3IncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20}, | |||
count: 3, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesOfUserIncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true}, | |||
count: 9, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesOfUser2IncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true}, | |||
count: 4, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesOfUser3IncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true}, | |||
count: 7, | |||
}, | |||
{ | |||
name: "PublicRepositoriesOfOrganization", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, | |||
count: 1, | |||
}, | |||
{ | |||
name: "PublicAndPrivateRepositoriesOfOrganization", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 2, | |||
}, | |||
{ | |||
name: "AllPublic/PublicRepositoriesByName", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 7, | |||
}, | |||
{ | |||
name: "AllPublic/PublicAndPrivateRepositoriesByName", | |||
opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, | |||
count: 14, | |||
}, | |||
{ | |||
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, | |||
count: 28, | |||
}, | |||
{ | |||
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, | |||
count: 33, | |||
}, | |||
{ | |||
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", | |||
opts: &SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true}, | |||
count: 15, | |||
}, | |||
{ | |||
name: "AllPublic/PublicAndPrivateRepositoriesOfUser2IncludingCollaborativeByName", | |||
opts: &SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true}, | |||
count: 13, | |||
}, | |||
{ | |||
name: "AllPublic/PublicRepositoriesOfOrganization", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, | |||
count: 28, | |||
}, | |||
{ | |||
name: "AllTemplates", | |||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, | |||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, | |||
count: 2, | |||
}, | |||
{ | |||
name: "OwnerSlashRepoSearch", | |||
opts: &SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, | |||
count: 3, | |||
}, | |||
{ | |||
name: "OwnerSlashSearch", | |||
opts: &SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, | |||
opts: &repo_model.SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, | |||
count: 4, | |||
}, | |||
} | |||
for _, testCase := range testCases { | |||
t.Run(testCase.name, func(t *testing.T) { | |||
repos, count, err := SearchRepositoryByName(testCase.opts) | |||
repos, count, err := repo_model.SearchRepositoryByName(testCase.opts) | |||
assert.NoError(t, err) | |||
assert.Equal(t, int64(testCase.count), count) | |||
@@ -354,29 +355,29 @@ func TestSearchRepositoryByTopicName(t *testing.T) { | |||
testCases := []struct { | |||
name string | |||
opts *SearchRepoOptions | |||
opts *repo_model.SearchRepoOptions | |||
count int | |||
}{ | |||
{ | |||
name: "AllPublic/SearchPublicRepositoriesFromTopicAndName", | |||
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"}, | |||
opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"}, | |||
count: 2, | |||
}, | |||
{ | |||
name: "AllPublic/OnlySearchPublicRepositoriesFromTopic", | |||
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true}, | |||
opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true}, | |||
count: 1, | |||
}, | |||
{ | |||
name: "AllPublic/OnlySearchMultipleKeywordPublicRepositoriesFromTopic", | |||
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql,golang", TopicOnly: true}, | |||
opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql,golang", TopicOnly: true}, | |||
count: 2, | |||
}, | |||
} | |||
for _, testCase := range testCases { | |||
t.Run(testCase.name, func(t *testing.T) { | |||
_, count, err := SearchRepositoryByName(testCase.opts) | |||
_, count, err := repo_model.SearchRepositoryByName(testCase.opts) | |||
assert.NoError(t, err) | |||
assert.Equal(t, int64(testCase.count), count) | |||
}) |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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/util" | |||
@@ -15,18 +16,18 @@ import ( | |||
) | |||
var ( | |||
countRepospts = CountRepositoryOptions{OwnerID: 10} | |||
countReposptsPublic = CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolFalse} | |||
countReposptsPrivate = CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolTrue} | |||
countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10} | |||
countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolFalse} | |||
countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolTrue} | |||
) | |||
func TestGetRepositoryCount(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
ctx := db.DefaultContext | |||
count, err1 := CountRepositories(ctx, countRepospts) | |||
privateCount, err2 := CountRepositories(ctx, countReposptsPrivate) | |||
publicCount, err3 := CountRepositories(ctx, countReposptsPublic) | |||
count, err1 := repo_model.CountRepositories(ctx, countRepospts) | |||
privateCount, err2 := repo_model.CountRepositories(ctx, countReposptsPrivate) | |||
publicCount, err3 := repo_model.CountRepositories(ctx, countReposptsPublic) | |||
assert.NoError(t, err1) | |||
assert.NoError(t, err2) | |||
assert.NoError(t, err3) | |||
@@ -37,7 +38,7 @@ func TestGetRepositoryCount(t *testing.T) { | |||
func TestGetPublicRepositoryCount(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
count, err := CountRepositories(db.DefaultContext, countReposptsPublic) | |||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPublic) | |||
assert.NoError(t, err) | |||
assert.Equal(t, int64(1), count) | |||
} | |||
@@ -45,14 +46,14 @@ func TestGetPublicRepositoryCount(t *testing.T) { | |||
func TestGetPrivateRepositoryCount(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
count, err := CountRepositories(db.DefaultContext, countReposptsPrivate) | |||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPrivate) | |||
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) | |||
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()) | |||
} |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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" | |||
@@ -17,26 +18,26 @@ func TestStarRepo(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
const userID = 2 | |||
const repoID = 1 | |||
unittest.AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) | |||
assert.NoError(t, StarRepo(userID, repoID, true)) | |||
unittest.AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) | |||
assert.NoError(t, StarRepo(userID, repoID, true)) | |||
unittest.AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) | |||
assert.NoError(t, StarRepo(userID, repoID, false)) | |||
unittest.AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) | |||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) | |||
assert.NoError(t, repo_model.StarRepo(userID, repoID, true)) | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) | |||
assert.NoError(t, repo_model.StarRepo(userID, repoID, true)) | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) | |||
assert.NoError(t, repo_model.StarRepo(userID, repoID, false)) | |||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) | |||
} | |||
func TestIsStaring(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
assert.True(t, IsStaring(db.DefaultContext, 2, 4)) | |||
assert.False(t, IsStaring(db.DefaultContext, 3, 4)) | |||
assert.True(t, repo_model.IsStaring(db.DefaultContext, 2, 4)) | |||
assert.False(t, repo_model.IsStaring(db.DefaultContext, 3, 4)) | |||
} | |||
func TestRepository_GetStargazers(t *testing.T) { | |||
// repo with stargazers | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) | |||
gazers, err := GetStargazers(repo, db.ListOptions{Page: 0}) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository) | |||
gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0}) | |||
assert.NoError(t, err) | |||
if assert.Len(t, gazers, 1) { | |||
assert.Equal(t, int64(2), gazers[0].ID) | |||
@@ -46,8 +47,8 @@ func TestRepository_GetStargazers(t *testing.T) { | |||
func TestRepository_GetStargazers2(t *testing.T) { | |||
// repo with stargazers | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | |||
gazers, err := GetStargazers(repo, db.ListOptions{Page: 0}) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) | |||
gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0}) | |||
assert.NoError(t, err) | |||
assert.Len(t, gazers, 0) | |||
} |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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" | |||
@@ -19,47 +20,47 @@ func TestAddTopic(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
topics, _, err := FindTopics(&FindTopicOptions{}) | |||
topics, _, err := repo_model.FindTopics(&repo_model.FindTopicOptions{}) | |||
assert.NoError(t, err) | |||
assert.Len(t, topics, totalNrOfTopics) | |||
topics, total, err := FindTopics(&FindTopicOptions{ | |||
topics, total, err := repo_model.FindTopics(&repo_model.FindTopicOptions{ | |||
ListOptions: db.ListOptions{Page: 1, PageSize: 2}, | |||
}) | |||
assert.NoError(t, err) | |||
assert.Len(t, topics, 2) | |||
assert.EqualValues(t, 6, total) | |||
topics, _, err = FindTopics(&FindTopicOptions{ | |||
topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ | |||
RepoID: 1, | |||
}) | |||
assert.NoError(t, err) | |||
assert.Len(t, topics, repo1NrOfTopics) | |||
assert.NoError(t, SaveTopics(2, "golang")) | |||
assert.NoError(t, repo_model.SaveTopics(2, "golang")) | |||
repo2NrOfTopics := 1 | |||
topics, _, err = FindTopics(&FindTopicOptions{}) | |||
topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{}) | |||
assert.NoError(t, err) | |||
assert.Len(t, topics, totalNrOfTopics) | |||
topics, _, err = FindTopics(&FindTopicOptions{ | |||
topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ | |||
RepoID: 2, | |||
}) | |||
assert.NoError(t, err) | |||
assert.Len(t, topics, repo2NrOfTopics) | |||
assert.NoError(t, SaveTopics(2, "golang", "gitea")) | |||
assert.NoError(t, repo_model.SaveTopics(2, "golang", "gitea")) | |||
repo2NrOfTopics = 2 | |||
totalNrOfTopics++ | |||
topic, err := GetTopicByName("gitea") | |||
topic, err := repo_model.GetTopicByName("gitea") | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, 1, topic.RepoCount) | |||
topics, _, err = FindTopics(&FindTopicOptions{}) | |||
topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{}) | |||
assert.NoError(t, err) | |||
assert.Len(t, topics, totalNrOfTopics) | |||
topics, _, err = FindTopics(&FindTopicOptions{ | |||
topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ | |||
RepoID: 2, | |||
}) | |||
assert.NoError(t, err) | |||
@@ -67,14 +68,14 @@ func TestAddTopic(t *testing.T) { | |||
} | |||
func TestTopicValidator(t *testing.T) { | |||
assert.True(t, ValidateTopic("12345")) | |||
assert.True(t, ValidateTopic("2-test")) | |||
assert.True(t, ValidateTopic("test-3")) | |||
assert.True(t, ValidateTopic("first")) | |||
assert.True(t, ValidateTopic("second-test-topic")) | |||
assert.True(t, ValidateTopic("third-project-topic-with-max-length")) | |||
assert.False(t, ValidateTopic("$fourth-test,topic")) | |||
assert.False(t, ValidateTopic("-fifth-test-topic")) | |||
assert.False(t, ValidateTopic("sixth-go-project-topic-with-excess-length")) | |||
assert.True(t, repo_model.ValidateTopic("12345")) | |||
assert.True(t, repo_model.ValidateTopic("2-test")) | |||
assert.True(t, repo_model.ValidateTopic("test-3")) | |||
assert.True(t, repo_model.ValidateTopic("first")) | |||
assert.True(t, repo_model.ValidateTopic("second-test-topic")) | |||
assert.True(t, repo_model.ValidateTopic("third-project-topic-with-max-length")) | |||
assert.False(t, repo_model.ValidateTopic("$fourth-test,topic")) | |||
assert.False(t, repo_model.ValidateTopic("-fifth-test-topic")) | |||
assert.False(t, repo_model.ValidateTopic("sixth-go-project-topic-with-excess-length")) | |||
} |
@@ -172,3 +172,11 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s | |||
return committer.Commit() | |||
} | |||
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize | |||
func UpdateRepoSize(ctx context.Context, repoID, size int64) error { | |||
_, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{ | |||
Size: size, | |||
}) | |||
return err | |||
} |
@@ -5,7 +5,14 @@ | |||
package repo | |||
import ( | |||
"context" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/perm" | |||
user_model "code.gitea.io/gitea/models/user" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"xorm.io/builder" | |||
) | |||
// GetStarredRepos returns the repos starred by a particular user | |||
@@ -48,3 +55,118 @@ func GetWatchedRepos(userID int64, private bool, listOptions db.ListOptions) ([] | |||
total, err := sess.FindAndCount(&repos) | |||
return repos, total, err | |||
} | |||
// GetRepoAssignees returns all users that have write access and can be assigned to issues | |||
// of the repository, | |||
func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.User, err error) { | |||
if err = repo.GetOwner(ctx); err != nil { | |||
return nil, err | |||
} | |||
e := db.GetEngine(ctx) | |||
userIDs := make([]int64, 0, 10) | |||
if err = e.Table("access"). | |||
Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite). | |||
Select("user_id"). | |||
Find(&userIDs); err != nil { | |||
return nil, err | |||
} | |||
additionalUserIDs := make([]int64, 0, 10) | |||
if err = e.Table("team_user"). | |||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). | |||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). | |||
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). | |||
Distinct("`team_user`.uid"). | |||
Select("`team_user`.uid"). | |||
Find(&additionalUserIDs); err != nil { | |||
return nil, err | |||
} | |||
uidMap := map[int64]bool{} | |||
i := 0 | |||
for _, uid := range userIDs { | |||
if uidMap[uid] { | |||
continue | |||
} | |||
uidMap[uid] = true | |||
userIDs[i] = uid | |||
i++ | |||
} | |||
userIDs = userIDs[:i] | |||
userIDs = append(userIDs, additionalUserIDs...) | |||
for _, uid := range additionalUserIDs { | |||
if uidMap[uid] { | |||
continue | |||
} | |||
userIDs[i] = uid | |||
i++ | |||
} | |||
userIDs = userIDs[:i] | |||
// Leave a seat for owner itself to append later, but if owner is an organization | |||
// and just waste 1 unit is cheaper than re-allocate memory once. | |||
users := make([]*user_model.User, 0, len(userIDs)+1) | |||
if len(userIDs) > 0 { | |||
if err = e.In("id", userIDs).Find(&users); err != nil { | |||
return nil, err | |||
} | |||
} | |||
if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] { | |||
users = append(users, repo.Owner) | |||
} | |||
return users, nil | |||
} | |||
// GetReviewers get all users can be requested to review: | |||
// * for private repositories this returns all users that have read access or higher to the repository. | |||
// * for public repositories this returns all users that have read access or higher to the repository, | |||
// all repo watchers and all organization members. | |||
// TODO: may be we should have a busy choice for users to block review request to them. | |||
func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) ([]*user_model.User, error) { | |||
// Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries | |||
if err := repo.GetOwner(ctx); err != nil { | |||
return nil, err | |||
} | |||
cond := builder.And(builder.Neq{"`user`.id": posterID}) | |||
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate { | |||
// This a private repository: | |||
// Anyone who can read the repository is a requestable reviewer | |||
cond = cond.And(builder.In("`user`.id", | |||
builder.Select("user_id").From("access").Where( | |||
builder.Eq{"repo_id": repo.ID}. | |||
And(builder.Gte{"mode": perm.AccessModeRead}), | |||
), | |||
)) | |||
if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID { | |||
// as private *user* repos don't generate an entry in the `access` table, | |||
// the owner of a private repo needs to be explicitly added. | |||
cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) | |||
} | |||
} else { | |||
// This is a "public" repository: | |||
// Any user that has read access, is a watcher or organization member can be requested to review | |||
cond = cond.And(builder.And(builder.In("`user`.id", | |||
builder.Select("user_id").From("access"). | |||
Where(builder.Eq{"repo_id": repo.ID}. | |||
And(builder.Gte{"mode": perm.AccessModeRead})), | |||
).Or(builder.In("`user`.id", | |||
builder.Select("user_id").From("watch"). | |||
Where(builder.Eq{"repo_id": repo.ID}. | |||
And(builder.In("mode", WatchModeNormal, WatchModeAuto))), | |||
).Or(builder.In("`user`.id", | |||
builder.Select("uid").From("org_user"). | |||
Where(builder.Eq{"org_id": repo.OwnerID}), | |||
))))) | |||
} | |||
users := make([]*user_model.User, 0, 8) | |||
return users, db.GetEngine(ctx).Where(cond).OrderBy("name").Find(&users) | |||
} |
@@ -0,0 +1,74 @@ | |||
// 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_test | |||
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 TestRepoAssignees(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2) | |||
assert.NoError(t, err) | |||
assert.Len(t, users, 1) | |||
assert.Equal(t, users[0].ID, int64(2)) | |||
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository) | |||
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) | |||
assert.NoError(t, err) | |||
assert.Len(t, users, 3) | |||
assert.Equal(t, users[0].ID, int64(15)) | |||
assert.Equal(t, users[1].ID, int64(18)) | |||
assert.Equal(t, users[2].ID, int64(16)) | |||
} | |||
func TestRepoGetReviewers(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
// test public repo | |||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
ctx := db.DefaultContext | |||
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 4) | |||
// should include doer if doer is not PR poster. | |||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 4) | |||
// should not include PR poster, if PR poster would be otherwise eligible | |||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 3) | |||
// test private user repo | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
reviewers, err = repo_model.GetReviewers(ctx, repo2, 2, 4) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 1) | |||
assert.EqualValues(t, reviewers[0].ID, 2) | |||
// test private org repo | |||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) | |||
reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 1) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 2) | |||
reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 2) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 1) | |||
} |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
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" | |||
@@ -17,20 +18,20 @@ import ( | |||
func TestIsWatching(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
assert.True(t, IsWatching(1, 1)) | |||
assert.True(t, IsWatching(4, 1)) | |||
assert.True(t, IsWatching(11, 1)) | |||
assert.True(t, repo_model.IsWatching(1, 1)) | |||
assert.True(t, repo_model.IsWatching(4, 1)) | |||
assert.True(t, repo_model.IsWatching(11, 1)) | |||
assert.False(t, IsWatching(1, 5)) | |||
assert.False(t, IsWatching(8, 1)) | |||
assert.False(t, IsWatching(unittest.NonexistentID, unittest.NonexistentID)) | |||
assert.False(t, repo_model.IsWatching(1, 5)) | |||
assert.False(t, repo_model.IsWatching(8, 1)) | |||
assert.False(t, repo_model.IsWatching(unittest.NonexistentID, unittest.NonexistentID)) | |||
} | |||
func TestGetWatchers(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
watches, err := GetWatchers(db.DefaultContext, repo.ID) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
watches, err := repo_model.GetWatchers(db.DefaultContext, repo.ID) | |||
assert.NoError(t, err) | |||
// One watchers are inactive, thus minus 1 | |||
assert.Len(t, watches, repo.NumWatches-1) | |||
@@ -38,7 +39,7 @@ func TestGetWatchers(t *testing.T) { | |||
assert.EqualValues(t, repo.ID, watch.RepoID) | |||
} | |||
watches, err = GetWatchers(db.DefaultContext, unittest.NonexistentID) | |||
watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID) | |||
assert.NoError(t, err) | |||
assert.Len(t, watches, 0) | |||
} | |||
@@ -46,16 +47,16 @@ func TestGetWatchers(t *testing.T) { | |||
func TestRepository_GetWatchers(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
watchers, err := GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, repo.NumWatches) | |||
for _, watcher := range watchers { | |||
unittest.AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID}) | |||
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{UserID: watcher.ID, RepoID: repo.ID}) | |||
} | |||
repo = unittest.AssertExistsAndLoadBean(t, &Repository{ID: 9}).(*Repository) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}).(*repo_model.Repository) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, 0) | |||
} | |||
@@ -63,8 +64,8 @@ func TestRepository_GetWatchers(t *testing.T) { | |||
func TestWatchIfAuto(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
watchers, err := GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, repo.NumWatches) | |||
@@ -73,46 +74,46 @@ func TestWatchIfAuto(t *testing.T) { | |||
prevCount := repo.NumWatches | |||
// Must not add watch | |||
assert.NoError(t, WatchIfAuto(db.DefaultContext, 8, 1, true)) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 8, 1, true)) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, prevCount) | |||
// Should not add watch | |||
assert.NoError(t, WatchIfAuto(db.DefaultContext, 10, 1, true)) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 10, 1, true)) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, prevCount) | |||
setting.Service.AutoWatchOnChanges = true | |||
// Must not add watch | |||
assert.NoError(t, WatchIfAuto(db.DefaultContext, 8, 1, true)) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 8, 1, true)) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, prevCount) | |||
// Should not add watch | |||
assert.NoError(t, WatchIfAuto(db.DefaultContext, 12, 1, false)) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, false)) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, prevCount) | |||
// Should add watch | |||
assert.NoError(t, WatchIfAuto(db.DefaultContext, 12, 1, true)) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, true)) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, prevCount+1) | |||
// Should remove watch, inhibit from adding auto | |||
assert.NoError(t, WatchRepo(db.DefaultContext, 12, 1, false)) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, 12, 1, false)) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, prevCount) | |||
// Must not add watch | |||
assert.NoError(t, WatchIfAuto(db.DefaultContext, 12, 1, true)) | |||
watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, true)) | |||
watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) | |||
assert.NoError(t, err) | |||
assert.Len(t, watchers, prevCount) | |||
} | |||
@@ -120,20 +121,20 @@ func TestWatchIfAuto(t *testing.T) { | |||
func TestWatchRepoMode(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) | |||
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: WatchModeAuto}, 1) | |||
assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeAuto)) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeAuto}, 1) | |||
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: WatchModeNormal}, 1) | |||
assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNormal)) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeNormal}, 1) | |||
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: WatchModeDont}, 1) | |||
assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeDont)) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeDont}, 1) | |||
assert.NoError(t, WatchRepoMode(12, 1, WatchModeNone)) | |||
unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) | |||
assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNone)) | |||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) | |||
} |
@@ -2,12 +2,13 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package repo | |||
package repo_test | |||
import ( | |||
"path/filepath" | |||
"testing" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
"code.gitea.io/gitea/modules/setting" | |||
@@ -17,7 +18,7 @@ import ( | |||
func TestRepository_WikiCloneLink(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
cloneLink := repo.WikiCloneLink() | |||
assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH) | |||
assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS) | |||
@@ -26,20 +27,20 @@ func TestRepository_WikiCloneLink(t *testing.T) { | |||
func TestWikiPath(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git") | |||
assert.Equal(t, expected, WikiPath("user2", "repo1")) | |||
assert.Equal(t, expected, repo_model.WikiPath("user2", "repo1")) | |||
} | |||
func TestRepository_WikiPath(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git") | |||
assert.Equal(t, expected, repo.WikiPath()) | |||
} | |||
func TestRepository_HasWiki(t *testing.T) { | |||
unittest.PrepareTestEnv(t) | |||
repo1 := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
assert.True(t, repo1.HasWiki()) | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
assert.False(t, repo2.HasWiki()) | |||
} |
@@ -1,118 +0,0 @@ | |||
// Copyright 2019 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 ( | |||
"bufio" | |||
"bytes" | |||
"context" | |||
"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/log" | |||
"github.com/gobwas/glob" | |||
) | |||
// GenerateRepoOptions contains the template units to generate | |||
type GenerateRepoOptions struct { | |||
Name string | |||
DefaultBranch string | |||
Description string | |||
Private bool | |||
GitContent bool | |||
Topics bool | |||
GitHooks bool | |||
Webhooks bool | |||
Avatar bool | |||
IssueLabels bool | |||
} | |||
// IsValid checks whether at least one option is chosen for generation | |||
func (gro GenerateRepoOptions) IsValid() bool { | |||
return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels // or other items as they are added | |||
} | |||
// GiteaTemplate holds information about a .gitea/template file | |||
type GiteaTemplate struct { | |||
Path string | |||
Content []byte | |||
globs []glob.Glob | |||
} | |||
// Globs parses the .gitea/template globs or returns them if they were already parsed | |||
func (gt GiteaTemplate) Globs() []glob.Glob { | |||
if gt.globs != nil { | |||
return gt.globs | |||
} | |||
gt.globs = make([]glob.Glob, 0) | |||
scanner := bufio.NewScanner(bytes.NewReader(gt.Content)) | |||
for scanner.Scan() { | |||
line := strings.TrimSpace(scanner.Text()) | |||
if line == "" || strings.HasPrefix(line, "#") { | |||
continue | |||
} | |||
g, err := glob.Compile(line, '/') | |||
if err != nil { | |||
log.Info("Invalid glob expression '%s' (skipped): %v", line, err) | |||
continue | |||
} | |||
gt.globs = append(gt.globs, g) | |||
} | |||
return gt.globs | |||
} | |||
// GenerateWebhooks generates webhooks from a template repository | |||
func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { | |||
templateWebhooks, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{RepoID: templateRepo.ID}) | |||
if err != nil { | |||
return err | |||
} | |||
for _, templateWebhook := range templateWebhooks { | |||
generateWebhook := &webhook.Webhook{ | |||
RepoID: generateRepo.ID, | |||
URL: templateWebhook.URL, | |||
HTTPMethod: templateWebhook.HTTPMethod, | |||
ContentType: templateWebhook.ContentType, | |||
Secret: templateWebhook.Secret, | |||
HookEvent: templateWebhook.HookEvent, | |||
IsActive: templateWebhook.IsActive, | |||
Type: templateWebhook.Type, | |||
OrgID: templateWebhook.OrgID, | |||
Events: templateWebhook.Events, | |||
Meta: templateWebhook.Meta, | |||
} | |||
if err := webhook.CreateWebhook(ctx, generateWebhook); err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
// GenerateIssueLabels generates issue labels from a template repository | |||
func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { | |||
templateLabels, err := GetLabelsByRepoID(ctx, templateRepo.ID, "", db.ListOptions{}) | |||
if err != nil { | |||
return err | |||
} | |||
for _, templateLabel := range templateLabels { | |||
generateLabel := &Label{ | |||
RepoID: generateRepo.ID, | |||
Name: templateLabel.Name, | |||
Description: templateLabel.Description, | |||
Color: templateLabel.Color, | |||
} | |||
if err := db.Insert(ctx, generateLabel); err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} |
@@ -1,704 +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" | |||
"strings" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/organization" | |||
"code.gitea.io/gitea/models/perm" | |||
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/container" | |||
"code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/util" | |||
"xorm.io/builder" | |||
) | |||
// RepositoryListDefaultPageSize is the default number of repositories | |||
// to load in memory when running administrative tasks on all (or almost | |||
// all) of them. | |||
// The number should be low enough to avoid filling up all RAM with | |||
// repository data... | |||
const RepositoryListDefaultPageSize = 64 | |||
// RepositoryList contains a list of repositories | |||
type RepositoryList []*repo_model.Repository | |||
func (repos RepositoryList) Len() int { | |||
return len(repos) | |||
} | |||
func (repos RepositoryList) Less(i, j int) bool { | |||
return repos[i].FullName() < repos[j].FullName() | |||
} | |||
func (repos RepositoryList) Swap(i, j int) { | |||
repos[i], repos[j] = repos[j], repos[i] | |||
} | |||
// FIXME: Remove in favor of maps.values when MIN_GO_VERSION >= 1.18 | |||
func valuesRepository(m map[int64]*repo_model.Repository) []*repo_model.Repository { | |||
values := make([]*repo_model.Repository, 0, len(m)) | |||
for _, v := range m { | |||
values = append(values, v) | |||
} | |||
return values | |||
} | |||
// RepositoryListOfMap make list from values of map | |||
func RepositoryListOfMap(repoMap map[int64]*repo_model.Repository) RepositoryList { | |||
return RepositoryList(valuesRepository(repoMap)) | |||
} | |||
func (repos RepositoryList) loadAttributes(ctx context.Context) error { | |||
if len(repos) == 0 { | |||
return nil | |||
} | |||
set := make(map[int64]struct{}) | |||
repoIDs := make([]int64, len(repos)) | |||
for i := range repos { | |||
set[repos[i].OwnerID] = struct{}{} | |||
repoIDs[i] = repos[i].ID | |||
} | |||
// Load owners. | |||
users := make(map[int64]*user_model.User, len(set)) | |||
if err := db.GetEngine(ctx). | |||
Where("id > 0"). | |||
In("id", container.KeysInt64(set)). | |||
Find(&users); err != nil { | |||
return fmt.Errorf("find users: %v", err) | |||
} | |||
for i := range repos { | |||
repos[i].Owner = users[repos[i].OwnerID] | |||
} | |||
// Load primary language. | |||
stats := make(repo_model.LanguageStatList, 0, len(repos)) | |||
if err := db.GetEngine(ctx). | |||
Where("`is_primary` = ? AND `language` != ?", true, "other"). | |||
In("`repo_id`", repoIDs). | |||
Find(&stats); err != nil { | |||
return fmt.Errorf("find primary languages: %v", err) | |||
} | |||
stats.LoadAttributes() | |||
for i := range repos { | |||
for _, st := range stats { | |||
if st.RepoID == repos[i].ID { | |||
repos[i].PrimaryLanguage = st | |||
break | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
// LoadAttributes loads the attributes for the given RepositoryList | |||
func (repos RepositoryList) LoadAttributes() error { | |||
return repos.loadAttributes(db.DefaultContext) | |||
} | |||
// SearchRepoOptions holds the search options | |||
type SearchRepoOptions struct { | |||
db.ListOptions | |||
Actor *user_model.User | |||
Keyword string | |||
OwnerID int64 | |||
PriorityOwnerID int64 | |||
TeamID int64 | |||
OrderBy db.SearchOrderBy | |||
Private bool // Include private repositories in results | |||
StarredByID int64 | |||
WatchedByID int64 | |||
AllPublic bool // Include also all public repositories of users and public organisations | |||
AllLimited bool // Include also all public repositories of limited organisations | |||
// None -> include public and private | |||
// True -> include just private | |||
// False -> include just public | |||
IsPrivate util.OptionalBool | |||
// None -> include collaborative AND non-collaborative | |||
// True -> include just collaborative | |||
// False -> include just non-collaborative | |||
Collaborate util.OptionalBool | |||
// None -> include forks AND non-forks | |||
// True -> include just forks | |||
// False -> include just non-forks | |||
Fork util.OptionalBool | |||
// None -> include templates AND non-templates | |||
// True -> include just templates | |||
// False -> include just non-templates | |||
Template util.OptionalBool | |||
// None -> include mirrors AND non-mirrors | |||
// True -> include just mirrors | |||
// False -> include just non-mirrors | |||
Mirror util.OptionalBool | |||
// None -> include archived AND non-archived | |||
// True -> include just archived | |||
// False -> include just non-archived | |||
Archived util.OptionalBool | |||
// only search topic name | |||
TopicOnly bool | |||
// only search repositories with specified primary language | |||
Language string | |||
// include description in keyword search | |||
IncludeDescription bool | |||
// None -> include has milestones AND has no milestone | |||
// True -> include just has milestones | |||
// False -> include just has no milestone | |||
HasMilestones util.OptionalBool | |||
// LowerNames represents valid lower names to restrict to | |||
LowerNames []string | |||
} | |||
// SearchOrderBy is used to sort the result | |||
type SearchOrderBy string | |||
func (s SearchOrderBy) String() string { | |||
return string(s) | |||
} | |||
// Strings for sorting result | |||
const ( | |||
SearchOrderByAlphabetically SearchOrderBy = "name ASC" | |||
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" | |||
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" | |||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" | |||
SearchOrderByOldest SearchOrderBy = "created_unix ASC" | |||
SearchOrderByNewest SearchOrderBy = "created_unix DESC" | |||
SearchOrderBySize SearchOrderBy = "size ASC" | |||
SearchOrderBySizeReverse SearchOrderBy = "size DESC" | |||
SearchOrderByID SearchOrderBy = "id ASC" | |||
SearchOrderByIDReverse SearchOrderBy = "id DESC" | |||
SearchOrderByStars SearchOrderBy = "num_stars ASC" | |||
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" | |||
SearchOrderByForks SearchOrderBy = "num_forks ASC" | |||
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | |||
) | |||
// userOwnedRepoCond returns user ownered repositories | |||
func userOwnedRepoCond(userID int64) builder.Cond { | |||
return builder.Eq{ | |||
"repository.owner_id": userID, | |||
} | |||
} | |||
// userAssignedRepoCond return user as assignee repositories list | |||
func userAssignedRepoCond(id string, userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{ | |||
"repository.is_private": false, | |||
}, | |||
builder.In(id, | |||
builder.Select("issue.repo_id").From("issue_assignees"). | |||
InnerJoin("issue", "issue.id = issue_assignees.issue_id"). | |||
Where(builder.Eq{ | |||
"issue_assignees.assignee_id": userID, | |||
}), | |||
), | |||
) | |||
} | |||
// userCreateIssueRepoCond return user created issues repositories list | |||
func userCreateIssueRepoCond(id string, userID int64, isPull bool) builder.Cond { | |||
return builder.And( | |||
builder.Eq{ | |||
"repository.is_private": false, | |||
}, | |||
builder.In(id, | |||
builder.Select("issue.repo_id").From("issue"). | |||
Where(builder.Eq{ | |||
"issue.poster_id": userID, | |||
"issue.is_pull": isPull, | |||
}), | |||
), | |||
) | |||
} | |||
// userMentionedRepoCond return user metinoed repositories list | |||
func userMentionedRepoCond(id string, userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{ | |||
"repository.is_private": false, | |||
}, | |||
builder.In(id, | |||
builder.Select("issue.repo_id").From("issue_user"). | |||
InnerJoin("issue", "issue.id = issue_user.issue_id"). | |||
Where(builder.Eq{ | |||
"issue_user.is_mentioned": true, | |||
"issue_user.uid": userID, | |||
}), | |||
), | |||
) | |||
} | |||
// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access | |||
func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Type) builder.Cond { | |||
return builder.In(id, | |||
builder.Select("repo_id").From("team_repo").Where( | |||
builder.Eq{ | |||
"team_id": teamID, | |||
}.And( | |||
builder.Or( | |||
// Check if the user is member of the team. | |||
builder.In( | |||
"team_id", builder.Select("team_id").From("team_user").Where( | |||
builder.Eq{ | |||
"uid": userID, | |||
}, | |||
), | |||
), | |||
// Check if the user is in the owner team of the organisation. | |||
builder.Exists(builder.Select("team_id").From("team_user"). | |||
Where(builder.Eq{ | |||
"org_id": orgID, | |||
"team_id": builder.Select("id").From("team").Where( | |||
builder.Eq{ | |||
"org_id": orgID, | |||
"lower_name": strings.ToLower(organization.OwnerTeamName), | |||
}), | |||
"uid": userID, | |||
}), | |||
), | |||
)).And( | |||
builder.In( | |||
"team_id", builder.Select("team_id").From("team_unit").Where( | |||
builder.Eq{ | |||
"`team_unit`.org_id": orgID, | |||
}.And( | |||
builder.In("`team_unit`.type", units), | |||
), | |||
), | |||
), | |||
), | |||
)) | |||
} | |||
// userCollaborationRepoCond returns user as collabrators repositories list | |||
func userCollaborationRepoCond(idStr string, userID int64) builder.Cond { | |||
return builder.In(idStr, builder.Select("repo_id"). | |||
From("`access`"). | |||
Where(builder.And( | |||
builder.Eq{"`access`.user_id": userID}, | |||
builder.Gt{"`access`.mode": int(perm.AccessModeNone)}, | |||
)), | |||
) | |||
} | |||
// userOrgTeamRepoCond selects repos that the given user has access to through team membership | |||
func userOrgTeamRepoCond(idStr string, userID int64) builder.Cond { | |||
return builder.In(idStr, userOrgTeamRepoBuilder(userID)) | |||
} | |||
// userOrgTeamRepoBuilder returns repo ids where user's teams can access. | |||
func userOrgTeamRepoBuilder(userID int64) *builder.Builder { | |||
return builder.Select("`team_repo`.repo_id"). | |||
From("team_repo"). | |||
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"). | |||
Where(builder.Eq{"`team_user`.uid": userID}) | |||
} | |||
// userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit. | |||
func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder { | |||
return userOrgTeamRepoBuilder(userID). | |||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). | |||
Where(builder.Eq{"`team_unit`.`type`": unitType}) | |||
} | |||
// userOrgUnitRepoCond selects repos that the given user has access to through org and the special unit | |||
func userOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) builder.Cond { | |||
return builder.In(idStr, | |||
userOrgTeamUnitRepoBuilder(userID, unitType). | |||
And(builder.Eq{"`team_unit`.org_id": orgID}), | |||
) | |||
} | |||
// userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations | |||
func userOrgPublicRepoCond(userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{"`repository`.is_private": false}, | |||
builder.In("`repository`.owner_id", | |||
builder.Select("`org_user`.org_id"). | |||
From("org_user"). | |||
Where(builder.Eq{"`org_user`.uid": userID}), | |||
), | |||
) | |||
} | |||
// userOrgPublicRepoCondPrivate returns the condition that one user could access all public repositories in private organizations | |||
func userOrgPublicRepoCondPrivate(userID int64) builder.Cond { | |||
return builder.And( | |||
builder.Eq{"`repository`.is_private": false}, | |||
builder.In("`repository`.owner_id", | |||
builder.Select("`org_user`.org_id"). | |||
From("org_user"). | |||
Join("INNER", "`user`", "`user`.id = `org_user`.org_id"). | |||
Where(builder.Eq{ | |||
"`org_user`.uid": userID, | |||
"`user`.`type`": user_model.UserTypeOrganization, | |||
"`user`.visibility": structs.VisibleTypePrivate, | |||
}), | |||
), | |||
) | |||
} | |||
// userOrgPublicUnitRepoCond returns the condition that one user could access all public repositories in the special organization | |||
func userOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond { | |||
return userOrgPublicRepoCond(userID). | |||
And(builder.Eq{"`repository`.owner_id": orgID}) | |||
} | |||
// SearchRepositoryCondition creates a query condition according search repository options | |||
func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||
cond := builder.NewCond() | |||
if opts.Private { | |||
if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID { | |||
// OK we're in the context of a User | |||
cond = cond.And(accessibleRepositoryCondition(opts.Actor)) | |||
} | |||
} else { | |||
// Not looking at private organisations and users | |||
// We should be able to see all non-private repositories that | |||
// isn't in a private or limited organisation. | |||
cond = cond.And( | |||
builder.Eq{"is_private": false}, | |||
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where( | |||
builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}), | |||
))) | |||
} | |||
if opts.IsPrivate != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) | |||
} | |||
if opts.Template != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) | |||
} | |||
// Restrict to starred repositories | |||
if opts.StarredByID > 0 { | |||
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) | |||
} | |||
// Restrict to watched repositories | |||
if opts.WatchedByID > 0 { | |||
cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID}))) | |||
} | |||
// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate | |||
if opts.OwnerID > 0 { | |||
accessCond := builder.NewCond() | |||
if opts.Collaborate != util.OptionalBoolTrue { | |||
accessCond = builder.Eq{"owner_id": opts.OwnerID} | |||
} | |||
if opts.Collaborate != util.OptionalBoolFalse { | |||
// A Collaboration is: | |||
collaborateCond := builder.And( | |||
// 1. Repository we don't own | |||
builder.Neq{"owner_id": opts.OwnerID}, | |||
// 2. But we can see because of: | |||
builder.Or( | |||
// A. We have access | |||
userCollaborationRepoCond("`repository`.id", opts.OwnerID), | |||
// B. We are in a team for | |||
userOrgTeamRepoCond("`repository`.id", opts.OwnerID), | |||
// C. Public repositories in organizations that we are member of | |||
userOrgPublicRepoCondPrivate(opts.OwnerID), | |||
), | |||
) | |||
if !opts.Private { | |||
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false)) | |||
} | |||
accessCond = accessCond.Or(collaborateCond) | |||
} | |||
if opts.AllPublic { | |||
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})))) | |||
} | |||
if opts.AllLimited { | |||
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})))) | |||
} | |||
cond = cond.And(accessCond) | |||
} | |||
if opts.TeamID > 0 { | |||
cond = cond.And(builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").From("team_repo").Where(builder.Eq{"`team_repo`.team_id": opts.TeamID}))) | |||
} | |||
if opts.Keyword != "" { | |||
// separate keyword | |||
subQueryCond := builder.NewCond() | |||
for _, v := range strings.Split(opts.Keyword, ",") { | |||
if opts.TopicOnly { | |||
subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)}) | |||
} else { | |||
subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)}) | |||
} | |||
} | |||
subQuery := builder.Select("repo_topic.repo_id").From("repo_topic"). | |||
Join("INNER", "topic", "topic.id = repo_topic.topic_id"). | |||
Where(subQueryCond). | |||
GroupBy("repo_topic.repo_id") | |||
keywordCond := builder.In("id", subQuery) | |||
if !opts.TopicOnly { | |||
likes := builder.NewCond() | |||
for _, v := range strings.Split(opts.Keyword, ",") { | |||
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) | |||
// If the string looks like "org/repo", match against that pattern too | |||
if opts.TeamID == 0 && strings.Count(opts.Keyword, "/") == 1 { | |||
pieces := strings.Split(opts.Keyword, "/") | |||
ownerName := pieces[0] | |||
repoName := pieces[1] | |||
likes = likes.Or(builder.And(builder.Like{"owner_name", strings.ToLower(ownerName)}, builder.Like{"lower_name", strings.ToLower(repoName)})) | |||
} | |||
if opts.IncludeDescription { | |||
likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) | |||
} | |||
} | |||
keywordCond = keywordCond.Or(likes) | |||
} | |||
cond = cond.And(keywordCond) | |||
} | |||
if opts.Language != "" { | |||
cond = cond.And(builder.In("id", builder. | |||
Select("repo_id"). | |||
From("language_stat"). | |||
Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true}))) | |||
} | |||
if opts.Fork != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) | |||
} | |||
if opts.Mirror != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) | |||
} | |||
if opts.Actor != nil && opts.Actor.IsRestricted { | |||
cond = cond.And(accessibleRepositoryCondition(opts.Actor)) | |||
} | |||
if opts.Archived != util.OptionalBoolNone { | |||
cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue}) | |||
} | |||
switch opts.HasMilestones { | |||
case util.OptionalBoolTrue: | |||
cond = cond.And(builder.Gt{"num_milestones": 0}) | |||
case util.OptionalBoolFalse: | |||
cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) | |||
} | |||
return cond | |||
} | |||
// SearchRepository returns repositories based on search options, | |||
// it returns results in given range and number of total results. | |||
func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { | |||
cond := SearchRepositoryCondition(opts) | |||
return SearchRepositoryByCondition(opts, cond, true) | |||
} | |||
// SearchRepositoryByCondition search repositories by condition | |||
func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) { | |||
ctx := db.DefaultContext | |||
sess, count, err := searchRepositoryByCondition(ctx, opts, cond) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
defaultSize := 50 | |||
if opts.PageSize > 0 { | |||
defaultSize = opts.PageSize | |||
} | |||
repos := make(RepositoryList, 0, defaultSize) | |||
if err := sess.Find(&repos); err != nil { | |||
return nil, 0, fmt.Errorf("Repo: %v", err) | |||
} | |||
if opts.PageSize <= 0 { | |||
count = int64(len(repos)) | |||
} | |||
if loadAttributes { | |||
if err := repos.loadAttributes(ctx); err != nil { | |||
return nil, 0, fmt.Errorf("LoadAttributes: %v", err) | |||
} | |||
} | |||
return repos, count, nil | |||
} | |||
func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) { | |||
if opts.Page <= 0 { | |||
opts.Page = 1 | |||
} | |||
if len(opts.OrderBy) == 0 { | |||
opts.OrderBy = db.SearchOrderByAlphabetically | |||
} | |||
args := make([]interface{}, 0) | |||
if opts.PriorityOwnerID > 0 { | |||
opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", opts.OrderBy)) | |||
args = append(args, opts.PriorityOwnerID) | |||
} else if strings.Count(opts.Keyword, "/") == 1 { | |||
// With "owner/repo" search times, prioritise results which match the owner field | |||
orgName := strings.Split(opts.Keyword, "/")[0] | |||
opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", opts.OrderBy)) | |||
args = append(args, orgName) | |||
} | |||
sess := db.GetEngine(ctx) | |||
var count int64 | |||
if opts.PageSize > 0 { | |||
var err error | |||
count, err = sess. | |||
Where(cond). | |||
Count(new(repo_model.Repository)) | |||
if err != nil { | |||
return nil, 0, fmt.Errorf("Count: %v", err) | |||
} | |||
} | |||
sess = sess.Where(cond).OrderBy(opts.OrderBy.String(), args...) | |||
if opts.PageSize > 0 { | |||
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) | |||
} | |||
return sess, count, nil | |||
} | |||
// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible | |||
func accessibleRepositoryCondition(user *user_model.User) builder.Cond { | |||
cond := builder.NewCond() | |||
if user == nil || !user.IsRestricted || user.ID <= 0 { | |||
orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate} | |||
if user == nil || user.ID <= 0 { | |||
orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited) | |||
} | |||
// 1. Be able to see all non-private repositories that either: | |||
cond = cond.Or(builder.And( | |||
builder.Eq{"`repository`.is_private": false}, | |||
// 2. Aren't in an private organisation or limited organisation if we're not logged in | |||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where( | |||
builder.And( | |||
builder.Eq{"type": user_model.UserTypeOrganization}, | |||
builder.In("visibility", orgVisibilityLimit)), | |||
)))) | |||
} | |||
if user != nil { | |||
cond = cond.Or( | |||
// 2. Be able to see all repositories that we have access to | |||
userCollaborationRepoCond("`repository`.id", user.ID), | |||
// 3. Repositories that we directly own | |||
builder.Eq{"`repository`.owner_id": user.ID}, | |||
// 4. Be able to see all repositories that we are in a team | |||
userOrgTeamRepoCond("`repository`.id", user.ID), | |||
// 5. Be able to see all public repos in private organizations that we are an org_user of | |||
userOrgPublicRepoCond(user.ID), | |||
) | |||
} | |||
return cond | |||
} | |||
// SearchRepositoryByName takes keyword and part of repository name to search, | |||
// it returns results in given range and number of total results. | |||
func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { | |||
opts.IncludeDescription = false | |||
return SearchRepository(opts) | |||
} | |||
// SearchRepositoryIDs takes keyword and part of repository name to search, | |||
// it returns results in given range and number of total results. | |||
func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { | |||
opts.IncludeDescription = false | |||
cond := SearchRepositoryCondition(opts) | |||
sess, count, err := searchRepositoryByCondition(db.DefaultContext, opts, cond) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
defaultSize := 50 | |||
if opts.PageSize > 0 { | |||
defaultSize = opts.PageSize | |||
} | |||
ids := make([]int64, 0, defaultSize) | |||
err = sess.Select("id").Table("repository").Find(&ids) | |||
if opts.PageSize <= 0 { | |||
count = int64(len(ids)) | |||
} | |||
return ids, count, err | |||
} | |||
// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered. | |||
func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder { | |||
// NB: Please note this code needs to still work if user is nil | |||
return builder.Select("id").From("repository").Where(accessibleRepositoryCondition(user)) | |||
} | |||
// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id | |||
func FindUserAccessibleRepoIDs(user *user_model.User) ([]int64, error) { | |||
repoIDs := make([]int64, 0, 10) | |||
if err := db.GetEngine(db.DefaultContext). | |||
Table("repository"). | |||
Cols("id"). | |||
Where(accessibleRepositoryCondition(user)). | |||
Find(&repoIDs); err != nil { | |||
return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err) | |||
} | |||
return repoIDs, nil | |||
} | |||
// GetUserRepositories returns a list of repositories of given user. | |||
func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) { | |||
if len(opts.OrderBy) == 0 { | |||
opts.OrderBy = "updated_unix DESC" | |||
} | |||
cond := builder.NewCond() | |||
cond = cond.And(builder.Eq{"owner_id": opts.Actor.ID}) | |||
if !opts.Private { | |||
cond = cond.And(builder.Eq{"is_private": false}) | |||
} | |||
if opts.LowerNames != nil && len(opts.LowerNames) > 0 { | |||
cond = cond.And(builder.In("lower_name", opts.LowerNames)) | |||
} | |||
sess := db.GetEngine(db.DefaultContext) | |||
count, err := sess.Where(cond).Count(new(repo_model.Repository)) | |||
if err != nil { | |||
return nil, 0, fmt.Errorf("Count: %v", err) | |||
} | |||
sess = sess.Where(cond).OrderBy(opts.OrderBy.String()) | |||
repos := make(RepositoryList, 0, opts.PageSize) | |||
return repos, count, db.SetSessionPagination(sess, opts).Find(&repos) | |||
} |
@@ -84,127 +84,8 @@ func TestMetas(t *testing.T) { | |||
assert.Equal(t, ",owners,team1,", metas["teams"]) | |||
} | |||
func TestUpdateRepositoryVisibilityChanged(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
// Get sample repo and change visibility | |||
repo, err := repo_model.GetRepositoryByID(9) | |||
assert.NoError(t, err) | |||
repo.IsPrivate = true | |||
// Update it | |||
err = UpdateRepository(repo, true) | |||
assert.NoError(t, err) | |||
// Check visibility of action has become private | |||
act := Action{} | |||
_, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) | |||
assert.NoError(t, err) | |||
assert.True(t, act.IsPrivate) | |||
} | |||
func TestDoctorUserStarNum(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
assert.NoError(t, DoctorUserStarNum()) | |||
} | |||
func TestRepoGetReviewers(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
// test public repo | |||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) | |||
reviewers, err := GetReviewers(repo1, 2, 2) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 4) | |||
// should include doer if doer is not PR poster. | |||
reviewers, err = GetReviewers(repo1, 11, 2) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 4) | |||
// should not include PR poster, if PR poster would be otherwise eligible | |||
reviewers, err = GetReviewers(repo1, 11, 4) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 3) | |||
// test private user repo | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
reviewers, err = GetReviewers(repo2, 2, 4) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 1) | |||
assert.EqualValues(t, reviewers[0].ID, 2) | |||
// test private org repo | |||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) | |||
reviewers, err = GetReviewers(repo3, 2, 1) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 2) | |||
reviewers, err = GetReviewers(repo3, 2, 2) | |||
assert.NoError(t, err) | |||
assert.Len(t, reviewers, 1) | |||
} | |||
func TestRepoGetReviewerTeams(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
teams, err := GetReviewerTeams(repo2) | |||
assert.NoError(t, err) | |||
assert.Empty(t, teams) | |||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) | |||
teams, err = GetReviewerTeams(repo3) | |||
assert.NoError(t, err) | |||
assert.Len(t, teams, 2) | |||
} | |||
func TestLinkedRepository(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
testCases := []struct { | |||
name string | |||
attachID int64 | |||
expectedRepo *repo_model.Repository | |||
expectedUnitType unit.Type | |||
}{ | |||
{"LinkedIssue", 1, &repo_model.Repository{ID: 1}, unit.TypeIssues}, | |||
{"LinkedComment", 3, &repo_model.Repository{ID: 1}, unit.TypePullRequests}, | |||
{"LinkedRelease", 9, &repo_model.Repository{ID: 1}, unit.TypeReleases}, | |||
{"Notlinked", 10, nil, -1}, | |||
} | |||
for _, tc := range testCases { | |||
t.Run(tc.name, func(t *testing.T) { | |||
attach, err := repo_model.GetAttachmentByID(db.DefaultContext, tc.attachID) | |||
assert.NoError(t, err) | |||
repo, unitType, err := LinkedRepository(attach) | |||
assert.NoError(t, err) | |||
if tc.expectedRepo != nil { | |||
assert.Equal(t, tc.expectedRepo.ID, repo.ID) | |||
} | |||
assert.Equal(t, tc.expectedUnitType, unitType) | |||
}) | |||
} | |||
} | |||
func TestRepoAssignees(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
users, err := GetRepoAssignees(repo2) | |||
assert.NoError(t, err) | |||
assert.Len(t, users, 1) | |||
assert.Equal(t, users[0].ID, int64(2)) | |||
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository) | |||
users, err = GetRepoAssignees(repo21) | |||
assert.NoError(t, err) | |||
assert.Len(t, users, 3) | |||
assert.Equal(t, users[0].ID, int64(15)) | |||
assert.Equal(t, users[1].ID, int64(18)) | |||
assert.Equal(t, users[2].ID, int64(16)) | |||
} |
@@ -397,6 +397,14 @@ func CreateWebhook(ctx context.Context, w *Webhook) error { | |||
return db.Insert(ctx, w) | |||
} | |||
// CreateWebhooks creates multiple web hooks | |||
func CreateWebhooks(ctx context.Context, ws []*Webhook) error { | |||
for i := 0; i < len(ws); i++ { | |||
ws[i].Type = strings.TrimSpace(ws[i].Type) | |||
} | |||
return db.Insert(ctx, ws) | |||
} | |||
// getWebhook uses argument bean as query condition, | |||
// ID must be specified and do not assign unnecessary fields. | |||
func getWebhook(bean *Webhook) (*Webhook, error) { |
@@ -26,6 +26,7 @@ import ( | |||
code_indexer "code.gitea.io/gitea/modules/indexer/code" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/markup/markdown" | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
"code.gitea.io/gitea/modules/setting" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/util" | |||
@@ -551,14 +552,14 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { | |||
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues) | |||
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests) | |||
canSignedUserFork, err := models.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository) | |||
canSignedUserFork, err := repo_module.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository) | |||
if err != nil { | |||
ctx.ServerError("CanUserForkRepo", err) | |||
return | |||
} | |||
ctx.Data["CanSignedUserFork"] = canSignedUserFork | |||
userAndOrgForks, err := models.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository) | |||
userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository) | |||
if err != nil { | |||
ctx.ServerError("GetForksByUserAndOrgs", err) | |||
return |
@@ -291,8 +291,8 @@ func populateIssueIndexer(ctx context.Context) { | |||
return | |||
default: | |||
} | |||
repos, _, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{Page: page, PageSize: models.RepositoryListDefaultPageSize}, | |||
repos, _, err := repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{Page: page, PageSize: repo_model.RepositoryListDefaultPageSize}, | |||
OrderBy: db.SearchOrderByID, | |||
Private: true, | |||
Collaborate: util.OptionalBoolFalse, |
@@ -7,15 +7,20 @@ package repository | |||
import ( | |||
"context" | |||
"fmt" | |||
"os" | |||
"path" | |||
"strings" | |||
"unicode/utf8" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/db" | |||
access_model "code.gitea.io/gitea/models/perm/access" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/util" | |||
) | |||
@@ -108,7 +113,7 @@ func CreateRepository(doer, u *user_model.User, opts models.CreateRepoOptions) ( | |||
} | |||
} | |||
if err := models.CheckDaemonExportOK(ctx, repo); err != nil { | |||
if err := CheckDaemonExportOK(ctx, repo); err != nil { | |||
return fmt.Errorf("checkDaemonExportOK: %v", err) | |||
} | |||
@@ -133,3 +138,111 @@ func CreateRepository(doer, u *user_model.User, opts models.CreateRepoOptions) ( | |||
return repo, nil | |||
} | |||
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize | |||
func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { | |||
size, err := util.GetDirectorySize(repo.RepoPath()) | |||
if err != nil { | |||
return fmt.Errorf("updateSize: %v", err) | |||
} | |||
lfsSize, err := models.GetRepoLFSSize(ctx, repo.ID) | |||
if err != nil { | |||
return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) | |||
} | |||
return repo_model.UpdateRepoSize(ctx, repo.ID, size+lfsSize) | |||
} | |||
// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... | |||
func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { | |||
if err := repo.GetOwner(ctx); err != nil { | |||
return err | |||
} | |||
// Create/Remove git-daemon-export-ok for git-daemon... | |||
daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`) | |||
isExist, err := util.IsExist(daemonExportFile) | |||
if err != nil { | |||
log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) | |||
return err | |||
} | |||
isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic | |||
if !isPublic && isExist { | |||
if err = util.Remove(daemonExportFile); err != nil { | |||
log.Error("Failed to remove %s: %v", daemonExportFile, err) | |||
} | |||
} else if isPublic && !isExist { | |||
if f, err := os.Create(daemonExportFile); err != nil { | |||
log.Error("Failed to create %s: %v", daemonExportFile, err) | |||
} else { | |||
f.Close() | |||
} | |||
} | |||
return nil | |||
} | |||
// UpdateRepository updates a repository with db context | |||
func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { | |||
repo.LowerName = strings.ToLower(repo.Name) | |||
if utf8.RuneCountInString(repo.Description) > 255 { | |||
repo.Description = string([]rune(repo.Description)[:255]) | |||
} | |||
if utf8.RuneCountInString(repo.Website) > 255 { | |||
repo.Website = string([]rune(repo.Website)[:255]) | |||
} | |||
e := db.GetEngine(ctx) | |||
if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { | |||
return fmt.Errorf("update: %v", err) | |||
} | |||
if err = UpdateRepoSize(ctx, repo); err != nil { | |||
log.Error("Failed to update size for repository: %v", err) | |||
} | |||
if visibilityChanged { | |||
if err = repo.GetOwner(ctx); err != nil { | |||
return fmt.Errorf("getOwner: %v", err) | |||
} | |||
if repo.Owner.IsOrganization() { | |||
// Organization repository need to recalculate access table when visibility is changed. | |||
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { | |||
return fmt.Errorf("recalculateTeamAccesses: %v", err) | |||
} | |||
} | |||
// If repo has become private, we need to set its actions to private. | |||
if repo.IsPrivate { | |||
_, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&models.Action{ | |||
IsPrivate: true, | |||
}) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
// Create/Remove git-daemon-export-ok for git-daemon... | |||
if err := CheckDaemonExportOK(db.WithEngine(ctx, e), repo); err != nil { | |||
return err | |||
} | |||
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) | |||
if err != nil { | |||
return fmt.Errorf("getRepositoriesByForkID: %v", err) | |||
} | |||
for i := range forkRepos { | |||
forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate | |||
if err = UpdateRepository(ctx, forkRepos[i], true); err != nil { | |||
return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) | |||
} | |||
} | |||
} | |||
return nil | |||
} |
@@ -12,6 +12,7 @@ import ( | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/organization" | |||
"code.gitea.io/gitea/models/perm" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/structs" | |||
@@ -147,3 +148,23 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { | |||
} | |||
assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") | |||
} | |||
func TestUpdateRepositoryVisibilityChanged(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
// Get sample repo and change visibility | |||
repo, err := repo_model.GetRepositoryByID(9) | |||
assert.NoError(t, err) | |||
repo.IsPrivate = true | |||
// Update it | |||
err = UpdateRepository(db.DefaultContext, repo, true) | |||
assert.NoError(t, err) | |||
// Check visibility of action has become private | |||
act := models.Action{} | |||
_, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) | |||
assert.NoError(t, err) | |||
assert.True(t, act.IsPrivate) | |||
} |
@@ -0,0 +1,33 @@ | |||
// Copyright 2022 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 repository | |||
import ( | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/organization" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
user_model "code.gitea.io/gitea/models/user" | |||
) | |||
// CanUserDelete returns true if user could delete the repository | |||
func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) { | |||
if user.IsAdmin || user.ID == repo.OwnerID { | |||
return true, nil | |||
} | |||
if err := repo.GetOwner(db.DefaultContext); err != nil { | |||
return false, err | |||
} | |||
if repo.Owner.IsOrganization() { | |||
isOwner, err := organization.OrgFromUser(repo.Owner).IsOwnedBy(user.ID) | |||
if err != nil { | |||
return false, err | |||
} | |||
return isOwner, nil | |||
} | |||
return false, nil | |||
} |
@@ -0,0 +1,31 @@ | |||
// Copyright 2019 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 repository | |||
import ( | |||
"code.gitea.io/gitea/models/organization" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
user_model "code.gitea.io/gitea/models/user" | |||
) | |||
// CanUserForkRepo returns true if specified user can fork repository. | |||
func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, error) { | |||
if user == nil { | |||
return false, nil | |||
} | |||
if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { | |||
return true, nil | |||
} | |||
ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(user.ID) | |||
if err != nil { | |||
return false, err | |||
} | |||
for _, org := range ownedOrgs { | |||
if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) { | |||
return true, nil | |||
} | |||
} | |||
return false, nil | |||
} |
@@ -5,6 +5,8 @@ | |||
package repository | |||
import ( | |||
"bufio" | |||
"bytes" | |||
"context" | |||
"fmt" | |||
"os" | |||
@@ -20,6 +22,7 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/util" | |||
"github.com/gobwas/glob" | |||
"github.com/huandu/xstrings" | |||
) | |||
@@ -78,7 +81,38 @@ func generateExpansion(src string, templateRepo, generateRepo *repo_model.Reposi | |||
}) | |||
} | |||
func checkGiteaTemplate(tmpDir string) (*models.GiteaTemplate, error) { | |||
// GiteaTemplate holds information about a .gitea/template file | |||
type GiteaTemplate struct { | |||
Path string | |||
Content []byte | |||
globs []glob.Glob | |||
} | |||
// Globs parses the .gitea/template globs or returns them if they were already parsed | |||
func (gt GiteaTemplate) Globs() []glob.Glob { | |||
if gt.globs != nil { | |||
return gt.globs | |||
} | |||
gt.globs = make([]glob.Glob, 0) | |||
scanner := bufio.NewScanner(bytes.NewReader(gt.Content)) | |||
for scanner.Scan() { | |||
line := strings.TrimSpace(scanner.Text()) | |||
if line == "" || strings.HasPrefix(line, "#") { | |||
continue | |||
} | |||
g, err := glob.Compile(line, '/') | |||
if err != nil { | |||
log.Info("Invalid glob expression '%s' (skipped): %v", line, err) | |||
continue | |||
} | |||
gt.globs = append(gt.globs, g) | |||
} | |||
return gt.globs | |||
} | |||
func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { | |||
gtPath := filepath.Join(tmpDir, ".gitea", "template") | |||
if _, err := os.Stat(gtPath); os.IsNotExist(err) { | |||
return nil, nil | |||
@@ -91,7 +125,7 @@ func checkGiteaTemplate(tmpDir string) (*models.GiteaTemplate, error) { | |||
return nil, err | |||
} | |||
gt := &models.GiteaTemplate{ | |||
gt := &GiteaTemplate{ | |||
Path: gtPath, | |||
Content: content, | |||
} | |||
@@ -227,7 +261,7 @@ func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *r | |||
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { | |||
return fmt.Errorf("setDefaultBranch: %v", err) | |||
} | |||
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { | |||
if err = UpdateRepository(ctx, repo, false); err != nil { | |||
return fmt.Errorf("updateRepository: %v", err) | |||
} | |||
@@ -240,7 +274,7 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo | |||
return err | |||
} | |||
if err := models.UpdateRepoSize(ctx, generateRepo); err != nil { | |||
if err := UpdateRepoSize(ctx, generateRepo); err != nil { | |||
return fmt.Errorf("failed to update size for repository: %v", err) | |||
} | |||
@@ -250,8 +284,27 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo | |||
return nil | |||
} | |||
// GenerateRepoOptions contains the template units to generate | |||
type GenerateRepoOptions struct { | |||
Name string | |||
DefaultBranch string | |||
Description string | |||
Private bool | |||
GitContent bool | |||
Topics bool | |||
GitHooks bool | |||
Webhooks bool | |||
Avatar bool | |||
IssueLabels bool | |||
} | |||
// IsValid checks whether at least one option is chosen for generation | |||
func (gro GenerateRepoOptions) IsValid() bool { | |||
return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels // or other items as they are added | |||
} | |||
// GenerateRepository generates a repository from a template | |||
func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts models.GenerateRepoOptions) (_ *repo_model.Repository, err error) { | |||
func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { | |||
generateRepo := &repo_model.Repository{ | |||
OwnerID: owner.ID, | |||
Owner: owner, | |||
@@ -288,7 +341,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ | |||
return generateRepo, err | |||
} | |||
if err = models.CheckDaemonExportOK(ctx, generateRepo); err != nil { | |||
if err = CheckDaemonExportOK(ctx, generateRepo); err != nil { | |||
return generateRepo, fmt.Errorf("checkDaemonExportOK: %v", err) | |||
} | |||
@@ -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 repository | |||
import ( | |||
"testing" |
@@ -444,7 +444,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re | |||
} | |||
} | |||
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { | |||
if err = UpdateRepository(ctx, repo, false); err != nil { | |||
return fmt.Errorf("updateRepository: %v", err) | |||
} | |||
@@ -116,7 +116,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, | |||
repo.Owner = u | |||
} | |||
if err = models.CheckDaemonExportOK(ctx, repo); err != nil { | |||
if err = CheckDaemonExportOK(ctx, repo); err != nil { | |||
return repo, fmt.Errorf("checkDaemonExportOK: %v", err) | |||
} | |||
@@ -168,9 +168,11 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, | |||
} | |||
} | |||
if err = models.UpdateRepoSize(ctx, repo); err != nil { | |||
log.Error("Failed to update size for repository: %v", err) | |||
ctx, committer, err := db.TxContext() | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer committer.Close() | |||
if opts.Mirror { | |||
mirrorModel := repo_model.Mirror{ | |||
@@ -203,17 +205,24 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, | |||
} | |||
} | |||
if err = repo_model.InsertMirror(&mirrorModel); err != nil { | |||
if err = repo_model.InsertMirror(ctx, &mirrorModel); err != nil { | |||
return repo, fmt.Errorf("InsertOne: %v", err) | |||
} | |||
repo.IsMirror = true | |||
err = models.UpdateRepository(repo, false) | |||
if err = UpdateRepository(ctx, repo, false); err != nil { | |||
return nil, err | |||
} | |||
} else { | |||
repo, err = CleanUpMigrateInfo(ctx, repo) | |||
if err = UpdateRepoSize(ctx, repo); err != nil { | |||
log.Error("Failed to update size for repository: %v", err) | |||
} | |||
if repo, err = CleanUpMigrateInfo(ctx, repo); err != nil { | |||
return nil, err | |||
} | |||
} | |||
return repo, err | |||
return repo, committer.Commit() | |||
} | |||
// cleanUpMigrateGitConfig removes mirror info which prevents "push --all". | |||
@@ -253,7 +262,7 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo | |||
} | |||
} | |||
return repo, models.UpdateRepository(repo, false) | |||
return repo, UpdateRepository(ctx, repo, false) | |||
} | |||
// SyncReleasesWithTags synchronizes release table with repository tags |
@@ -312,7 +312,7 @@ func GetReviewers(ctx *context.APIContext) { | |||
// "200": | |||
// "$ref": "#/responses/UserList" | |||
reviewers, err := models.GetReviewers(ctx.Repo.Repository, ctx.Doer.ID, 0) | |||
reviewers, err := repo_model.GetReviewers(ctx, ctx.Repo.Repository, ctx.Doer.ID, 0) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) | |||
return | |||
@@ -342,7 +342,7 @@ func GetAssignees(ctx *context.APIContext) { | |||
// "200": | |||
// "$ref": "#/responses/UserList" | |||
assignees, err := models.GetRepoAssignees(ctx.Repo.Repository) | |||
assignees, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) | |||
return |
@@ -17,6 +17,7 @@ import ( | |||
issues_model "code.gitea.io/gitea/models/issues" | |||
"code.gitea.io/gitea/models/organization" | |||
access_model "code.gitea.io/gitea/models/perm/access" | |||
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/context" | |||
@@ -130,7 +131,7 @@ func SearchIssues(ctx *context.APIContext) { | |||
} | |||
// find repos user can access (for issue search) | |||
opts := &models.SearchRepoOptions{ | |||
opts := &repo_model.SearchRepoOptions{ | |||
Private: false, | |||
AllPublic: true, | |||
TopicOnly: false, | |||
@@ -176,8 +177,8 @@ func SearchIssues(ctx *context.APIContext) { | |||
opts.TeamID = team.ID | |||
} | |||
repoCond := models.SearchRepositoryCondition(opts) | |||
repoIDs, _, err := models.SearchRepositoryIDs(opts) | |||
repoCond := repo_model.SearchRepositoryCondition(opts) | |||
repoIDs, _, err := repo_model.SearchRepositoryIDs(opts) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err) | |||
return |
@@ -123,7 +123,7 @@ func Search(ctx *context.APIContext) { | |||
// "422": | |||
// "$ref": "#/responses/validationError" | |||
opts := &models.SearchRepoOptions{ | |||
opts := &repo_model.SearchRepoOptions{ | |||
ListOptions: utils.GetListOptions(ctx), | |||
Actor: ctx.Doer, | |||
Keyword: ctx.FormTrim("q"), | |||
@@ -192,7 +192,7 @@ func Search(ctx *context.APIContext) { | |||
} | |||
var err error | |||
repos, count, err := models.SearchRepository(opts) | |||
repos, count, err := repo_model.SearchRepository(opts) | |||
if err != nil { | |||
ctx.JSON(http.StatusInternalServerError, api.SearchError{ | |||
OK: false, | |||
@@ -344,7 +344,7 @@ func Generate(ctx *context.APIContext) { | |||
return | |||
} | |||
opts := models.GenerateRepoOptions{ | |||
opts := repo_module.GenerateRepoOptions{ | |||
Name: form.Name, | |||
DefaultBranch: form.DefaultBranch, | |||
Description: form.Description, | |||
@@ -717,7 +717,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err | |||
repo.DefaultBranch = *opts.DefaultBranch | |||
} | |||
if err := models.UpdateRepository(repo, visibilityChanged); err != nil { | |||
if err := repo_service.UpdateRepository(repo, visibilityChanged); err != nil { | |||
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err) | |||
return err | |||
} | |||
@@ -1036,7 +1036,7 @@ func Delete(ctx *context.APIContext) { | |||
owner := ctx.Repo.Owner | |||
repo := ctx.Repo.Repository | |||
canDelete, err := models.CanUserDelete(repo, ctx.Doer) | |||
canDelete, err := repo_module.CanUserDelete(repo, ctx.Doer) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "CanUserDelete", err) | |||
return |
@@ -7,9 +7,9 @@ package user | |||
import ( | |||
"net/http" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/perm" | |||
access_model "code.gitea.io/gitea/models/perm/access" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/convert" | |||
@@ -21,7 +21,7 @@ import ( | |||
func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) { | |||
opts := utils.GetListOptions(ctx) | |||
repos, count, err := models.GetUserRepositories(&models.SearchRepoOptions{ | |||
repos, count, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ | |||
Actor: u, | |||
Private: private, | |||
ListOptions: opts, | |||
@@ -103,7 +103,7 @@ func ListMyRepos(ctx *context.APIContext) { | |||
// "200": | |||
// "$ref": "#/responses/RepositoryList" | |||
opts := &models.SearchRepoOptions{ | |||
opts := &repo_model.SearchRepoOptions{ | |||
ListOptions: utils.GetListOptions(ctx), | |||
Actor: ctx.Doer, | |||
OwnerID: ctx.Doer.ID, | |||
@@ -112,7 +112,7 @@ func ListMyRepos(ctx *context.APIContext) { | |||
} | |||
var err error | |||
repos, count, err := models.SearchRepository(opts) | |||
repos, count, err := repo_model.SearchRepository(opts) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "SearchRepository", err) | |||
return |
@@ -55,7 +55,7 @@ func Code(ctx *context.Context) { | |||
// guest user or non-admin user | |||
if ctx.Doer == nil || !isAdmin { | |||
repoIDs, err = models.FindUserAccessibleRepoIDs(ctx.Doer) | |||
repoIDs, err = repo_model.FindUserAccessibleRepoIDs(ctx.Doer) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
@@ -79,7 +79,7 @@ func Code(ctx *context.Context) { | |||
rightRepoMap := make(map[int64]*repo_model.Repository, len(repoMaps)) | |||
repoIDs = make([]int64, 0, len(repoMaps)) | |||
for id, repo := range repoMaps { | |||
if models.CheckRepoUnitUser(repo, ctx.Doer, unit.TypeCode) { | |||
if models.CheckRepoUnitUser(ctx, repo, ctx.Doer, unit.TypeCode) { | |||
rightRepoMap[id] = repo | |||
repoIDs = append(repoIDs, id) | |||
} |
@@ -7,7 +7,6 @@ package explore | |||
import ( | |||
"net/http" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/db" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/modules/base" | |||
@@ -81,7 +80,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||
language := ctx.FormTrim("language") | |||
ctx.Data["Language"] = language | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: page, | |||
PageSize: opts.PageSize, |
@@ -8,7 +8,6 @@ import ( | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/organization" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
@@ -105,7 +104,7 @@ func Home(ctx *context.Context) { | |||
count int64 | |||
err error | |||
) | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
PageSize: setting.UI.User.RepoPagingNum, | |||
Page: page, |
@@ -24,6 +24,7 @@ import ( | |||
user_setting "code.gitea.io/gitea/routers/web/user/setting" | |||
"code.gitea.io/gitea/services/forms" | |||
"code.gitea.io/gitea/services/org" | |||
repo_service "code.gitea.io/gitea/services/repository" | |||
user_service "code.gitea.io/gitea/services/user" | |||
) | |||
@@ -117,7 +118,7 @@ func SettingsPost(ctx *context.Context) { | |||
// update forks visibility | |||
if visibilityChanged { | |||
repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ | |||
repos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ | |||
Actor: org.AsUser(), Private: true, ListOptions: db.ListOptions{Page: 1, PageSize: org.NumRepos}, | |||
}) | |||
if err != nil { | |||
@@ -126,7 +127,7 @@ func SettingsPost(ctx *context.Context) { | |||
} | |||
for _, repo := range repos { | |||
repo.OwnerName = org.Name | |||
if err := models.UpdateRepository(repo, true); err != nil { | |||
if err := repo_service.UpdateRepository(repo, true); err != nil { | |||
ctx.ServerError("UpdateRepository", err) | |||
return | |||
} |
@@ -8,7 +8,6 @@ import ( | |||
"fmt" | |||
"net/http" | |||
"code.gitea.io/gitea/models" | |||
access_model "code.gitea.io/gitea/models/perm/access" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/modules/context" | |||
@@ -19,6 +18,7 @@ import ( | |||
"code.gitea.io/gitea/modules/upload" | |||
"code.gitea.io/gitea/routers/common" | |||
"code.gitea.io/gitea/services/attachment" | |||
repo_service "code.gitea.io/gitea/services/repository" | |||
) | |||
// UploadIssueAttachment response for Issue/PR attachments | |||
@@ -95,7 +95,7 @@ func GetAttachment(ctx *context.Context) { | |||
return | |||
} | |||
repository, unitType, err := models.LinkedRepository(attach) | |||
repository, unitType, err := repo_service.LinkedRepository(attach) | |||
if err != nil { | |||
ctx.ServerError("LinkedRepository", err) | |||
return |
@@ -457,7 +457,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { | |||
if rootRepo != nil && | |||
rootRepo.ID != ci.HeadRepo.ID && | |||
rootRepo.ID != baseRepo.ID { | |||
canRead := models.CheckRepoUnitUser(rootRepo, ctx.Doer, unit.TypeCode) | |||
canRead := models.CheckRepoUnitUser(ctx, rootRepo, ctx.Doer, unit.TypeCode) | |||
if canRead { | |||
ctx.Data["RootRepo"] = rootRepo | |||
if !fileOnly { | |||
@@ -482,7 +482,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { | |||
ownForkRepo.ID != ci.HeadRepo.ID && | |||
ownForkRepo.ID != baseRepo.ID && | |||
(rootRepo == nil || ownForkRepo.ID != rootRepo.ID) { | |||
canRead := models.CheckRepoUnitUser(ownForkRepo, ctx.Doer, unit.TypeCode) | |||
canRead := models.CheckRepoUnitUser(ctx, ownForkRepo, ctx.Doer, unit.TypeCode) | |||
if canRead { | |||
ctx.Data["OwnForkRepo"] = ownForkRepo | |||
if !fileOnly { |
@@ -49,6 +49,7 @@ import ( | |||
"code.gitea.io/gitea/services/forms" | |||
issue_service "code.gitea.io/gitea/services/issue" | |||
pull_service "code.gitea.io/gitea/services/pull" | |||
repo_service "code.gitea.io/gitea/services/repository" | |||
) | |||
const ( | |||
@@ -283,7 +284,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti | |||
ctx.Data["CommitStatuses"] = commitStatuses | |||
// Get assignees. | |||
ctx.Data["Assignees"], err = models.GetRepoAssignees(repo) | |||
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo) | |||
if err != nil { | |||
ctx.ServerError("GetAssignees", err) | |||
return | |||
@@ -441,7 +442,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R | |||
return | |||
} | |||
ctx.Data["Assignees"], err = models.GetRepoAssignees(repo) | |||
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo) | |||
if err != nil { | |||
ctx.ServerError("GetAssignees", err) | |||
return | |||
@@ -522,13 +523,13 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is | |||
posterID = 0 | |||
} | |||
reviewers, err = models.GetReviewers(repo, ctx.Doer.ID, posterID) | |||
reviewers, err = repo_model.GetReviewers(ctx, repo, ctx.Doer.ID, posterID) | |||
if err != nil { | |||
ctx.ServerError("GetReviewers", err) | |||
return | |||
} | |||
teamReviewers, err = models.GetReviewerTeams(repo) | |||
teamReviewers, err = repo_service.GetReviewerTeams(repo) | |||
if err != nil { | |||
ctx.ServerError("GetReviewerTeams", err) | |||
return | |||
@@ -2160,7 +2161,7 @@ func SearchIssues(ctx *context.Context) { | |||
} | |||
// find repos user can access (for issue search) | |||
opts := &models.SearchRepoOptions{ | |||
opts := &repo_model.SearchRepoOptions{ | |||
Private: false, | |||
AllPublic: true, | |||
TopicOnly: false, | |||
@@ -2206,8 +2207,8 @@ func SearchIssues(ctx *context.Context) { | |||
opts.TeamID = team.ID | |||
} | |||
repoCond := models.SearchRepositoryCondition(opts) | |||
repoIDs, _, err := models.SearchRepositoryIDs(opts) | |||
repoCond := repo_model.SearchRepositoryCondition(opts) | |||
repoIDs, _, err := repo_model.SearchRepositoryIDs(opts) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err.Error()) | |||
return |
@@ -758,7 +758,7 @@ func ViewPullFiles(ctx *context.Context) { | |||
setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) | |||
ctx.Data["RequireTribute"] = true | |||
if ctx.Data["Assignees"], err = models.GetRepoAssignees(ctx.Repo.Repository); err != nil { | |||
if ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository); err != nil { | |||
ctx.ServerError("GetAssignees", err) | |||
return | |||
} |
@@ -152,7 +152,7 @@ func Create(ctx *context.Context) { | |||
templateID := ctx.FormInt64("template_id") | |||
if templateID > 0 { | |||
templateRepo, err := repo_model.GetRepositoryByID(templateID) | |||
if err == nil && models.CheckRepoUnitUser(templateRepo, ctxUser, unit.TypeCode) { | |||
if err == nil && models.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) { | |||
ctx.Data["repo_template"] = templateID | |||
ctx.Data["repo_template_name"] = templateRepo.Name | |||
} | |||
@@ -223,7 +223,7 @@ func CreatePost(ctx *context.Context) { | |||
var repo *repo_model.Repository | |||
var err error | |||
if form.RepoTemplate > 0 { | |||
opts := models.GenerateRepoOptions{ | |||
opts := repo_module.GenerateRepoOptions{ | |||
Name: form.RepoName, | |||
Description: form.Description, | |||
Private: form.Private, | |||
@@ -304,7 +304,7 @@ func Action(ctx *context.Context) { | |||
ctx.Repo.Repository.Description = ctx.FormString("desc") | |||
ctx.Repo.Repository.Website = ctx.FormString("site") | |||
err = models.UpdateRepository(ctx.Repo.Repository, false) | |||
err = repo_service.UpdateRepository(ctx.Repo.Repository, false) | |||
} | |||
if err != nil { | |||
@@ -509,7 +509,7 @@ func InitiateDownload(ctx *context.Context) { | |||
// SearchRepo repositories via options | |||
func SearchRepo(ctx *context.Context) { | |||
opts := &models.SearchRepoOptions{ | |||
opts := &repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
Page: ctx.FormInt("page"), | |||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), | |||
@@ -581,7 +581,7 @@ func SearchRepo(ctx *context.Context) { | |||
} | |||
var err error | |||
repos, count, err := models.SearchRepository(opts) | |||
repos, count, err := repo_model.SearchRepository(opts) | |||
if err != nil { | |||
ctx.JSON(http.StatusInternalServerError, api.SearchError{ | |||
OK: false, |
@@ -168,7 +168,7 @@ func SettingsPost(ctx *context.Context) { | |||
} | |||
repo.IsPrivate = form.Private | |||
if err := models.UpdateRepository(repo, visibilityChanged); err != nil { | |||
if err := repo_service.UpdateRepository(repo, visibilityChanged); err != nil { | |||
ctx.ServerError("UpdateRepository", err) | |||
return | |||
} | |||
@@ -491,7 +491,7 @@ func SettingsPost(ctx *context.Context) { | |||
return | |||
} | |||
if repoChanged { | |||
if err := models.UpdateRepository(repo, false); err != nil { | |||
if err := repo_service.UpdateRepository(repo, false); err != nil { | |||
ctx.ServerError("UpdateRepository", err) | |||
return | |||
} | |||
@@ -510,7 +510,7 @@ func SettingsPost(ctx *context.Context) { | |||
} | |||
if changed { | |||
if err := models.UpdateRepository(repo, false); err != nil { | |||
if err := repo_service.UpdateRepository(repo, false); err != nil { | |||
ctx.ServerError("UpdateRepository", err) | |||
return | |||
} | |||
@@ -530,7 +530,7 @@ func SettingsPost(ctx *context.Context) { | |||
repo.IsFsckEnabled = form.EnableHealthCheck | |||
} | |||
if err := models.UpdateRepository(repo, false); err != nil { | |||
if err := repo_service.UpdateRepository(repo, false); err != nil { | |||
ctx.ServerError("UpdateRepository", err) | |||
return | |||
} |
@@ -34,6 +34,7 @@ import ( | |||
"code.gitea.io/gitea/modules/lfs" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/markup" | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/typesniffer" | |||
@@ -905,7 +906,7 @@ func renderCode(ctx *context.Context) { | |||
ctx.ServerError("UpdateRepositoryCols", err) | |||
return | |||
} | |||
if err = models.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil { | |||
if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil { | |||
ctx.ServerError("UpdateRepoSize", err) | |||
return | |||
} |
@@ -166,7 +166,7 @@ func Milestones(ctx *context.Context) { | |||
return | |||
} | |||
repoOpts := models.SearchRepoOptions{ | |||
repoOpts := repo_model.SearchRepoOptions{ | |||
Actor: ctxUser, | |||
OwnerID: ctxUser.ID, | |||
Private: true, | |||
@@ -181,7 +181,7 @@ func Milestones(ctx *context.Context) { | |||
} | |||
var ( | |||
userRepoCond = models.SearchRepositoryCondition(&repoOpts) // all repo condition user could visit | |||
userRepoCond = repo_model.SearchRepositoryCondition(&repoOpts) // all repo condition user could visit | |||
repoCond = userRepoCond | |||
repoIDs []int64 | |||
@@ -234,7 +234,7 @@ func Milestones(ctx *context.Context) { | |||
return | |||
} | |||
showRepos, _, err := models.SearchRepositoryByCondition(&repoOpts, userRepoCond, false) | |||
showRepos, _, err := repo_model.SearchRepositoryByCondition(&repoOpts, userRepoCond, false) | |||
if err != nil { | |||
ctx.ServerError("SearchRepositoryByCondition", err) | |||
return | |||
@@ -437,7 +437,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { | |||
// As team: | |||
// - Team org's owns the repository. | |||
// - Team has read permission to repository. | |||
repoOpts := &models.SearchRepoOptions{ | |||
repoOpts := &repo_model.SearchRepoOptions{ | |||
Actor: ctx.Doer, | |||
OwnerID: ctx.Doer.ID, | |||
Private: true, | |||
@@ -559,7 +559,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { | |||
} | |||
// a RepositoryList | |||
showRepos := models.RepositoryListOfMap(showReposMap) | |||
showRepos := repo_model.RepositoryListOfMap(showReposMap) | |||
sort.Sort(showRepos) | |||
// maps pull request IDs to their CommitStatus. Will be posted to ctx.Data. |
@@ -8,7 +8,7 @@ import ( | |||
"net/http" | |||
"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" | |||
"code.gitea.io/gitea/modules/test" | |||
@@ -26,7 +26,7 @@ func TestArchivedIssues(t *testing.T) { | |||
ctx.Req.Form.Set("state", "open") | |||
// Assume: User 30 has access to two Repos with Issues, one of the Repos being archived. | |||
repos, _, _ := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctx.Doer}) | |||
repos, _, _ := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{Actor: ctx.Doer}) | |||
assert.Len(t, repos, 2) | |||
IsArchived := make(map[int64]bool) | |||
NumIssues := make(map[int64]int) |
@@ -7,7 +7,6 @@ package user | |||
import ( | |||
"net/http" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/db" | |||
packages_model "code.gitea.io/gitea/models/packages" | |||
container_model "code.gitea.io/gitea/models/packages/container" | |||
@@ -288,7 +287,7 @@ func PackageSettings(ctx *context.Context) { | |||
ctx.Data["ContextUser"] = ctx.ContextUser | |||
ctx.Data["PackageDescriptor"] = pd | |||
repos, _, _ := models.GetUserRepositories(&models.SearchRepoOptions{ | |||
repos, _, _ := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ | |||
Actor: pd.Owner, | |||
Private: true, | |||
}) |
@@ -195,7 +195,7 @@ func Profile(ctx *context.Context) { | |||
} | |||
case "stars": | |||
ctx.Data["PageIsProfileStarList"] = true | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
PageSize: setting.UI.User.RepoPagingNum, | |||
Page: page, | |||
@@ -227,7 +227,7 @@ func Profile(ctx *context.Context) { | |||
return | |||
} | |||
case "watching": | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
PageSize: setting.UI.User.RepoPagingNum, | |||
Page: page, | |||
@@ -249,7 +249,7 @@ func Profile(ctx *context.Context) { | |||
total = int(count) | |||
default: | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | |||
ListOptions: db.ListOptions{ | |||
PageSize: setting.UI.User.RepoPagingNum, | |||
Page: page, |
@@ -15,7 +15,6 @@ import ( | |||
"path/filepath" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/organization" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
@@ -304,7 +303,7 @@ func Repos(ctx *context.Context) { | |||
return | |||
} | |||
userRepos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ | |||
userRepos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ | |||
Actor: ctxUser, | |||
Private: true, | |||
ListOptions: db.ListOptions{ | |||
@@ -329,7 +328,7 @@ func Repos(ctx *context.Context) { | |||
ctx.Data["Dirs"] = repoNames | |||
ctx.Data["ReposMap"] = repos | |||
} else { | |||
repos, count64, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts}) | |||
repos, count64, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts}) | |||
if err != nil { | |||
ctx.ServerError("GetUserRepositories", err) | |||
return |
@@ -1220,7 +1220,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio | |||
oid := strings.TrimPrefix(line[1:], lfs.MetaFileOidPrefix) | |||
if len(oid) == 64 { | |||
m := &models.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}} | |||
count, err := db.Count(m) | |||
count, err := db.CountByBean(db.DefaultContext, m) | |||
if err == nil && count > 0 { | |||
curFile.IsBin = true |
@@ -145,7 +145,7 @@ func mailIssueCommentBatch(ctx *mailCommentContext, users []*user_model.User, vi | |||
visited[user.ID] = true | |||
// test if this user is allowed to see the issue/pull | |||
if !models.CheckRepoUnitUser(ctx.Issue.Repo, user, checkUnit) { | |||
if !models.CheckRepoUnitUser(ctx, ctx.Issue.Repo, user, checkUnit) { | |||
continue | |||
} | |||
@@ -10,7 +10,6 @@ import ( | |||
"strings" | |||
"time" | |||
"code.gitea.io/gitea/models" | |||
admin_model "code.gitea.io/gitea/models/admin" | |||
"code.gitea.io/gitea/models/db" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
@@ -301,7 +300,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo | |||
gitRepo.Close() | |||
log.Trace("SyncMirrors [repo: %-v]: updating size of repository", m.Repo) | |||
if err := models.UpdateRepoSize(ctx, m.Repo); err != nil { | |||
if err := repo_module.UpdateRepoSize(ctx, m.Repo); err != nil { | |||
log.Error("SyncMirrors [repo: %-v]: failed to update size for mirror repository: %v", m.Repo, err) | |||
} | |||
@@ -73,7 +73,7 @@ func AdoptRepository(doer, u *user_model.User, opts models.CreateRepoOptions) (* | |||
if err := adoptRepository(ctx, repoPath, doer, repo, opts); err != nil { | |||
return fmt.Errorf("createDelegateHooks: %v", err) | |||
} | |||
if err := models.CheckDaemonExportOK(ctx, repo); err != nil { | |||
if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { | |||
return fmt.Errorf("checkDaemonExportOK: %v", err) | |||
} | |||
@@ -182,7 +182,7 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r | |||
} | |||
} | |||
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { | |||
if err = repo_module.UpdateRepository(ctx, repo, false); err != nil { | |||
return fmt.Errorf("updateRepository: %v", err) | |||
} | |||
@@ -246,7 +246,7 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad | |||
} | |||
return err | |||
} | |||
repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ | |||
repos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ | |||
Actor: ctxUser, | |||
Private: true, | |||
ListOptions: db.ListOptions{ |
@@ -17,6 +17,7 @@ import ( | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/log" | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
"code.gitea.io/gitea/modules/util" | |||
"xorm.io/builder" | |||
@@ -89,7 +90,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro | |||
} | |||
// Now update the size of the repository | |||
if err := models.UpdateRepoSize(ctx, repo); err != nil { | |||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { | |||
log.Error("Updating size as part of garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) | |||
desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) | |||
if err = admin_model.CreateRepositoryNotice(desc); err != nil { |
@@ -96,7 +96,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork | |||
return err | |||
} | |||
if err = models.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil { | |||
if err = repo_model.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil { | |||
return err | |||
} | |||
@@ -116,7 +116,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork | |||
return fmt.Errorf("git clone: %v", err) | |||
} | |||
if err := models.CheckDaemonExportOK(txCtx, repo); err != nil { | |||
if err := repo_module.CheckDaemonExportOK(txCtx, repo); err != nil { | |||
return fmt.Errorf("checkDaemonExportOK: %v", err) | |||
} | |||
@@ -139,7 +139,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork | |||
} | |||
// even if below operations failed, it could be ignored. And they will be retried | |||
if err := models.UpdateRepoSize(ctx, repo); err != nil { | |||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { | |||
log.Error("Failed to update size for repository: %v", err) | |||
} | |||
if err := repo_model.CopyLanguageStat(opts.BaseRepo, repo); err != nil { | |||
@@ -173,7 +173,7 @@ func ConvertForkToNormalRepository(repo *repo_model.Repository) error { | |||
return nil | |||
} | |||
if err := models.DecrementRepoForkNum(ctx, repo.ForkID); err != nil { | |||
if err := repo_model.DecrementRepoForkNum(ctx, repo.ForkID); err != nil { | |||
log.Error("Unable to decrement repo fork num for old root repo %d of repository %-v whilst converting from fork. Error: %v", repo.ForkID, repo, err) | |||
return err | |||
} | |||
@@ -181,7 +181,7 @@ func ConvertForkToNormalRepository(repo *repo_model.Repository) error { | |||
repo.IsFork = false | |||
repo.ForkID = 0 | |||
if err := models.UpdateRepositoryCtx(ctx, repo, false); err != nil { | |||
if err := repo_module.UpdateRepository(ctx, repo, false); err != nil { | |||
log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err) | |||
return err | |||
} |
@@ -10,6 +10,7 @@ import ( | |||
"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" | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
@@ -84,3 +85,29 @@ func GenerateGitHooks(ctx context.Context, templateRepo, generateRepo *repo_mode | |||
} | |||
return nil | |||
} | |||
// GenerateWebhooks generates webhooks from a template repository | |||
func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { | |||
templateWebhooks, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{RepoID: templateRepo.ID}) | |||
if err != nil { | |||
return err | |||
} | |||
ws := make([]*webhook.Webhook, 0, len(templateWebhooks)) | |||
for _, templateWebhook := range templateWebhooks { | |||
ws = append(ws, &webhook.Webhook{ | |||
RepoID: generateRepo.ID, | |||
URL: templateWebhook.URL, | |||
HTTPMethod: templateWebhook.HTTPMethod, | |||
ContentType: templateWebhook.ContentType, | |||
Secret: templateWebhook.Secret, | |||
HookEvent: templateWebhook.HookEvent, | |||
IsActive: templateWebhook.IsActive, | |||
Type: templateWebhook.Type, | |||
OrgID: templateWebhook.OrgID, | |||
Events: templateWebhook.Events, | |||
Meta: templateWebhook.Meta, | |||
}) | |||
} | |||
return webhook.CreateWebhooks(ctx, ws) | |||
} |
@@ -95,7 +95,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { | |||
} | |||
defer gitRepo.Close() | |||
if err = models.UpdateRepoSize(ctx, repo); err != nil { | |||
if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { | |||
log.Error("Failed to update size for repository: %v", err) | |||
} | |||
@@ -14,6 +14,7 @@ import ( | |||
"code.gitea.io/gitea/models/organization" | |||
packages_model "code.gitea.io/gitea/models/packages" | |||
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/log" | |||
"code.gitea.io/gitea/modules/notification" | |||
@@ -85,3 +86,42 @@ func Init() error { | |||
admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) | |||
return initPushQueue() | |||
} | |||
// UpdateRepository updates a repository | |||
func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err error) { | |||
ctx, committer, err := db.TxContext() | |||
if err != nil { | |||
return err | |||
} | |||
defer committer.Close() | |||
if err = repo_module.UpdateRepository(ctx, repo, visibilityChanged); err != nil { | |||
return fmt.Errorf("updateRepository: %v", err) | |||
} | |||
return committer.Commit() | |||
} | |||
// LinkedRepository returns the linked repo if any | |||
func LinkedRepository(a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { | |||
if a.IssueID != 0 { | |||
iss, err := models.GetIssueByID(a.IssueID) | |||
if err != nil { | |||
return nil, unit.TypeIssues, err | |||
} | |||
repo, err := repo_model.GetRepositoryByID(iss.RepoID) | |||
unitType := unit.TypeIssues | |||
if iss.IsPull { | |||
unitType = unit.TypePullRequests | |||
} | |||
return repo, unitType, err | |||
} else if a.ReleaseID != 0 { | |||
rel, err := models.GetReleaseByID(db.DefaultContext, a.ReleaseID) | |||
if err != nil { | |||
return nil, unit.TypeReleases, err | |||
} | |||
repo, err := repo_model.GetRepositoryByID(rel.RepoID) | |||
return repo, unit.TypeReleases, err | |||
} | |||
return nil, -1, nil | |||
} |
@@ -0,0 +1,43 @@ | |||
// Copyright 2022 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 repository | |||
import ( | |||
"testing" | |||
"code.gitea.io/gitea/models/db" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unit" | |||
"code.gitea.io/gitea/models/unittest" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestLinkedRepository(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
testCases := []struct { | |||
name string | |||
attachID int64 | |||
expectedRepo *repo_model.Repository | |||
expectedUnitType unit.Type | |||
}{ | |||
{"LinkedIssue", 1, &repo_model.Repository{ID: 1}, unit.TypeIssues}, | |||
{"LinkedComment", 3, &repo_model.Repository{ID: 1}, unit.TypePullRequests}, | |||
{"LinkedRelease", 9, &repo_model.Repository{ID: 1}, unit.TypeReleases}, | |||
{"Notlinked", 10, nil, -1}, | |||
} | |||
for _, tc := range testCases { | |||
t.Run(tc.name, func(t *testing.T) { | |||
attach, err := repo_model.GetAttachmentByID(db.DefaultContext, tc.attachID) | |||
assert.NoError(t, err) | |||
repo, unitType, err := LinkedRepository(attach) | |||
assert.NoError(t, err) | |||
if tc.expectedRepo != nil { | |||
assert.Equal(t, tc.expectedRepo.ID, repo.ID) | |||
} | |||
assert.Equal(t, tc.expectedUnitType, unitType) | |||
}) | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
// Copyright 2022 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 repository | |||
import ( | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/organization" | |||
"code.gitea.io/gitea/models/perm" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
) | |||
// GetReviewerTeams get all teams can be requested to review | |||
func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) { | |||
if err := repo.GetOwner(db.DefaultContext); err != nil { | |||
return nil, err | |||
} | |||
if !repo.Owner.IsOrganization() { | |||
return nil, nil | |||
} | |||
return organization.GetTeamsWithAccessToRepo(db.DefaultContext, repo.OwnerID, repo.ID, perm.AccessModeRead) | |||
} |
@@ -0,0 +1,28 @@ | |||
// Copyright 2022 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 repository | |||
import ( | |||
"testing" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestRepoGetReviewerTeams(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) | |||
teams, err := GetReviewerTeams(repo2) | |||
assert.NoError(t, err) | |||
assert.Empty(t, teams) | |||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) | |||
teams, err = GetReviewerTeams(repo3) | |||
assert.NoError(t, err) | |||
assert.Len(t, teams, 2) | |||
} |
@@ -16,8 +16,27 @@ import ( | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
) | |||
// GenerateIssueLabels generates issue labels from a template repository | |||
func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { | |||
templateLabels, err := models.GetLabelsByRepoID(ctx, templateRepo.ID, "", db.ListOptions{}) | |||
if err != nil { | |||
return err | |||
} | |||
newLabels := make([]*models.Label, 0, len(templateLabels)) | |||
for _, templateLabel := range templateLabels { | |||
newLabels = append(newLabels, &models.Label{ | |||
RepoID: generateRepo.ID, | |||
Name: templateLabel.Name, | |||
Description: templateLabel.Description, | |||
Color: templateLabel.Color, | |||
}) | |||
} | |||
return db.Insert(ctx, newLabels) | |||
} | |||
// GenerateRepository generates a repository from a template | |||
func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.Repository, opts models.GenerateRepoOptions) (_ *repo_model.Repository, err error) { | |||
func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.Repository, opts repo_module.GenerateRepoOptions) (_ *repo_model.Repository, err error) { | |||
if !doer.IsAdmin && !owner.CanCreateRepo() { | |||
return nil, repo_model.ErrReachLimitOfRepo{ | |||
Limit: owner.MaxRepoCreation, | |||
@@ -54,7 +73,7 @@ func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.R | |||
// Webhooks | |||
if opts.Webhooks { | |||
if err = models.GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { | |||
if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { | |||
return err | |||
} | |||
} | |||
@@ -68,7 +87,7 @@ func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.R | |||
// Issue Labels | |||
if opts.IssueLabels { | |||
if err = models.GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { | |||
if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { | |||
return err | |||
} | |||
} |