| @@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) { | |||
| expectedResults | |||
| }{ | |||
| {name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{ | |||
| nil: {count: 15}, | |||
| user: {count: 15}, | |||
| user2: {count: 15}}, | |||
| nil: {count: 16}, | |||
| user: {count: 16}, | |||
| user2: {count: 16}}, | |||
| }, | |||
| {name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{ | |||
| nil: {count: 10}, | |||
| @@ -22,8 +22,12 @@ func TestAccessLevel(t *testing.T) { | |||
| user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | |||
| user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | |||
| repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository) | |||
| repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository) | |||
| // A public repository owned by User 2 | |||
| repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
| assert.False(t, repo1.IsPrivate) | |||
| // A private repository owned by Org 3 | |||
| repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | |||
| assert.True(t, repo2.IsPrivate) | |||
| level, err := AccessLevel(user1.ID, repo1) | |||
| assert.NoError(t, err) | |||
| @@ -47,8 +51,12 @@ func TestHasAccess(t *testing.T) { | |||
| user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | |||
| user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | |||
| repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository) | |||
| repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository) | |||
| // A public repository owned by User 2 | |||
| repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||
| assert.False(t, repo1.IsPrivate) | |||
| // A private repository owned by Org 3 | |||
| repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | |||
| assert.True(t, repo2.IsPrivate) | |||
| for _, accessMode := range accessModes { | |||
| has, err := HasAccess(user1.ID, repo1, accessMode) | |||
| @@ -351,7 +351,7 @@ | |||
| is_mirror: true | |||
| num_forks: 1 | |||
| is_fork: false | |||
| - | |||
| id: 29 | |||
| fork_id: 27 | |||
| @@ -365,7 +365,7 @@ | |||
| num_closed_pulls: 0 | |||
| is_mirror: false | |||
| is_fork: true | |||
| - | |||
| id: 30 | |||
| fork_id: 28 | |||
| @@ -389,3 +389,14 @@ | |||
| num_forks: 0 | |||
| num_issues: 0 | |||
| is_mirror: false | |||
| - | |||
| id: 32 | |||
| owner_id: 3 | |||
| lower_name: repo21 | |||
| name: repo21 | |||
| is_private: false | |||
| num_stars: 0 | |||
| num_forks: 0 | |||
| num_issues: 0 | |||
| is_mirror: false | |||
| @@ -4,9 +4,8 @@ | |||
| lower_name: owners | |||
| name: Owners | |||
| authorize: 4 # owner | |||
| num_repos: 2 | |||
| num_repos: 3 | |||
| num_members: 1 | |||
| unit_types: '[1,2,3,4,5,6,7]' | |||
| - | |||
| id: 2 | |||
| @@ -16,7 +15,6 @@ | |||
| authorize: 2 # write | |||
| num_repos: 1 | |||
| num_members: 2 | |||
| unit_types: '[1,2,3,4,5,6,7]' | |||
| - | |||
| id: 3 | |||
| @@ -26,7 +24,6 @@ | |||
| authorize: 4 # owner | |||
| num_repos: 0 | |||
| num_members: 1 | |||
| unit_types: '[1,2,3,4,5,6,7]' | |||
| - | |||
| id: 4 | |||
| @@ -36,7 +33,6 @@ | |||
| authorize: 4 # owner | |||
| num_repos: 0 | |||
| num_members: 1 | |||
| unit_types: '[1,2,3,4,5,6,7]' | |||
| - | |||
| id: 5 | |||
| @@ -46,7 +42,6 @@ | |||
| authorize: 4 # owner | |||
| num_repos: 2 | |||
| num_members: 2 | |||
| unit_types: '[1,2,3,4,5,6,7]' | |||
| - | |||
| id: 6 | |||
| @@ -56,4 +51,3 @@ | |||
| authorize: 4 # owner | |||
| num_repos: 2 | |||
| num_members: 1 | |||
| unit_types: '[1,2,3,4,5,6,7]' | |||
| @@ -33,9 +33,15 @@ | |||
| org_id: 19 | |||
| team_id: 6 | |||
| repo_id: 27 | |||
| - | |||
| id: 7 | |||
| org_id: 19 | |||
| team_id: 6 | |||
| repo_id: 28 | |||
| repo_id: 28 | |||
| - | |||
| id: 8 | |||
| org_id: 3 | |||
| team_id: 1 | |||
| repo_id: 32 | |||
| @@ -0,0 +1,209 @@ | |||
| - | |||
| id: 1 | |||
| team_id: 1 | |||
| type: 1 | |||
| - | |||
| id: 2 | |||
| team_id: 1 | |||
| type: 2 | |||
| - | |||
| id: 3 | |||
| team_id: 1 | |||
| type: 3 | |||
| - | |||
| id: 4 | |||
| team_id: 1 | |||
| type: 4 | |||
| - | |||
| id: 5 | |||
| team_id: 1 | |||
| type: 5 | |||
| - | |||
| id: 6 | |||
| team_id: 1 | |||
| type: 6 | |||
| - | |||
| id: 7 | |||
| team_id: 1 | |||
| type: 7 | |||
| - | |||
| id: 8 | |||
| team_id: 2 | |||
| type: 1 | |||
| - | |||
| id: 9 | |||
| team_id: 2 | |||
| type: 2 | |||
| - | |||
| id: 10 | |||
| team_id: 2 | |||
| type: 3 | |||
| - | |||
| id: 11 | |||
| team_id: 2 | |||
| type: 4 | |||
| - | |||
| id: 12 | |||
| team_id: 2 | |||
| type: 5 | |||
| - | |||
| id: 13 | |||
| team_id: 2 | |||
| type: 6 | |||
| - | |||
| id: 14 | |||
| team_id: 2 | |||
| type: 7 | |||
| - | |||
| id: 15 | |||
| team_id: 3 | |||
| type: 1 | |||
| - | |||
| id: 16 | |||
| team_id: 3 | |||
| type: 2 | |||
| - | |||
| id: 17 | |||
| team_id: 3 | |||
| type: 3 | |||
| - | |||
| id: 18 | |||
| team_id: 3 | |||
| type: 4 | |||
| - | |||
| id: 19 | |||
| team_id: 3 | |||
| type: 5 | |||
| - | |||
| id: 20 | |||
| team_id: 3 | |||
| type: 6 | |||
| - | |||
| id: 21 | |||
| team_id: 3 | |||
| type: 7 | |||
| - | |||
| id: 22 | |||
| team_id: 4 | |||
| type: 1 | |||
| - | |||
| id: 23 | |||
| team_id: 4 | |||
| type: 2 | |||
| - | |||
| id: 24 | |||
| team_id: 4 | |||
| type: 3 | |||
| - | |||
| id: 25 | |||
| team_id: 4 | |||
| type: 4 | |||
| - | |||
| id: 26 | |||
| team_id: 4 | |||
| type: 5 | |||
| - | |||
| id: 27 | |||
| team_id: 4 | |||
| type: 6 | |||
| - | |||
| id: 28 | |||
| team_id: 4 | |||
| type: 7 | |||
| - | |||
| id: 29 | |||
| team_id: 5 | |||
| type: 1 | |||
| - | |||
| id: 30 | |||
| team_id: 5 | |||
| type: 2 | |||
| - | |||
| id: 31 | |||
| team_id: 5 | |||
| type: 3 | |||
| - | |||
| id: 32 | |||
| team_id: 5 | |||
| type: 4 | |||
| - | |||
| id: 33 | |||
| team_id: 5 | |||
| type: 5 | |||
| - | |||
| id: 34 | |||
| team_id: 5 | |||
| type: 6 | |||
| - | |||
| id: 35 | |||
| team_id: 5 | |||
| type: 7 | |||
| - | |||
| id: 36 | |||
| team_id: 6 | |||
| type: 1 | |||
| - | |||
| id: 37 | |||
| team_id: 6 | |||
| type: 2 | |||
| - | |||
| id: 38 | |||
| team_id: 6 | |||
| type: 3 | |||
| - | |||
| id: 39 | |||
| team_id: 6 | |||
| type: 4 | |||
| - | |||
| id: 40 | |||
| team_id: 6 | |||
| type: 5 | |||
| - | |||
| id: 41 | |||
| team_id: 6 | |||
| type: 6 | |||
| - | |||
| id: 42 | |||
| team_id: 6 | |||
| type: 7 | |||
| @@ -45,7 +45,7 @@ | |||
| is_admin: false | |||
| avatar: avatar3 | |||
| avatar_email: user3@example.com | |||
| num_repos: 2 | |||
| num_repos: 3 | |||
| num_members: 2 | |||
| num_teams: 2 | |||
| @@ -190,6 +190,8 @@ var migrations = []Migration{ | |||
| NewMigration("remove stale watches", removeStaleWatches), | |||
| // v68 -> V69 | |||
| NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics), | |||
| // v69 -> v70 | |||
| NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), | |||
| } | |||
| // Migrate database to current version | |||
| @@ -25,10 +25,15 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) { | |||
| Created time.Time `xorm:"-"` | |||
| } | |||
| type Team struct { | |||
| ID int64 | |||
| UnitTypes []int `xorm:"json"` | |||
| } | |||
| // Update team unit types | |||
| const batchSize = 100 | |||
| for start := 0; ; start += batchSize { | |||
| teams := make([]*models.Team, 0, batchSize) | |||
| teams := make([]*Team, 0, batchSize) | |||
| if err := x.Limit(batchSize, start).Find(&teams); err != nil { | |||
| return err | |||
| } | |||
| @@ -36,7 +41,7 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) { | |||
| break | |||
| } | |||
| for _, team := range teams { | |||
| ut := make([]models.UnitType, 0, len(team.UnitTypes)) | |||
| ut := make([]int, 0, len(team.UnitTypes)) | |||
| for _, u := range team.UnitTypes { | |||
| if u < V16UnitTypeCommits { | |||
| ut = append(ut, u) | |||
| @@ -0,0 +1,80 @@ | |||
| // Copyright 2018 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 migrations | |||
| import ( | |||
| "fmt" | |||
| "github.com/go-xorm/xorm" | |||
| ) | |||
| func moveTeamUnitsToTeamUnitTable(x *xorm.Engine) error { | |||
| // Team see models/team.go | |||
| type Team struct { | |||
| ID int64 | |||
| OrgID int64 | |||
| UnitTypes []int `xorm:"json"` | |||
| } | |||
| // TeamUnit see models/org_team.go | |||
| type TeamUnit struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| OrgID int64 `xorm:"INDEX"` | |||
| TeamID int64 `xorm:"UNIQUE(s)"` | |||
| Type int `xorm:"UNIQUE(s)"` | |||
| } | |||
| if err := x.Sync2(new(TeamUnit)); err != nil { | |||
| return fmt.Errorf("Sync2: %v", err) | |||
| } | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err := sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| // Update team unit types | |||
| const batchSize = 100 | |||
| for start := 0; ; start += batchSize { | |||
| teams := make([]*Team, 0, batchSize) | |||
| if err := x.Limit(batchSize, start).Find(&teams); err != nil { | |||
| return err | |||
| } | |||
| if len(teams) == 0 { | |||
| break | |||
| } | |||
| for _, team := range teams { | |||
| var unitTypes []int | |||
| if len(team.UnitTypes) == 0 { | |||
| unitTypes = allUnitTypes | |||
| } else { | |||
| unitTypes = team.UnitTypes | |||
| } | |||
| // insert units for team | |||
| var units = make([]TeamUnit, 0, len(unitTypes)) | |||
| for _, tp := range unitTypes { | |||
| units = append(units, TeamUnit{ | |||
| OrgID: team.OrgID, | |||
| TeamID: team.ID, | |||
| Type: tp, | |||
| }) | |||
| } | |||
| if _, err := sess.Insert(&units); err != nil { | |||
| return fmt.Errorf("Insert team units: %v", err) | |||
| } | |||
| } | |||
| } | |||
| if err := dropTableColumns(sess, "team", "unit_types"); err != nil { | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| @@ -122,6 +122,7 @@ func init() { | |||
| new(Reaction), | |||
| new(IssueAssignees), | |||
| new(U2FRegistration), | |||
| new(TeamUnit), | |||
| ) | |||
| gonicNames := []string{"SSL", "UID"} | |||
| @@ -119,7 +119,17 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor | |||
| } | |||
| } | |||
| issue.loadRepo(e) | |||
| for _, watch := range watches { | |||
| issue.Repo.Units = nil | |||
| if issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypePullRequests) { | |||
| continue | |||
| } | |||
| if !issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypeIssues) { | |||
| continue | |||
| } | |||
| if err := notifyUser(watch.UserID); err != nil { | |||
| return err | |||
| } | |||
| @@ -154,12 +154,26 @@ func CreateOrganization(org, owner *User) (err error) { | |||
| Name: ownerTeamName, | |||
| Authorize: AccessModeOwner, | |||
| NumMembers: 1, | |||
| UnitTypes: allRepUnitTypes, | |||
| } | |||
| if _, err = sess.Insert(t); err != nil { | |||
| return fmt.Errorf("insert owner team: %v", err) | |||
| } | |||
| // insert units for team | |||
| var units = make([]TeamUnit, 0, len(allRepUnitTypes)) | |||
| for _, tp := range allRepUnitTypes { | |||
| units = append(units, TeamUnit{ | |||
| OrgID: org.ID, | |||
| TeamID: t.ID, | |||
| Type: tp, | |||
| }) | |||
| } | |||
| if _, err = sess.Insert(&units); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| if _, err = sess.Insert(&TeamUser{ | |||
| UID: owner.ID, | |||
| OrgID: org.ID, | |||
| @@ -238,6 +252,7 @@ func deleteOrg(e *xorm.Session, u *User) error { | |||
| &Team{OrgID: u.ID}, | |||
| &OrgUser{OrgID: u.ID}, | |||
| &TeamUser{OrgID: u.ID}, | |||
| &TeamUnit{OrgID: u.ID}, | |||
| ); err != nil { | |||
| return fmt.Errorf("deleteBeans: %v", err) | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| // Copyright 2018 The Gitea Authors. All rights reserved. | |||
| // Copyright 2016 The Gogs Authors. All rights reserved. | |||
| // Use of this source code is governed by a MIT-style | |||
| // license that can be found in the LICENSE file. | |||
| @@ -10,7 +11,6 @@ import ( | |||
| "strings" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "github.com/go-xorm/xorm" | |||
| ) | |||
| @@ -28,15 +28,16 @@ type Team struct { | |||
| Members []*User `xorm:"-"` | |||
| NumRepos int | |||
| NumMembers int | |||
| UnitTypes []UnitType `xorm:"json"` | |||
| Units []*TeamUnit `xorm:"-"` | |||
| } | |||
| // GetUnitTypes returns unit types the team owned, empty means all the unit types | |||
| func (t *Team) GetUnitTypes() []UnitType { | |||
| if len(t.UnitTypes) == 0 { | |||
| return allRepUnitTypes | |||
| func (t *Team) getUnits(e Engine) (err error) { | |||
| if t.Units != nil { | |||
| return nil | |||
| } | |||
| return t.UnitTypes | |||
| t.Units, err = getUnitsByTeamID(e, t.ID) | |||
| return err | |||
| } | |||
| // HasWriteAccess returns true if team has at least write level access mode. | |||
| @@ -214,11 +215,12 @@ func (t *Team) RemoveRepository(repoID int64) error { | |||
| // UnitEnabled returns if the team has the given unit type enabled | |||
| func (t *Team) UnitEnabled(tp UnitType) bool { | |||
| if len(t.UnitTypes) == 0 { | |||
| return true | |||
| if err := t.getUnits(x); err != nil { | |||
| log.Warn("Error loading repository (ID: %d) units: %s", t.ID, err.Error()) | |||
| } | |||
| for _, u := range t.UnitTypes { | |||
| if u == tp { | |||
| for _, unit := range t.Units { | |||
| if unit.Type == tp { | |||
| return true | |||
| } | |||
| } | |||
| @@ -275,6 +277,17 @@ func NewTeam(t *Team) (err error) { | |||
| return err | |||
| } | |||
| // insert units for team | |||
| if len(t.Units) > 0 { | |||
| for _, unit := range t.Units { | |||
| unit.TeamID = t.ID | |||
| } | |||
| if _, err = sess.Insert(&t.Units); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| } | |||
| // Update organization number of teams. | |||
| if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { | |||
| sess.Rollback() | |||
| @@ -424,6 +437,13 @@ func DeleteTeam(t *Team) error { | |||
| return err | |||
| } | |||
| // Delete team-unit. | |||
| if _, err := sess. | |||
| Where("team_id=?", t.ID). | |||
| Delete(new(TeamUnit)); err != nil { | |||
| return err | |||
| } | |||
| // Delete team. | |||
| if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil { | |||
| return err | |||
| @@ -695,3 +715,47 @@ func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, er | |||
| And("team_repo.repo_id = ?", repoID). | |||
| Find(&teams) | |||
| } | |||
| // ___________ ____ ___ .__ __ | |||
| // \__ ___/___ _____ _____ | | \____ |__|/ |_ | |||
| // | |_/ __ \\__ \ / \| | / \| \ __\ | |||
| // | |\ ___/ / __ \| Y Y \ | / | \ || | | |||
| // |____| \___ >____ /__|_| /______/|___| /__||__| | |||
| // \/ \/ \/ \/ | |||
| // TeamUnit describes all units of a repository | |||
| type TeamUnit struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| OrgID int64 `xorm:"INDEX"` | |||
| TeamID int64 `xorm:"UNIQUE(s)"` | |||
| Type UnitType `xorm:"UNIQUE(s)"` | |||
| } | |||
| // Unit returns Unit | |||
| func (t *TeamUnit) Unit() Unit { | |||
| return Units[t.Type] | |||
| } | |||
| func getUnitsByTeamID(e Engine, teamID int64) (units []*TeamUnit, err error) { | |||
| return units, e.Where("team_id = ?", teamID).Find(&units) | |||
| } | |||
| // UpdateTeamUnits updates a teams's units | |||
| func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if _, err = sess.Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil { | |||
| return err | |||
| } | |||
| if _, err = sess.Insert(units); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| @@ -489,8 +489,8 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) { | |||
| assert.NoError(t, err) | |||
| assert.EqualValues(t, expectedCount, count) | |||
| } | |||
| testSuccess(2, 2) | |||
| testSuccess(4, 1) | |||
| testSuccess(2, 3) | |||
| testSuccess(4, 2) | |||
| } | |||
| func TestAccessibleReposEnv_RepoIDs(t *testing.T) { | |||
| @@ -503,8 +503,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) { | |||
| assert.NoError(t, err) | |||
| assert.Equal(t, expectedRepoIDs, repoIDs) | |||
| } | |||
| testSuccess(2, 1, 100, []int64{3, 5}) | |||
| testSuccess(4, 0, 100, []int64{3}) | |||
| testSuccess(2, 1, 100, []int64{3, 5, 32}) | |||
| testSuccess(4, 0, 100, []int64{3, 32}) | |||
| } | |||
| func TestAccessibleReposEnv_Repos(t *testing.T) { | |||
| @@ -522,8 +522,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) { | |||
| } | |||
| assert.Equal(t, expectedRepos, repos) | |||
| } | |||
| testSuccess(2, []int64{3, 5}) | |||
| testSuccess(4, []int64{3}) | |||
| testSuccess(2, []int64{3, 5, 32}) | |||
| testSuccess(4, []int64{3, 32}) | |||
| } | |||
| func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { | |||
| @@ -365,22 +365,14 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) ( | |||
| return err | |||
| } | |||
| var allTypes = make(map[UnitType]struct{}, len(allRepUnitTypes)) | |||
| for _, team := range teams { | |||
| // Administrators can not be limited | |||
| if team.Authorize >= AccessModeAdmin { | |||
| return nil | |||
| } | |||
| for _, unitType := range team.UnitTypes { | |||
| allTypes[unitType] = struct{}{} | |||
| } | |||
| } | |||
| // unique | |||
| var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units)) | |||
| for _, u := range repo.Units { | |||
| if _, ok := allTypes[u.Type]; ok { | |||
| newRepoUnits = append(newRepoUnits, u) | |||
| for _, team := range teams { | |||
| if team.UnitEnabled(u.Type) { | |||
| newRepoUnits = append(newRepoUnits, u) | |||
| break | |||
| } | |||
| } | |||
| } | |||
| @@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) { | |||
| count: 14}, | |||
| {name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", | |||
| opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true}, | |||
| count: 15}, | |||
| count: 16}, | |||
| {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", | |||
| opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, | |||
| count: 19}, | |||
| count: 20}, | |||
| {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", | |||
| opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, | |||
| count: 13}, | |||
| @@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) { | |||
| count: 11}, | |||
| {name: "AllPublic/PublicRepositoriesOfOrganization", | |||
| opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse}, | |||
| count: 15}, | |||
| count: 16}, | |||
| } | |||
| for _, testCase := range testCases { | |||
| @@ -109,6 +109,23 @@ func notifyWatchers(e Engine, act *Action) error { | |||
| act.ID = 0 | |||
| act.UserID = watches[i].UserID | |||
| act.Repo.Units = nil | |||
| switch act.OpType { | |||
| case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch: | |||
| if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeCode) { | |||
| continue | |||
| } | |||
| case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue: | |||
| if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeIssues) { | |||
| continue | |||
| } | |||
| case ActionCreatePullRequest, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest: | |||
| if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypePullRequests) { | |||
| continue | |||
| } | |||
| } | |||
| if _, err = e.InsertOne(act); err != nil { | |||
| return fmt.Errorf("insert new action: %v", err) | |||
| } | |||
| @@ -546,28 +546,46 @@ func (u *User) GetRepositories(page, pageSize int) (err error) { | |||
| return err | |||
| } | |||
| // GetRepositoryIDs returns repositories IDs where user owned | |||
| func (u *User) GetRepositoryIDs() ([]int64, error) { | |||
| // GetRepositoryIDs returns repositories IDs where user owned and has unittypes | |||
| func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) { | |||
| var ids []int64 | |||
| return ids, x.Table("repository").Cols("id").Where("owner_id = ?", u.ID).Find(&ids) | |||
| sess := x.Table("repository").Cols("repository.id") | |||
| if len(units) > 0 { | |||
| sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id") | |||
| sess = sess.In("repo_unit.type", units) | |||
| } | |||
| return ids, sess.Where("owner_id = ?", u.ID).Find(&ids) | |||
| } | |||
| // GetOrgRepositoryIDs returns repositories IDs where user's team owned | |||
| func (u *User) GetOrgRepositoryIDs() ([]int64, error) { | |||
| // GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes | |||
| func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) { | |||
| var ids []int64 | |||
| return ids, x.Table("repository"). | |||
| sess := x.Table("repository"). | |||
| Cols("repository.id"). | |||
| Join("INNER", "team_user", "repository.owner_id = team_user.org_id AND team_user.uid = ?", u.ID). | |||
| Join("INNER", "team_user", "repository.owner_id = team_user.org_id"). | |||
| Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true) | |||
| if len(units) > 0 { | |||
| sess = sess.Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id") | |||
| sess = sess.In("team_unit.type", units) | |||
| } | |||
| return ids, sess. | |||
| Where("team_user.uid = ?", u.ID). | |||
| GroupBy("repository.id").Find(&ids) | |||
| } | |||
| // GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations | |||
| func (u *User) GetAccessRepoIDs() ([]int64, error) { | |||
| ids, err := u.GetRepositoryIDs() | |||
| func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) { | |||
| ids, err := u.GetRepositoryIDs(units...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| ids2, err := u.GetOrgRepositoryIDs() | |||
| ids2, err := u.GetOrgRepositoryIDs(units...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -159,3 +159,25 @@ func BenchmarkHashPassword(b *testing.B) { | |||
| u.HashPassword(pass) | |||
| } | |||
| } | |||
| func TestGetOrgRepositoryIDs(t *testing.T) { | |||
| assert.NoError(t, PrepareTestDatabase()) | |||
| user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | |||
| user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) | |||
| user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | |||
| accessibleRepos, err := user2.GetOrgRepositoryIDs() | |||
| assert.NoError(t, err) | |||
| // User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization | |||
| assert.Equal(t, []int64{3, 5, 32}, accessibleRepos) | |||
| accessibleRepos, err = user4.GetOrgRepositoryIDs() | |||
| assert.NoError(t, err) | |||
| // User 4's team has access to private repo 3, repo 32 is a public repo of the organization | |||
| assert.Equal(t, []int64{3, 32}, accessibleRepos) | |||
| accessibleRepos, err = user5.GetOrgRepositoryIDs() | |||
| assert.NoError(t, err) | |||
| // User 5's team has no access to any repo | |||
| assert.Len(t, accessibleRepos, 0) | |||
| } | |||
| @@ -182,7 +182,14 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) { | |||
| Authorize: models.ParseAccessMode(form.Permission), | |||
| } | |||
| if t.Authorize < models.AccessModeAdmin { | |||
| t.UnitTypes = form.Units | |||
| var units = make([]*models.TeamUnit, 0, len(form.Units)) | |||
| for _, tp := range form.Units { | |||
| units = append(units, &models.TeamUnit{ | |||
| OrgID: ctx.Org.Organization.ID, | |||
| Type: tp, | |||
| }) | |||
| } | |||
| t.Units = units | |||
| } | |||
| ctx.Data["Team"] = t | |||
| @@ -264,9 +271,17 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) { | |||
| } | |||
| t.Description = form.Description | |||
| if t.Authorize < models.AccessModeAdmin { | |||
| t.UnitTypes = form.Units | |||
| var units = make([]models.TeamUnit, 0, len(form.Units)) | |||
| for _, tp := range form.Units { | |||
| units = append(units, models.TeamUnit{ | |||
| OrgID: t.OrgID, | |||
| TeamID: t.ID, | |||
| Type: tp, | |||
| }) | |||
| } | |||
| models.UpdateTeamUnits(t, units) | |||
| } else { | |||
| t.UnitTypes = nil | |||
| models.UpdateTeamUnits(t, nil) | |||
| } | |||
| if ctx.HasError() { | |||
| @@ -203,7 +203,11 @@ func Issues(ctx *context.Context) { | |||
| return | |||
| } | |||
| } else { | |||
| userRepoIDs, err = ctxUser.GetAccessRepoIDs() | |||
| unitType := models.UnitTypeIssues | |||
| if isPullList { | |||
| unitType = models.UnitTypePullRequests | |||
| } | |||
| userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType) | |||
| if err != nil { | |||
| ctx.ServerError("ctxUser.GetAccessRepoIDs", err) | |||
| return | |||
| @@ -26,8 +26,8 @@ func TestIssues(t *testing.T) { | |||
| Issues(ctx) | |||
| assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) | |||
| assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"]) | |||
| assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"]) | |||
| assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) | |||
| assert.Len(t, ctx.Data["Issues"], 1) | |||
| assert.Len(t, ctx.Data["Repos"], 2) | |||
| assert.Len(t, ctx.Data["Repos"], 1) | |||
| } | |||
| @@ -57,7 +57,7 @@ | |||
| {{range $t, $unit := $.Units}} | |||
| <div class="field"> | |||
| <div class="ui toggle checkbox"> | |||
| <input type="checkbox" class="hidden" name="units" value="{{$unit.Type}}"{{if $.Team.UnitEnabled $unit.Type}} checked{{end}}> | |||
| <input type="checkbox" class="hidden" name="units" value="{{$unit.Type}}"{{if or (eq $.Team.ID 0) ($.Team.UnitEnabled $unit.Type)}} checked{{end}}> | |||
| <label>{{$.i18n.Tr $unit.NameKey}}</label> | |||
| <span class="help">{{$.i18n.Tr $unit.DescKey}}</span> | |||
| </div> | |||