Add teams to repo on collaboration page. (#8045)
* Add teams to repo on collaboration page. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add option for repository admins to change teams access to repo. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add comment for functions Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make RepoAdminChangeTeamAccess default false in xorm and make it default checked in template instead. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make proper language strings and fix error redirection. * Add unit tests for adding and deleting team from repository. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add database migration Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix redirect Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix locale string mismatch. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Move team access mode text logic to template. * Move collaborator access mode text logic to template.
This commit is contained in:
		
							parent
							
								
									63ff61615e
								
							
						
					
					
						commit
						a0e88dfc2e
					
				
					 30 changed files with 575 additions and 79 deletions
				
			
		| 
						 | 
				
			
			@ -1370,6 +1370,23 @@ func (err ErrTeamAlreadyExist) Error() string {
 | 
			
		|||
	return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrTeamNotExist represents a "TeamNotExist" error
 | 
			
		||||
type ErrTeamNotExist struct {
 | 
			
		||||
	OrgID  int64
 | 
			
		||||
	TeamID int64
 | 
			
		||||
	Name   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrTeamNotExist checks if an error is a ErrTeamNotExist.
 | 
			
		||||
func IsErrTeamNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrTeamNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrTeamNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Two-factor authentication
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -508,4 +508,15 @@
 | 
			
		|||
  num_stars: 0
 | 
			
		||||
  num_forks: 0
 | 
			
		||||
  num_issues: 0
 | 
			
		||||
  is_mirror: false
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 43
 | 
			
		||||
  owner_id: 26
 | 
			
		||||
  lower_name: repo26
 | 
			
		||||
  name: repo26
 | 
			
		||||
  is_private: true
 | 
			
		||||
  num_stars: 0
 | 
			
		||||
  num_forks: 0
 | 
			
		||||
  num_issues: 0
 | 
			
		||||
  is_mirror: false
 | 
			
		||||
| 
						 | 
				
			
			@ -87,3 +87,12 @@
 | 
			
		|||
  authorize: 1 # owner
 | 
			
		||||
  num_repos: 0
 | 
			
		||||
  num_members: 1
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 11
 | 
			
		||||
  org_id: 26
 | 
			
		||||
  lower_name: team11
 | 
			
		||||
  name: team11
 | 
			
		||||
  authorize: 1 # read
 | 
			
		||||
  num_repos: 0
 | 
			
		||||
  num_members: 0
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -410,3 +410,21 @@
 | 
			
		|||
  num_repos: 0
 | 
			
		||||
  num_members: 1
 | 
			
		||||
  num_teams: 1
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 26
 | 
			
		||||
  lower_name: org26
 | 
			
		||||
  name: org26
 | 
			
		||||
  full_name: "Org26"
 | 
			
		||||
  email: org26@example.com
 | 
			
		||||
  email_notifications_preference: onmention
 | 
			
		||||
  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
 | 
			
		||||
  type: 1 # organization
 | 
			
		||||
  salt: ZogKvWdyEx
 | 
			
		||||
  is_admin: false
 | 
			
		||||
  avatar: avatar26
 | 
			
		||||
  avatar_email: org26@example.com
 | 
			
		||||
  num_repos: 1
 | 
			
		||||
  num_members: 0
 | 
			
		||||
  num_teams: 1
 | 
			
		||||
  repo_admin_change_team_access: true
 | 
			
		||||
| 
						 | 
				
			
			@ -248,6 +248,8 @@ var migrations = []Migration{
 | 
			
		|||
	NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns),
 | 
			
		||||
	// v96 -> v97
 | 
			
		||||
	NewMigration("delete orphaned attachments", deleteOrphanedAttachments),
 | 
			
		||||
	// v97 -> v98
 | 
			
		||||
	NewMigration("add repo_admin_change_team_access to user", addRepoAdminChangeTeamAccessColumnForUser),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Migrate database to current version
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										15
									
								
								models/migrations/v97.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								models/migrations/v97.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
// 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 migrations
 | 
			
		||||
 | 
			
		||||
import "github.com/go-xorm/xorm"
 | 
			
		||||
 | 
			
		||||
func addRepoAdminChangeTeamAccessColumnForUser(x *xorm.Engine) error {
 | 
			
		||||
	type User struct {
 | 
			
		||||
		RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return x.Sync2(new(User))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,7 +6,6 @@
 | 
			
		|||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
| 
						 | 
				
			
			@ -20,11 +19,6 @@ import (
 | 
			
		|||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// ErrTeamNotExist team does not exist
 | 
			
		||||
	ErrTeamNotExist = errors.New("Team does not exist")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// IsOwnedBy returns true if given user is in the owner team.
 | 
			
		||||
func (org *User) IsOwnedBy(uid int64) (bool, error) {
 | 
			
		||||
	return IsOrganizationOwner(org.ID, uid)
 | 
			
		||||
| 
						 | 
				
			
			@ -304,7 +298,7 @@ type OrgUser struct {
 | 
			
		|||
func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) {
 | 
			
		||||
	ownerTeam, err := getOwnerTeam(e, orgID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == ErrTeamNotExist {
 | 
			
		||||
		if IsErrTeamNotExist(err) {
 | 
			
		||||
			log.Error("Organization does not have owner team: %d", orgID)
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -352,7 +352,7 @@ func getTeam(e Engine, orgID int64, name string) (*Team, error) {
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrTeamNotExist
 | 
			
		||||
		return nil, ErrTeamNotExist{orgID, 0, name}
 | 
			
		||||
	}
 | 
			
		||||
	return t, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -373,7 +373,7 @@ func getTeamByID(e Engine, teamID int64) (*Team, error) {
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrTeamNotExist
 | 
			
		||||
		return nil, ErrTeamNotExist{0, teamID, ""}
 | 
			
		||||
	}
 | 
			
		||||
	return t, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,11 +64,11 @@ func TestUser_GetTeam(t *testing.T) {
 | 
			
		|||
	assert.Equal(t, "team1", team.LowerName)
 | 
			
		||||
 | 
			
		||||
	_, err = org.GetTeam("does not exist")
 | 
			
		||||
	assert.Equal(t, ErrTeamNotExist, err)
 | 
			
		||||
	assert.True(t, IsErrTeamNotExist(err))
 | 
			
		||||
 | 
			
		||||
	nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
 | 
			
		||||
	_, err = nonOrg.GetTeam("team")
 | 
			
		||||
	assert.Equal(t, ErrTeamNotExist, err)
 | 
			
		||||
	assert.True(t, IsErrTeamNotExist(err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUser_GetOwnerTeam(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ func TestUser_GetOwnerTeam(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
 | 
			
		||||
	_, err = nonOrg.GetOwnerTeam()
 | 
			
		||||
	assert.Equal(t, ErrTeamNotExist, err)
 | 
			
		||||
	assert.True(t, IsErrTeamNotExist(err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUser_GetTeams(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,20 +16,6 @@ type Collaboration struct {
 | 
			
		|||
	Mode   AccessMode `xorm:"DEFAULT 2 NOT NULL"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ModeI18nKey returns the collaboration mode I18n Key
 | 
			
		||||
func (c *Collaboration) ModeI18nKey() string {
 | 
			
		||||
	switch c.Mode {
 | 
			
		||||
	case AccessModeRead:
 | 
			
		||||
		return "repo.settings.collaboration.read"
 | 
			
		||||
	case AccessModeWrite:
 | 
			
		||||
		return "repo.settings.collaboration.write"
 | 
			
		||||
	case AccessModeAdmin:
 | 
			
		||||
		return "repo.settings.collaboration.admin"
 | 
			
		||||
	default:
 | 
			
		||||
		return "repo.settings.collaboration.undefined"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddCollaborator adds new collaboration to a repository with default access mode.
 | 
			
		||||
func (repo *Repository) AddCollaborator(u *User) error {
 | 
			
		||||
	collaboration := &Collaboration{
 | 
			
		||||
| 
						 | 
				
			
			@ -183,3 +169,17 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
 | 
			
		|||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) getRepoTeams(e Engine) (teams []*Team, err error) {
 | 
			
		||||
	return teams, e.
 | 
			
		||||
		Join("INNER", "team_repo", "team_repo.team_id = team.id").
 | 
			
		||||
		Where("team.org_id = ?", repo.OwnerID).
 | 
			
		||||
		And("team_repo.repo_id=?", repo.ID).
 | 
			
		||||
		OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END").
 | 
			
		||||
		Find(&teams)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRepoTeams gets the list of teams that has access to the repository
 | 
			
		||||
func (repo *Repository) GetRepoTeams() ([]*Team, error) {
 | 
			
		||||
	return repo.getRepoTeams(x)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,17 +10,6 @@ import (
 | 
			
		|||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCollaboration_ModeI18nKey(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "repo.settings.collaboration.read",
 | 
			
		||||
		(&Collaboration{Mode: AccessModeRead}).ModeI18nKey())
 | 
			
		||||
	assert.Equal(t, "repo.settings.collaboration.write",
 | 
			
		||||
		(&Collaboration{Mode: AccessModeWrite}).ModeI18nKey())
 | 
			
		||||
	assert.Equal(t, "repo.settings.collaboration.admin",
 | 
			
		||||
		(&Collaboration{Mode: AccessModeAdmin}).ModeI18nKey())
 | 
			
		||||
	assert.Equal(t, "repo.settings.collaboration.undefined",
 | 
			
		||||
		(&Collaboration{Mode: AccessModeNone}).ModeI18nKey())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRepository_AddCollaborator(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -147,12 +147,13 @@ type User struct {
 | 
			
		|||
	NumRepos     int
 | 
			
		||||
 | 
			
		||||
	// For organization
 | 
			
		||||
	NumTeams        int
 | 
			
		||||
	NumMembers      int
 | 
			
		||||
	Teams           []*Team             `xorm:"-"`
 | 
			
		||||
	Members         UserList            `xorm:"-"`
 | 
			
		||||
	MembersIsPublic map[int64]bool      `xorm:"-"`
 | 
			
		||||
	Visibility      structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
 | 
			
		||||
	NumTeams                  int
 | 
			
		||||
	NumMembers                int
 | 
			
		||||
	Teams                     []*Team             `xorm:"-"`
 | 
			
		||||
	Members                   UserList            `xorm:"-"`
 | 
			
		||||
	MembersIsPublic           map[int64]bool      `xorm:"-"`
 | 
			
		||||
	Visibility                structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
 | 
			
		||||
	RepoAdminChangeTeamAccess bool                `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
 | 
			
		||||
	// Preferences
 | 
			
		||||
	DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -140,7 +140,10 @@ func TestSearchUsers(t *testing.T) {
 | 
			
		|||
	testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 3, PageSize: 2},
 | 
			
		||||
		[]int64{19, 25})
 | 
			
		||||
 | 
			
		||||
	testOrgSuccess(&SearchUserOptions{Page: 4, PageSize: 2},
 | 
			
		||||
	testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 4, PageSize: 2},
 | 
			
		||||
		[]int64{26})
 | 
			
		||||
 | 
			
		||||
	testOrgSuccess(&SearchUserOptions{Page: 5, PageSize: 2},
 | 
			
		||||
		[]int64{})
 | 
			
		||||
 | 
			
		||||
	// test users
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ func (users UserList) loadOrganizationOwners(e Engine, orgID int64) (map[int64]*
 | 
			
		|||
	}
 | 
			
		||||
	ownerTeam, err := getOwnerTeam(e, orgID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == ErrTeamNotExist {
 | 
			
		||||
		if IsErrTeamNotExist(err) {
 | 
			
		||||
			log.Error("Organization does not have owner team: %d", orgID)
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,13 +33,14 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
 | 
			
		|||
 | 
			
		||||
// UpdateOrgSettingForm form for updating organization settings
 | 
			
		||||
type UpdateOrgSettingForm struct {
 | 
			
		||||
	Name            string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
 | 
			
		||||
	FullName        string `binding:"MaxSize(100)"`
 | 
			
		||||
	Description     string `binding:"MaxSize(255)"`
 | 
			
		||||
	Website         string `binding:"ValidUrl;MaxSize(255)"`
 | 
			
		||||
	Location        string `binding:"MaxSize(50)"`
 | 
			
		||||
	Visibility      structs.VisibleType
 | 
			
		||||
	MaxRepoCreation int
 | 
			
		||||
	Name                      string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
 | 
			
		||||
	FullName                  string `binding:"MaxSize(100)"`
 | 
			
		||||
	Description               string `binding:"MaxSize(255)"`
 | 
			
		||||
	Website                   string `binding:"ValidUrl;MaxSize(255)"`
 | 
			
		||||
	Location                  string `binding:"MaxSize(50)"`
 | 
			
		||||
	Visibility                structs.VisibleType
 | 
			
		||||
	MaxRepoCreation           int
 | 
			
		||||
	RepoAdminChangeTeamAccess bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates the fields
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,14 +6,15 @@ package structs
 | 
			
		|||
 | 
			
		||||
// Organization represents an organization
 | 
			
		||||
type Organization struct {
 | 
			
		||||
	ID          int64  `json:"id"`
 | 
			
		||||
	UserName    string `json:"username"`
 | 
			
		||||
	FullName    string `json:"full_name"`
 | 
			
		||||
	AvatarURL   string `json:"avatar_url"`
 | 
			
		||||
	Description string `json:"description"`
 | 
			
		||||
	Website     string `json:"website"`
 | 
			
		||||
	Location    string `json:"location"`
 | 
			
		||||
	Visibility  string `json:"visibility"`
 | 
			
		||||
	ID                        int64  `json:"id"`
 | 
			
		||||
	UserName                  string `json:"username"`
 | 
			
		||||
	FullName                  string `json:"full_name"`
 | 
			
		||||
	AvatarURL                 string `json:"avatar_url"`
 | 
			
		||||
	Description               string `json:"description"`
 | 
			
		||||
	Website                   string `json:"website"`
 | 
			
		||||
	Location                  string `json:"location"`
 | 
			
		||||
	Visibility                string `json:"visibility"`
 | 
			
		||||
	RepoAdminChangeTeamAccess bool   `json:"repo_admin_change_team_access"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateOrgOption options for creating an organization
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +27,8 @@ type CreateOrgOption struct {
 | 
			
		|||
	Location    string `json:"location"`
 | 
			
		||||
	// possible values are `public` (default), `limited` or `private`
 | 
			
		||||
	// enum: public,limited,private
 | 
			
		||||
	Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
 | 
			
		||||
	Visibility                string `json:"visibility" binding:"In(,public,limited,private)"`
 | 
			
		||||
	RepoAdminChangeTeamAccess bool   `json:"repo_admin_change_team_access"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditOrgOption options for editing an organization
 | 
			
		||||
| 
						 | 
				
			
			@ -37,5 +39,6 @@ type EditOrgOption struct {
 | 
			
		|||
	Location    string `json:"location"`
 | 
			
		||||
	// possible values are `public`, `limited` or `private`
 | 
			
		||||
	// enum: public,limited,private
 | 
			
		||||
	Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
 | 
			
		||||
	Visibility                string `json:"visibility" binding:"In(,public,limited,private)"`
 | 
			
		||||
	RepoAdminChangeTeamAccess bool   `json:"repo_admin_change_team_access"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -319,6 +319,7 @@ enterred_invalid_repo_name = The repository name you entered is incorrect.
 | 
			
		|||
enterred_invalid_owner_name = The new owner name is not valid.
 | 
			
		||||
enterred_invalid_password = The password you entered is incorrect.
 | 
			
		||||
user_not_exist = The user does not exist.
 | 
			
		||||
team_not_exist = The team does not exist.
 | 
			
		||||
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team.
 | 
			
		||||
cannot_add_org_to_team = An organization cannot be added as a team member.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1136,6 +1137,7 @@ settings.collaboration = Collaborators
 | 
			
		|||
settings.collaboration.admin = Administrator
 | 
			
		||||
settings.collaboration.write = Write
 | 
			
		||||
settings.collaboration.read = Read
 | 
			
		||||
settings.collaboration.owner = Owner
 | 
			
		||||
settings.collaboration.undefined = Undefined
 | 
			
		||||
settings.hooks = Webhooks
 | 
			
		||||
settings.githooks = Git Hooks
 | 
			
		||||
| 
						 | 
				
			
			@ -1217,6 +1219,11 @@ settings.collaborator_deletion_desc = Removing a collaborator will revoke their
 | 
			
		|||
settings.remove_collaborator_success = The collaborator has been removed.
 | 
			
		||||
settings.search_user_placeholder = Search user…
 | 
			
		||||
settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator.
 | 
			
		||||
settings.change_team_access_not_allowed = Changing team access for repository has been restricted to organization owner
 | 
			
		||||
settings.team_not_in_organization = The team is not in the same organization as the repository
 | 
			
		||||
settings.add_team_duplicate = Team already has the repository
 | 
			
		||||
settings.add_team_success = The team now have access to the repository.
 | 
			
		||||
settings.remove_team_success = The team's access to the repository has been removed.
 | 
			
		||||
settings.add_webhook = Add Webhook
 | 
			
		||||
settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character.
 | 
			
		||||
settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
 | 
			
		||||
| 
						 | 
				
			
			@ -1475,6 +1482,8 @@ settings.options = Organization
 | 
			
		|||
settings.full_name = Full Name
 | 
			
		||||
settings.website = Website
 | 
			
		||||
settings.location = Location
 | 
			
		||||
settings.permission = Permissions
 | 
			
		||||
settings.repoadminchangeteam = Repository admin can add and remove access for teams
 | 
			
		||||
settings.visibility = Visibility
 | 
			
		||||
settings.visibility.public = Public
 | 
			
		||||
settings.visibility.limited = Limited (Visible to logged in users only)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -747,6 +747,8 @@ footer .ui.left,footer .ui.right{line-height:40px}
 | 
			
		|||
.repository.settings.collaboration .collaborator.list>.item:not(:last-child){border-bottom:1px solid #ddd}
 | 
			
		||||
.repository.settings.collaboration #repo-collab-form #search-user-box .results{left:7px}
 | 
			
		||||
.repository.settings.collaboration #repo-collab-form .ui.button{margin-left:5px;margin-top:-3px}
 | 
			
		||||
.repository.settings.collaboration #repo-collab-team-form #search-team-box .results{left:7px}
 | 
			
		||||
.repository.settings.collaboration #repo-collab-team-form .ui.button{margin-left:5px;margin-top:-3px}
 | 
			
		||||
.repository.settings.branches .protected-branches .selection.dropdown{width:300px}
 | 
			
		||||
.repository.settings.branches .protected-branches .item{border:1px solid #eaeaea;padding:10px 15px}
 | 
			
		||||
.repository.settings.branches .protected-branches .item:not(:last-child){border-bottom:0}
 | 
			
		||||
| 
						 | 
				
			
			@ -783,6 +785,7 @@ footer .ui.left,footer .ui.right{line-height:40px}
 | 
			
		|||
.user-cards .list .item .meta{margin-top:5px}
 | 
			
		||||
#search-repo-box .results .result .image,#search-user-box .results .result .image{float:left;margin-right:8px;width:2em;height:2em}
 | 
			
		||||
#search-repo-box .results .result .content,#search-user-box .results .result .content{margin:6px 0}
 | 
			
		||||
#search-team-box .results .result .content{margin:6px 0}
 | 
			
		||||
#issue-filters.hide{display:none}
 | 
			
		||||
#issue-actions{margin-top:-1rem!important}
 | 
			
		||||
#issue-actions.hide{display:none}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1761,6 +1761,30 @@ function searchUsers() {
 | 
			
		|||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function searchTeams() {
 | 
			
		||||
    const $searchTeamBox = $('#search-team-box');
 | 
			
		||||
    $searchTeamBox.search({
 | 
			
		||||
        minCharacters: 2,
 | 
			
		||||
        apiSettings: {
 | 
			
		||||
            url: suburl + '/api/v1/orgs/' + $searchTeamBox.data('org') + '/teams',
 | 
			
		||||
            headers: {"X-Csrf-Token": csrf},
 | 
			
		||||
            onResponse: function(response) {
 | 
			
		||||
                const items = [];
 | 
			
		||||
                $.each(response, function (_i, item) {
 | 
			
		||||
                    const title = item.name + ' (' + item.permission + ' access)';
 | 
			
		||||
                    items.push({
 | 
			
		||||
                        title: title,
 | 
			
		||||
                    })
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return { results: items }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        searchFields: ['name', 'description'],
 | 
			
		||||
        showNoResults: false
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function searchRepositories() {
 | 
			
		||||
    const $searchRepoBox = $('#search-repo-box');
 | 
			
		||||
    $searchRepoBox.search({
 | 
			
		||||
| 
						 | 
				
			
			@ -2171,6 +2195,7 @@ $(document).ready(function () {
 | 
			
		|||
 | 
			
		||||
    buttonsClickOnEnter();
 | 
			
		||||
    searchUsers();
 | 
			
		||||
    searchTeams();
 | 
			
		||||
    searchRepositories();
 | 
			
		||||
 | 
			
		||||
    initCommentForm();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1736,6 +1736,19 @@
 | 
			
		|||
                    margin-top: -3px;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            #repo-collab-team-form {
 | 
			
		||||
                #search-team-box {
 | 
			
		||||
                    .results {
 | 
			
		||||
                        left: 7px;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                .ui.button {
 | 
			
		||||
                    margin-left: 5px;
 | 
			
		||||
                    margin-top: -3px;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.branches {
 | 
			
		||||
| 
						 | 
				
			
			@ -1936,6 +1949,16 @@
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#search-team-box {
 | 
			
		||||
    .results {
 | 
			
		||||
        .result {
 | 
			
		||||
            .content {
 | 
			
		||||
                margin: 6px 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#issue-filters.hide {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -206,14 +206,15 @@ func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
 | 
			
		|||
// ToOrganization convert models.User to api.Organization
 | 
			
		||||
func ToOrganization(org *models.User) *api.Organization {
 | 
			
		||||
	return &api.Organization{
 | 
			
		||||
		ID:          org.ID,
 | 
			
		||||
		AvatarURL:   org.AvatarLink(),
 | 
			
		||||
		UserName:    org.Name,
 | 
			
		||||
		FullName:    org.FullName,
 | 
			
		||||
		Description: org.Description,
 | 
			
		||||
		Website:     org.Website,
 | 
			
		||||
		Location:    org.Location,
 | 
			
		||||
		Visibility:  org.Visibility.String(),
 | 
			
		||||
		ID:                        org.ID,
 | 
			
		||||
		AvatarURL:                 org.AvatarLink(),
 | 
			
		||||
		UserName:                  org.Name,
 | 
			
		||||
		FullName:                  org.FullName,
 | 
			
		||||
		Description:               org.Description,
 | 
			
		||||
		Website:                   org.Website,
 | 
			
		||||
		Location:                  org.Location,
 | 
			
		||||
		Visibility:                org.Visibility.String(),
 | 
			
		||||
		RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,14 +95,15 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		Name:        form.UserName,
 | 
			
		||||
		FullName:    form.FullName,
 | 
			
		||||
		Description: form.Description,
 | 
			
		||||
		Website:     form.Website,
 | 
			
		||||
		Location:    form.Location,
 | 
			
		||||
		IsActive:    true,
 | 
			
		||||
		Type:        models.UserTypeOrganization,
 | 
			
		||||
		Visibility:  visibility,
 | 
			
		||||
		Name:                      form.UserName,
 | 
			
		||||
		FullName:                  form.FullName,
 | 
			
		||||
		Description:               form.Description,
 | 
			
		||||
		Website:                   form.Website,
 | 
			
		||||
		Location:                  form.Location,
 | 
			
		||||
		IsActive:                  true,
 | 
			
		||||
		Type:                      models.UserTypeOrganization,
 | 
			
		||||
		Visibility:                visibility,
 | 
			
		||||
		RepoAdminChangeTeamAccess: form.RepoAdminChangeTeamAccess,
 | 
			
		||||
	}
 | 
			
		||||
	if err := models.CreateOrganization(org, ctx.User); err != nil {
 | 
			
		||||
		if models.IsErrUserAlreadyExist(err) ||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,6 +83,7 @@ func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) {
 | 
			
		|||
	org.Website = form.Website
 | 
			
		||||
	org.Location = form.Location
 | 
			
		||||
	org.Visibility = form.Visibility
 | 
			
		||||
	org.RepoAdminChangeTeamAccess = form.RepoAdminChangeTeamAccess
 | 
			
		||||
	if err := models.UpdateUser(org); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateUser", err)
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -490,6 +490,18 @@ func Collaboration(ctx *context.Context) {
 | 
			
		|||
	}
 | 
			
		||||
	ctx.Data["Collaborators"] = users
 | 
			
		||||
 | 
			
		||||
	teams, err := ctx.Repo.Repository.GetRepoTeams()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetRepoTeams", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Teams"] = teams
 | 
			
		||||
	ctx.Data["Repo"] = ctx.Repo.Repository
 | 
			
		||||
	ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID
 | 
			
		||||
	ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName
 | 
			
		||||
	ctx.Data["Org"] = ctx.Repo.Repository.Owner
 | 
			
		||||
	ctx.Data["Units"] = models.Units
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, tplCollaboration)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -566,6 +578,77 @@ func DeleteCollaboration(ctx *context.Context) {
 | 
			
		|||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddTeamPost response for adding a team to a repository
 | 
			
		||||
func AddTeamPost(ctx *context.Context) {
 | 
			
		||||
	if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("team")))
 | 
			
		||||
	if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team, err := ctx.Repo.Owner.GetTeam(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrTeamNotExist(err) {
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
 | 
			
		||||
			ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.ServerError("GetTeam", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if team.OrgID != ctx.Repo.Repository.OwnerID {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if models.HasTeamRepo(ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = team.AddRepository(ctx.Repo.Repository); err != nil {
 | 
			
		||||
		ctx.ServerError("team.AddRepository", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success"))
 | 
			
		||||
	ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteTeam response for deleting a team from a repository
 | 
			
		||||
func DeleteTeam(ctx *context.Context) {
 | 
			
		||||
	if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team, err := models.GetTeamByID(ctx.QueryInt64("id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetTeamByID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = team.RemoveRepository(ctx.Repo.Repository.ID); err != nil {
 | 
			
		||||
		ctx.ServerError("team.RemoveRepositorys", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success"))
 | 
			
		||||
	ctx.JSON(200, map[string]interface{}{
 | 
			
		||||
		"redirect": ctx.Repo.RepoLink + "/settings/collaboration",
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseOwnerAndRepo get repos by owner
 | 
			
		||||
func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
 | 
			
		||||
	owner, err := models.GetUserByName(ctx.Params(":username"))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,3 +185,196 @@ func TestCollaborationPost_NonExistentUser(t *testing.T) {
 | 
			
		|||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team11")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    11,
 | 
			
		||||
		OrgID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
 | 
			
		||||
	assert.True(t, team.HasRepository(re.ID))
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.Empty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost_NotAllowed(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team11")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    11,
 | 
			
		||||
		OrgID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: false,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
 | 
			
		||||
	assert.False(t, team.HasRepository(re.ID))
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost_AddTeamTwice(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team11")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    11,
 | 
			
		||||
		OrgID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
	assert.True(t, team.HasRepository(re.ID))
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost_NonExistentTeam(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team-non-existent")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteTeam(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org3/team1/repo3")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("id", "2")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org3",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    2,
 | 
			
		||||
		OrgID: 3,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      3,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 3,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        3,
 | 
			
		||||
			LowerName:                 "org3",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	DeleteTeam(ctx)
 | 
			
		||||
 | 
			
		||||
	assert.False(t, team.HasRepository(re.ID))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -629,6 +629,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		|||
				m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
 | 
			
		||||
				m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
 | 
			
		||||
				m.Post("/delete", repo.DeleteCollaboration)
 | 
			
		||||
				m.Group("/team", func() {
 | 
			
		||||
					m.Post("", repo.AddTeamPost)
 | 
			
		||||
					m.Post("/delete", repo.DeleteTeam)
 | 
			
		||||
				})
 | 
			
		||||
			})
 | 
			
		||||
			m.Group("/branches", func() {
 | 
			
		||||
				m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,17 @@
 | 
			
		|||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="field" id="permission_box">
 | 
			
		||||
						<label>{{.i18n.Tr "org.settings.permission"}}</label>
 | 
			
		||||
						<div class="field">
 | 
			
		||||
							<div class="ui checkbox">
 | 
			
		||||
								<input class="hidden" type="checkbox" name="repo_admin_change_team_access" checked/>
 | 
			
		||||
								<label>{{.i18n.Tr "org.settings.repoadminchangeteam"}}</label>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="inline field">
 | 
			
		||||
						<label></label>
 | 
			
		||||
						<button class="ui green button">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,6 +56,16 @@
 | 
			
		|||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="field" id="permission_box">
 | 
			
		||||
							<label>{{.i18n.Tr "org.settings.permission"}}</label>
 | 
			
		||||
							<div class="field">
 | 
			
		||||
								<div class="ui checkbox">
 | 
			
		||||
									<input class="hidden" type="checkbox" name="repo_admin_change_team_access" checked/>
 | 
			
		||||
									<label>{{.i18n.Tr "org.settings.repoadminchangeteam"}}</label>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						{{if .SignedUser.IsAdmin}}
 | 
			
		||||
						<div class="ui divider"></div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@
 | 
			
		|||
					<div class="ui eight wide column">
 | 
			
		||||
						<span class="octicon octicon-shield"></span>
 | 
			
		||||
						<div class="ui inline dropdown">
 | 
			
		||||
							<div class="text">{{$.i18n.Tr .Collaboration.ModeI18nKey}}</div>
 | 
			
		||||
							<div class="text">{{if eq .Collaboration.Mode 1}}{{$.i18n.Tr "repo.settings.collaboration.read"}}{{else if eq .Collaboration.Mode 2}}{{$.i18n.Tr "repo.settings.collaboration.write"}}{{else if eq .Collaboration.Mode 3}}{{$.i18n.Tr "repo.settings.collaboration.admin"}}{{else}}{{$.i18n.Tr "repo.settings.collaboration.undefined"}}{{end}}</div>
 | 
			
		||||
							<i class="dropdown icon"></i>
 | 
			
		||||
							<div class="access-mode menu" data-url="{{$.Link}}/access_mode" data-uid="{{.ID}}">
 | 
			
		||||
							<div class="item" data-text="{{$.i18n.Tr "repo.settings.collaboration.admin"}}" data-value="3">{{$.i18n.Tr "repo.settings.collaboration.admin"}}</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +51,63 @@
 | 
			
		|||
				<button class="ui green button">{{.i18n.Tr "repo.settings.add_collaborator"}}</button>
 | 
			
		||||
			</form>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<h4 class="ui top attached header">
 | 
			
		||||
			Teams
 | 
			
		||||
		</h4>
 | 
			
		||||
		{{ $allowedToChangeTeams := ( or (.Org.RepoAdminChangeTeamAccess) (.Permission.IsOwner)) }}
 | 
			
		||||
		{{if .Teams}}
 | 
			
		||||
		<div class="ui attached segment collaborator list">
 | 
			
		||||
			{{range $t, $team := .Teams}}
 | 
			
		||||
				<div class="item ui grid">
 | 
			
		||||
					<div class="ui five wide column">
 | 
			
		||||
						<a href="{{AppSubUrl}}/org/{{$.OrgName}}/teams/{{.LowerName}}">
 | 
			
		||||
							{{.Name}}
 | 
			
		||||
						</a>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="ui eight wide column poping up" data-content="Team's permission is set on the team setting page and can't be changed per repository">
 | 
			
		||||
						<span class="octicon octicon-shield"></span>
 | 
			
		||||
						<div class="ui inline dropdown">
 | 
			
		||||
							<div class="text">{{if eq .Authorize 1}}{{$.i18n.Tr "repo.settings.collaboration.read"}}{{else if eq .Authorize 2}}{{$.i18n.Tr "repo.settings.collaboration.write"}}{{else if eq .Authorize 3}}{{$.i18n.Tr "repo.settings.collaboration.admin"}}{{else if eq .Authorize 4}}{{$.i18n.Tr "repo.settings.collaboration.owner"}}{{else}}{{$.i18n.Tr "repo.settings.collaboration.undefined"}}{{end}}</div>
 | 
			
		||||
						</div>
 | 
			
		||||
						{{ if or (eq .Authorize 1) (eq .Authorize 2) }}
 | 
			
		||||
							{{ $first := true }}
 | 
			
		||||
							<div class="description">
 | 
			
		||||
							Sections: {{range $u, $unit := $.Units}}{{if and ($.Repo.UnitEnabled $unit.Type) ($team.UnitEnabled $unit.Type)}}{{if $first}}{{ $first = false }}{{else}}, {{end}}{{$.i18n.Tr $unit.NameKey}}{{end}}{{end}} {{if $first}}None{{end}}
 | 
			
		||||
							</div>
 | 
			
		||||
						{{end}}
 | 
			
		||||
					</div>
 | 
			
		||||
					{{if $allowedToChangeTeams}}
 | 
			
		||||
						{{ $globalRepoAccess := (eq .LowerName "owners") }}
 | 
			
		||||
						<div class="ui two wide column {{if $globalRepoAccess}}poping up{{end}}" {{if $globalRepoAccess}}data-content="This team has access to all repositories and can't be removed."{{end}}>
 | 
			
		||||
							<button class="ui red tiny button inline text-thin delete-button {{if $globalRepoAccess}}disabled{{end}}" data-url="{{$.Link}}/team/delete" data-id="{{.ID}}">
 | 
			
		||||
									{{$.i18n.Tr "repo.settings.delete_collaborator"}}
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
		<div class="ui bottom attached segment">
 | 
			
		||||
			{{if $allowedToChangeTeams}}
 | 
			
		||||
				<form class="ui form" id="repo-collab-team-form" action="{{.Link}}/team" method="post">
 | 
			
		||||
					{{.CsrfTokenHtml}}
 | 
			
		||||
					<div class="inline field ui left">
 | 
			
		||||
						<div id="search-team-box" class="ui search" data-org="{{.OrgID}}">
 | 
			
		||||
							<div class="ui input">
 | 
			
		||||
								<input class="prompt" name="team" placeholder="Search teams..." autocomplete="off" autofocus required>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<button class="ui green button">Add Team</button>
 | 
			
		||||
				</form>
 | 
			
		||||
			{{else}}
 | 
			
		||||
				<div class="item">
 | 
			
		||||
					Changing team access for repository has been restricted to organization owner
 | 
			
		||||
				</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7718,6 +7718,10 @@
 | 
			
		|||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Location"
 | 
			
		||||
        },
 | 
			
		||||
        "repo_admin_change_team_access": {
 | 
			
		||||
          "type": "boolean",
 | 
			
		||||
          "x-go-name": "RepoAdminChangeTeamAccess"
 | 
			
		||||
        },
 | 
			
		||||
        "username": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "UserName"
 | 
			
		||||
| 
						 | 
				
			
			@ -8262,6 +8266,10 @@
 | 
			
		|||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Location"
 | 
			
		||||
        },
 | 
			
		||||
        "repo_admin_change_team_access": {
 | 
			
		||||
          "type": "boolean",
 | 
			
		||||
          "x-go-name": "RepoAdminChangeTeamAccess"
 | 
			
		||||
        },
 | 
			
		||||
        "visibility": {
 | 
			
		||||
          "description": "possible values are `public`, `limited` or `private`",
 | 
			
		||||
          "type": "string",
 | 
			
		||||
| 
						 | 
				
			
			@ -9271,6 +9279,10 @@
 | 
			
		|||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Location"
 | 
			
		||||
        },
 | 
			
		||||
        "repo_admin_change_team_access": {
 | 
			
		||||
          "type": "boolean",
 | 
			
		||||
          "x-go-name": "RepoAdminChangeTeamAccess"
 | 
			
		||||
        },
 | 
			
		||||
        "username": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "UserName"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue