Merge branch 'master' into theme
This commit is contained in:
commit
c47dc20a89
|
|
@ -4,7 +4,7 @@ workspace:
|
||||||
|
|
||||||
clone:
|
clone:
|
||||||
git:
|
git:
|
||||||
image: plugins/git:1
|
image: plugins/git:next
|
||||||
depth: 50
|
depth: 50
|
||||||
tags: true
|
tags: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -603,9 +603,9 @@ ko-KR = ko
|
||||||
[U2F]
|
[U2F]
|
||||||
; Two Factor authentication with security keys
|
; Two Factor authentication with security keys
|
||||||
; https://developers.yubico.com/U2F/App_ID.html
|
; https://developers.yubico.com/U2F/App_ID.html
|
||||||
APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
|
APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||||
; Comma seperated list of truisted facets
|
; Comma seperated list of truisted facets
|
||||||
TRUSTED_FACETS = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
|
TRUSTED_FACETS = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||||
|
|
||||||
; Extension mapping to highlight class
|
; Extension mapping to highlight class
|
||||||
; e.g. .toml=ini
|
; e.g. .toml=ini
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
/usr/sbin/update-ca-certificates
|
|
||||||
|
|
||||||
if [ ! -d /data/git/.ssh ]; then
|
if [ ! -d /data/git/.ssh ]; then
|
||||||
mkdir -p /data/git/.ssh
|
mkdir -p /data/git/.ssh
|
||||||
chmod 700 /data/git/.ssh
|
chmod 700 /data/git/.ssh
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
|
||||||
expectedResults
|
expectedResults
|
||||||
}{
|
}{
|
||||||
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
|
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
|
||||||
nil: {count: 15},
|
nil: {count: 16},
|
||||||
user: {count: 15},
|
user: {count: 16},
|
||||||
user2: {count: 15}},
|
user2: {count: 16}},
|
||||||
},
|
},
|
||||||
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
|
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
|
||||||
nil: {count: 10},
|
nil: {count: 10},
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,12 @@ func TestAccessLevel(t *testing.T) {
|
||||||
|
|
||||||
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
||||||
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
|
// A public repository owned by User 2
|
||||||
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
|
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)
|
level, err := AccessLevel(user1.ID, repo1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -47,8 +51,12 @@ func TestHasAccess(t *testing.T) {
|
||||||
|
|
||||||
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
||||||
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
|
// A public repository owned by User 2
|
||||||
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
|
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 {
|
for _, accessMode := range accessModes {
|
||||||
has, err := HasAccess(user1.ID, repo1, accessMode)
|
has, err := HasAccess(user1.ID, repo1, accessMode)
|
||||||
|
|
|
||||||
|
|
@ -389,3 +389,14 @@
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
is_mirror: false
|
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
|
lower_name: owners
|
||||||
name: Owners
|
name: Owners
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 2
|
num_repos: 3
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 2
|
id: 2
|
||||||
|
|
@ -16,7 +15,6 @@
|
||||||
authorize: 2 # write
|
authorize: 2 # write
|
||||||
num_repos: 1
|
num_repos: 1
|
||||||
num_members: 2
|
num_members: 2
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 3
|
id: 3
|
||||||
|
|
@ -26,7 +24,6 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 4
|
id: 4
|
||||||
|
|
@ -36,7 +33,6 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 5
|
id: 5
|
||||||
|
|
@ -46,7 +42,6 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 2
|
num_repos: 2
|
||||||
num_members: 2
|
num_members: 2
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 6
|
id: 6
|
||||||
|
|
@ -56,4 +51,3 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 2
|
num_repos: 2
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
@ -39,3 +39,9 @@
|
||||||
org_id: 19
|
org_id: 19
|
||||||
team_id: 6
|
team_id: 6
|
||||||
repo_id: 28
|
repo_id: 28
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 8
|
||||||
|
org_id: 3
|
||||||
|
team_id: 1
|
||||||
|
repo_id: 32
|
||||||
|
|
|
||||||
209
models/fixtures/team_unit.yml
Normal file
209
models/fixtures/team_unit.yml
Normal file
|
|
@ -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
|
is_admin: false
|
||||||
avatar: avatar3
|
avatar: avatar3
|
||||||
avatar_email: user3@example.com
|
avatar_email: user3@example.com
|
||||||
num_repos: 2
|
num_repos: 3
|
||||||
num_members: 2
|
num_members: 2
|
||||||
num_teams: 2
|
num_teams: 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,3 +71,15 @@ func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error
|
||||||
Find(&watches)
|
Find(&watches)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeIssueWatchersByRepoID(e Engine, userID int64, repoID int64) error {
|
||||||
|
iw := &IssueWatch{
|
||||||
|
IsWatching: false,
|
||||||
|
}
|
||||||
|
_, err := e.
|
||||||
|
Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", repoID).
|
||||||
|
Cols("is_watching", "updated_unix").
|
||||||
|
Where("`issue_watch`.user_id = ?", userID).
|
||||||
|
Update(iw)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,12 @@ var migrations = []Migration{
|
||||||
NewMigration("add u2f", addU2FReg),
|
NewMigration("add u2f", addU2FReg),
|
||||||
// v66 -> v67
|
// v66 -> v67
|
||||||
NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
|
NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
|
||||||
|
// v67 -> v68
|
||||||
|
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
|
// Migrate database to current version
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,15 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
|
||||||
Created time.Time `xorm:"-"`
|
Created time.Time `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Team struct {
|
||||||
|
ID int64
|
||||||
|
UnitTypes []int `xorm:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
// Update team unit types
|
// Update team unit types
|
||||||
const batchSize = 100
|
const batchSize = 100
|
||||||
for start := 0; ; start += batchSize {
|
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 {
|
if err := x.Limit(batchSize, start).Find(&teams); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +41,7 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
ut := make([]models.UnitType, 0, len(team.UnitTypes))
|
ut := make([]int, 0, len(team.UnitTypes))
|
||||||
for _, u := range team.UnitTypes {
|
for _, u := range team.UnitTypes {
|
||||||
if u < V16UnitTypeCommits {
|
if u < V16UnitTypeCommits {
|
||||||
ut = append(ut, u)
|
ut = append(ut, u)
|
||||||
|
|
|
||||||
158
models/migrations/v67.go
Normal file
158
models/migrations/v67.go
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
// 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 (
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func removeStaleWatches(x *xorm.Engine) error {
|
||||||
|
type Watch struct {
|
||||||
|
ID int64
|
||||||
|
UserID int64
|
||||||
|
RepoID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type IssueWatch struct {
|
||||||
|
ID int64
|
||||||
|
UserID int64
|
||||||
|
RepoID int64
|
||||||
|
IsWatching bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
ID int64
|
||||||
|
IsPrivate bool
|
||||||
|
OwnerID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Access struct {
|
||||||
|
UserID int64
|
||||||
|
RepoID int64
|
||||||
|
Mode int
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccessModeNone no access
|
||||||
|
AccessModeNone int = iota // 0
|
||||||
|
// AccessModeRead read access
|
||||||
|
AccessModeRead // 1
|
||||||
|
)
|
||||||
|
|
||||||
|
accessLevel := func(userID int64, repo *Repository) (int, error) {
|
||||||
|
mode := AccessModeNone
|
||||||
|
if !repo.IsPrivate {
|
||||||
|
mode = AccessModeRead
|
||||||
|
}
|
||||||
|
|
||||||
|
if userID == 0 {
|
||||||
|
return mode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if userID == repo.OwnerID {
|
||||||
|
return 4, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &Access{UserID: userID, RepoID: repo.ID}
|
||||||
|
if has, err := x.Get(a); !has || err != nil {
|
||||||
|
return mode, err
|
||||||
|
}
|
||||||
|
return a.Mode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoCache := make(map[int64]*Repository)
|
||||||
|
err := x.BufferSize(setting.IterateBufferSize).Iterate(new(Watch),
|
||||||
|
func(idx int, bean interface{}) error {
|
||||||
|
watch := bean.(*Watch)
|
||||||
|
|
||||||
|
repo := repoCache[watch.RepoID]
|
||||||
|
if repo == nil {
|
||||||
|
repo = &Repository{
|
||||||
|
ID: watch.RepoID,
|
||||||
|
}
|
||||||
|
if _, err := x.Get(repo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repoCache[watch.RepoID] = repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove watches from now unaccessible repositories
|
||||||
|
mode, err := accessLevel(watch.UserID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
has := AccessModeRead <= mode
|
||||||
|
if has {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Delete(&Watch{0, watch.UserID, repo.ID}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = sess.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repo.ID)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoCache = make(map[int64]*Repository)
|
||||||
|
err = x.BufferSize(setting.IterateBufferSize).
|
||||||
|
Distinct("issue_watch.user_id", "issue.repo_id").
|
||||||
|
Join("INNER", "issue", "issue_watch.issue_id = issue.id").
|
||||||
|
Where("issue_watch.is_watching = ?", true).
|
||||||
|
Iterate(new(IssueWatch),
|
||||||
|
func(idx int, bean interface{}) error {
|
||||||
|
watch := bean.(*IssueWatch)
|
||||||
|
|
||||||
|
repo := repoCache[watch.RepoID]
|
||||||
|
if repo == nil {
|
||||||
|
repo = &Repository{
|
||||||
|
ID: watch.RepoID,
|
||||||
|
}
|
||||||
|
if _, err := x.Get(repo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repoCache[watch.RepoID] = repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove issue watches from now unaccssible repositories
|
||||||
|
mode, err := accessLevel(watch.UserID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
has := AccessModeRead <= mode
|
||||||
|
if has {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iw := &IssueWatch{
|
||||||
|
IsWatching: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = sess.
|
||||||
|
Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", watch.RepoID).
|
||||||
|
Cols("is_watching", "updated_unix").
|
||||||
|
Where("`issue_watch`.user_id = ?", watch.UserID).
|
||||||
|
Update(iw)
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
160
models/migrations/v68.go
Normal file
160
models/migrations/v68.go
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
// 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 (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func reformatAndRemoveIncorrectTopics(x *xorm.Engine) (err error) {
|
||||||
|
log.Info("This migration could take up to minutes, please be patient.")
|
||||||
|
type Topic struct {
|
||||||
|
ID int64
|
||||||
|
Name string `xorm:"unique"`
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
const batchSize = 100
|
||||||
|
touchedRepo := make(map[int64]struct{})
|
||||||
|
topics := make([]*Topic, 0, batchSize)
|
||||||
|
delTopicIDs := make([]int64, 0, batchSize)
|
||||||
|
ids := make([]int64, 0, 30)
|
||||||
|
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Validating existed topics...")
|
||||||
|
for start := 0; ; start += batchSize {
|
||||||
|
topics = topics[:0]
|
||||||
|
if err := sess.Asc("id").Limit(batchSize, start).Find(&topics); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(topics) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, topic := range topics {
|
||||||
|
if models.ValidateTopic(topic.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
topic.Name = strings.Replace(strings.TrimSpace(strings.ToLower(topic.Name)), " ", "-", -1)
|
||||||
|
|
||||||
|
if err := sess.Table("repo_topic").Cols("repo_id").
|
||||||
|
Where("topic_id = ?", topic.ID).Find(&ids); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, id := range ids {
|
||||||
|
touchedRepo[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if models.ValidateTopic(topic.Name) {
|
||||||
|
log.Info("Updating topic: id = %v, name = %v", topic.ID, topic.Name)
|
||||||
|
if _, err := sess.Table("topic").ID(topic.ID).
|
||||||
|
Update(&Topic{Name: topic.Name}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delTopicIDs = append(delTopicIDs, topic.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Deleting incorrect topics...")
|
||||||
|
for start := 0; ; start += batchSize {
|
||||||
|
if (start + batchSize) < len(delTopicIDs) {
|
||||||
|
ids = delTopicIDs[start:(start + batchSize)]
|
||||||
|
} else {
|
||||||
|
ids = delTopicIDs[start:]
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Deleting 'repo_topic' rows for topics with ids = %v", ids)
|
||||||
|
if _, err := sess.In("topic_id", ids).Delete(&models.RepoTopic{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Deleting topics with id = %v", ids)
|
||||||
|
if _, err := sess.In("id", ids).Delete(&Topic{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ids) < batchSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repoTopics := make([]*models.RepoTopic, 0, batchSize)
|
||||||
|
delRepoTopics := make([]*models.RepoTopic, 0, batchSize)
|
||||||
|
tmpRepoTopics := make([]*models.RepoTopic, 0, 30)
|
||||||
|
|
||||||
|
log.Info("Checking the number of topics in the repositories...")
|
||||||
|
for start := 0; ; start += batchSize {
|
||||||
|
repoTopics = repoTopics[:0]
|
||||||
|
if err := sess.Cols("repo_id").Asc("repo_id").Limit(batchSize, start).
|
||||||
|
GroupBy("repo_id").Having("COUNT(*) > 25").Find(&repoTopics); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(repoTopics) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Number of repositories with more than 25 topics: %v", len(repoTopics))
|
||||||
|
for _, repoTopic := range repoTopics {
|
||||||
|
touchedRepo[repoTopic.RepoID] = struct{}{}
|
||||||
|
|
||||||
|
tmpRepoTopics = tmpRepoTopics[:0]
|
||||||
|
if err := sess.Where("repo_id = ?", repoTopic.RepoID).Find(&tmpRepoTopics); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Repository with id = %v has %v topics", repoTopic.RepoID, len(tmpRepoTopics))
|
||||||
|
|
||||||
|
for i := len(tmpRepoTopics) - 1; i > 24; i-- {
|
||||||
|
delRepoTopics = append(delRepoTopics, tmpRepoTopics[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Deleting superfluous topics for repositories (more than 25 topics)...")
|
||||||
|
for _, repoTopic := range delRepoTopics {
|
||||||
|
log.Info("Deleting 'repo_topic' rows for 'repository' with id = %v. Topic id = %v",
|
||||||
|
repoTopic.RepoID, repoTopic.TopicID)
|
||||||
|
|
||||||
|
if _, err := sess.Where("repo_id = ? AND topic_id = ?", repoTopic.RepoID,
|
||||||
|
repoTopic.TopicID).Delete(&models.RepoTopic{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := sess.Exec(
|
||||||
|
"UPDATE topic SET repo_count = (SELECT repo_count FROM topic WHERE id = ?) - 1 WHERE id = ?",
|
||||||
|
repoTopic.TopicID, repoTopic.TopicID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topicNames := make([]string, 0, 30)
|
||||||
|
log.Info("Updating repositories 'topics' fields...")
|
||||||
|
for repoID := range touchedRepo {
|
||||||
|
if err := sess.Table("topic").Cols("name").
|
||||||
|
Join("INNER", "repo_topic", "topic.id = repo_topic.topic_id").
|
||||||
|
Where("repo_topic.repo_id = ?", repoID).Find(&topicNames); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Updating 'topics' field for repository with id = %v", repoID)
|
||||||
|
if _, err := sess.ID(repoID).Cols("topics").
|
||||||
|
Update(&models.Repository{Topics: topicNames}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
80
models/migrations/v69.go
Normal file
80
models/migrations/v69.go
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
@ -121,6 +122,7 @@ func init() {
|
||||||
new(Reaction),
|
new(Reaction),
|
||||||
new(IssueAssignees),
|
new(IssueAssignees),
|
||||||
new(U2FRegistration),
|
new(U2FRegistration),
|
||||||
|
new(TeamUnit),
|
||||||
)
|
)
|
||||||
|
|
||||||
gonicNames := []string{"SSL", "UID"}
|
gonicNames := []string{"SSL", "UID"}
|
||||||
|
|
@ -184,6 +186,18 @@ func parsePostgreSQLHostPort(info string) (string, string) {
|
||||||
return host, port
|
return host, port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPostgreSQLConnectionString(DBHost, DBUser, DBPasswd, DBName, DBParam, DBSSLMode string) (connStr string) {
|
||||||
|
host, port := parsePostgreSQLHostPort(DBHost)
|
||||||
|
if host[0] == '/' { // looks like a unix socket
|
||||||
|
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
|
||||||
|
url.PathEscape(DBUser), url.PathEscape(DBPasswd), port, DBName, DBParam, DBSSLMode, host)
|
||||||
|
} else {
|
||||||
|
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
|
||||||
|
url.PathEscape(DBUser), url.PathEscape(DBPasswd), host, port, DBName, DBParam, DBSSLMode)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func parseMSSQLHostPort(info string) (string, string) {
|
func parseMSSQLHostPort(info string) (string, string) {
|
||||||
host, port := "127.0.0.1", "1433"
|
host, port := "127.0.0.1", "1433"
|
||||||
if strings.Contains(info, ":") {
|
if strings.Contains(info, ":") {
|
||||||
|
|
@ -214,14 +228,7 @@ func getEngine() (*xorm.Engine, error) {
|
||||||
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
|
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
|
||||||
}
|
}
|
||||||
case "postgres":
|
case "postgres":
|
||||||
host, port := parsePostgreSQLHostPort(DbCfg.Host)
|
connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode)
|
||||||
if host[0] == '/' { // looks like a unix socket
|
|
||||||
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
|
|
||||||
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), port, DbCfg.Name, Param, DbCfg.SSLMode, host)
|
|
||||||
} else {
|
|
||||||
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
|
|
||||||
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, Param, DbCfg.SSLMode)
|
|
||||||
}
|
|
||||||
case "mssql":
|
case "mssql":
|
||||||
host, port := parseMSSQLHostPort(DbCfg.Host)
|
host, port := parseMSSQLHostPort(DbCfg.Host)
|
||||||
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd)
|
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
@ -53,3 +54,42 @@ func Test_parsePostgreSQLHostPort(t *testing.T) {
|
||||||
assert.Equal(t, test.Port, port)
|
assert.Equal(t, test.Port, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_getPostgreSQLConnectionString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
User string
|
||||||
|
Passwd string
|
||||||
|
Name string
|
||||||
|
Param string
|
||||||
|
SSLMode string
|
||||||
|
Output string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Host: "/tmp/pg.sock",
|
||||||
|
Port: "4321",
|
||||||
|
User: "testuser",
|
||||||
|
Passwd: "space space !#$%^^%^```-=?=",
|
||||||
|
Name: "gitea",
|
||||||
|
Param: "",
|
||||||
|
SSLMode: "false",
|
||||||
|
Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/giteasslmode=false&host=/tmp/pg.sock",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Host: "localhost",
|
||||||
|
Port: "1234",
|
||||||
|
User: "pgsqlusername",
|
||||||
|
Passwd: "I love Gitea!",
|
||||||
|
Name: "gitea",
|
||||||
|
Param: "",
|
||||||
|
SSLMode: "true",
|
||||||
|
Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/giteasslmode=true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
connStr := getPostgreSQLConnectionString(test.Host, test.User, test.Passwd, test.Name, test.Param, test.SSLMode)
|
||||||
|
assert.Equal(t, test.Output, connStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,17 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
issue.loadRepo(e)
|
||||||
|
|
||||||
for _, watch := range watches {
|
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 {
|
if err := notifyUser(watch.UserID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,12 +154,26 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||||
Name: ownerTeamName,
|
Name: ownerTeamName,
|
||||||
Authorize: AccessModeOwner,
|
Authorize: AccessModeOwner,
|
||||||
NumMembers: 1,
|
NumMembers: 1,
|
||||||
UnitTypes: allRepUnitTypes,
|
|
||||||
}
|
}
|
||||||
if _, err = sess.Insert(t); err != nil {
|
if _, err = sess.Insert(t); err != nil {
|
||||||
return fmt.Errorf("insert owner team: %v", err)
|
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{
|
if _, err = sess.Insert(&TeamUser{
|
||||||
UID: owner.ID,
|
UID: owner.ID,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
|
|
@ -238,6 +252,7 @@ func deleteOrg(e *xorm.Session, u *User) error {
|
||||||
&Team{OrgID: u.ID},
|
&Team{OrgID: u.ID},
|
||||||
&OrgUser{OrgID: u.ID},
|
&OrgUser{OrgID: u.ID},
|
||||||
&TeamUser{OrgID: u.ID},
|
&TeamUser{OrgID: u.ID},
|
||||||
|
&TeamUnit{OrgID: u.ID},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("deleteBeans: %v", err)
|
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.
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
@ -10,7 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,15 +28,16 @@ type Team struct {
|
||||||
Members []*User `xorm:"-"`
|
Members []*User `xorm:"-"`
|
||||||
NumRepos int
|
NumRepos int
|
||||||
NumMembers 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) getUnits(e Engine) (err error) {
|
||||||
func (t *Team) GetUnitTypes() []UnitType {
|
if t.Units != nil {
|
||||||
if len(t.UnitTypes) == 0 {
|
return nil
|
||||||
return allRepUnitTypes
|
|
||||||
}
|
}
|
||||||
return t.UnitTypes
|
|
||||||
|
t.Units, err = getUnitsByTeamID(e, t.ID)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasWriteAccess returns true if team has at least write level access mode.
|
// HasWriteAccess returns true if team has at least write level access mode.
|
||||||
|
|
@ -178,6 +179,11 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
|
||||||
if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
|
if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err := removeIssueWatchersByRepoID(e, teamUser.UID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -209,11 +215,12 @@ func (t *Team) RemoveRepository(repoID int64) error {
|
||||||
|
|
||||||
// UnitEnabled returns if the team has the given unit type enabled
|
// UnitEnabled returns if the team has the given unit type enabled
|
||||||
func (t *Team) UnitEnabled(tp UnitType) bool {
|
func (t *Team) UnitEnabled(tp UnitType) bool {
|
||||||
if len(t.UnitTypes) == 0 {
|
if err := t.getUnits(x); err != nil {
|
||||||
return true
|
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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -270,6 +277,17 @@ func NewTeam(t *Team) (err error) {
|
||||||
return err
|
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.
|
// Update organization number of teams.
|
||||||
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
|
|
@ -374,11 +392,34 @@ func DeleteTeam(t *Team) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := t.getMembers(sess); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Delete all accesses.
|
// Delete all accesses.
|
||||||
for _, repo := range t.Repos {
|
for _, repo := range t.Repos {
|
||||||
if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
|
if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove watches from all users and now unaccessible repos
|
||||||
|
for _, user := range t.Members {
|
||||||
|
has, err := hasAccess(sess, user.ID, repo, AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(sess, user.ID, repo.ID, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err = removeIssueWatchersByRepoID(sess, user.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete team-repo
|
// Delete team-repo
|
||||||
|
|
@ -396,6 +437,13 @@ func DeleteTeam(t *Team) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete team-unit.
|
||||||
|
if _, err := sess.
|
||||||
|
Where("team_id=?", t.ID).
|
||||||
|
Delete(new(TeamUnit)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Delete team.
|
// Delete team.
|
||||||
if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
|
if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -518,6 +566,10 @@ func AddTeamMember(team *Team, userID int64) error {
|
||||||
if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
|
if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(sess, userID, repo.ID, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
|
|
@ -558,6 +610,23 @@ func removeTeamMember(e *xorm.Session, team *Team, userID int64) error {
|
||||||
if err := repo.recalculateTeamAccesses(e, 0); err != nil {
|
if err := repo.recalculateTeamAccesses(e, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove watches from now unaccessible
|
||||||
|
has, err := hasAccess(e, userID, repo, AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(e, userID, repo.ID, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err := removeIssueWatchersByRepoID(e, userID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user is a member of any team in the organization.
|
// Check if the user is a member of any team in the organization.
|
||||||
|
|
@ -646,3 +715,47 @@ func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, er
|
||||||
And("team_repo.repo_id = ?", repoID).
|
And("team_repo.repo_id = ?", repoID).
|
||||||
Find(&teams)
|
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.NoError(t, err)
|
||||||
assert.EqualValues(t, expectedCount, count)
|
assert.EqualValues(t, expectedCount, count)
|
||||||
}
|
}
|
||||||
testSuccess(2, 2)
|
testSuccess(2, 3)
|
||||||
testSuccess(4, 1)
|
testSuccess(4, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||||
|
|
@ -503,8 +503,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expectedRepoIDs, repoIDs)
|
assert.Equal(t, expectedRepoIDs, repoIDs)
|
||||||
}
|
}
|
||||||
testSuccess(2, 1, 100, []int64{3, 5})
|
testSuccess(2, 1, 100, []int64{3, 5, 32})
|
||||||
testSuccess(4, 0, 100, []int64{3})
|
testSuccess(4, 0, 100, []int64{3, 32})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
||||||
|
|
@ -522,8 +522,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(t, expectedRepos, repos)
|
assert.Equal(t, expectedRepos, repos)
|
||||||
}
|
}
|
||||||
testSuccess(2, []int64{3, 5})
|
testSuccess(2, []int64{3, 5, 32})
|
||||||
testSuccess(4, []int64{3})
|
testSuccess(4, []int64{3, 32})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -365,22 +365,14 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) (
|
||||||
return err
|
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
|
// unique
|
||||||
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units))
|
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units))
|
||||||
for _, u := range repo.Units {
|
for _, u := range repo.Units {
|
||||||
if _, ok := allTypes[u.Type]; ok {
|
for _, team := range teams {
|
||||||
|
if team.UnitEnabled(u.Type) {
|
||||||
newRepoUnits = append(newRepoUnits, u)
|
newRepoUnits = append(newRepoUnits, u)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1851,6 +1843,9 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||||
if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
|
if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
attachments := make([]*Attachment, 0, 5)
|
attachments := make([]*Attachment, 0, 5)
|
||||||
if err = sess.
|
if err = sess.
|
||||||
|
|
|
||||||
|
|
@ -172,5 +172,14 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(sess, uid, repo.ID, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repository
|
||||||
|
if err := removeIssueWatchersByRepoID(sess, uid, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
|
||||||
count: 14},
|
count: 14},
|
||||||
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
||||||
count: 15},
|
count: 16},
|
||||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||||
count: 19},
|
count: 20},
|
||||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||||
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||||
count: 13},
|
count: 13},
|
||||||
|
|
@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
|
||||||
count: 11},
|
count: 11},
|
||||||
{name: "AllPublic/PublicRepositoriesOfOrganization",
|
{name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
||||||
count: 15},
|
count: 16},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,23 @@ func notifyWatchers(e Engine, act *Action) error {
|
||||||
|
|
||||||
act.ID = 0
|
act.ID = 0
|
||||||
act.UserID = watches[i].UserID
|
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 {
|
if _, err = e.InsertOne(act); err != nil {
|
||||||
return fmt.Errorf("insert new action: %v", err)
|
return fmt.Errorf("insert new action: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
@ -20,6 +21,8 @@ func init() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||||||
|
|
||||||
// Topic represents a topic of repositories
|
// Topic represents a topic of repositories
|
||||||
type Topic struct {
|
type Topic struct {
|
||||||
ID int64
|
ID int64
|
||||||
|
|
@ -51,6 +54,11 @@ func (err ErrTopicNotExist) Error() string {
|
||||||
return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
|
return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateTopic checks topics by length and match pattern rules
|
||||||
|
func ValidateTopic(topic string) bool {
|
||||||
|
return len(topic) <= 35 && topicPattern.MatchString(topic)
|
||||||
|
}
|
||||||
|
|
||||||
// GetTopicByName retrieves topic by name
|
// GetTopicByName retrieves topic by name
|
||||||
func GetTopicByName(name string) (*Topic, error) {
|
func GetTopicByName(name string) (*Topic, error) {
|
||||||
var topic Topic
|
var topic Topic
|
||||||
|
|
@ -182,6 +190,13 @@ func SaveTopics(repoID int64, topicNames ...string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
topicNames = topicNames[:0]
|
||||||
|
if err := sess.Table("topic").Cols("name").
|
||||||
|
Join("INNER", "repo_topic", "topic.id = repo_topic.topic_id").
|
||||||
|
Where("repo_topic.repo_id = ?", repoID).Find(&topicNames); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{
|
if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{
|
||||||
Topics: topicNames,
|
Topics: topicNames,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -55,3 +55,16 @@ func TestAddTopic(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 2, len(topics))
|
assert.EqualValues(t, 2, len(topics))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -546,28 +546,46 @@ func (u *User) GetRepositories(page, pageSize int) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepositoryIDs returns repositories IDs where user owned
|
// GetRepositoryIDs returns repositories IDs where user owned and has unittypes
|
||||||
func (u *User) GetRepositoryIDs() ([]int64, error) {
|
func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) {
|
||||||
var ids []int64
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrgRepositoryIDs returns repositories IDs where user's team owned
|
return ids, sess.Where("owner_id = ?", u.ID).Find(&ids)
|
||||||
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
|
var ids []int64
|
||||||
return ids, x.Table("repository").
|
|
||||||
|
sess := x.Table("repository").
|
||||||
Cols("repository.id").
|
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)
|
GroupBy("repository.id").Find(&ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
|
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
|
||||||
func (u *User) GetAccessRepoIDs() ([]int64, error) {
|
func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) {
|
||||||
ids, err := u.GetRepositoryIDs()
|
ids, err := u.GetRepositoryIDs(units...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ids2, err := u.GetOrgRepositoryIDs()
|
ids2, err := u.GetOrgRepositoryIDs(units...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,3 +159,25 @@ func BenchmarkHashPassword(b *testing.B) {
|
||||||
u.HashPassword(pass)
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -588,7 +588,7 @@ editor.edit_file=Datei bearbeiten
|
||||||
editor.preview_changes=Vorschau der Änderungen
|
editor.preview_changes=Vorschau der Änderungen
|
||||||
editor.cannot_edit_non_text_files=Binärdateien können nicht im Webinterface bearbeitet werden.
|
editor.cannot_edit_non_text_files=Binärdateien können nicht im Webinterface bearbeitet werden.
|
||||||
editor.edit_this_file=Datei bearbeiten
|
editor.edit_this_file=Datei bearbeiten
|
||||||
editor.must_be_on_a_branch=Du musst dich in einer Branch befinden, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen.
|
editor.must_be_on_a_branch=Du musst dich in einem Branch befinden, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen.
|
||||||
editor.fork_before_edit=Du musst dieses Repository forken, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen.
|
editor.fork_before_edit=Du musst dieses Repository forken, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen.
|
||||||
editor.delete_this_file=Datei löschen
|
editor.delete_this_file=Datei löschen
|
||||||
editor.must_have_write_access=Du benötigst Schreibzugriff, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen.
|
editor.must_have_write_access=Du benötigst Schreibzugriff, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen.
|
||||||
|
|
@ -1085,16 +1085,16 @@ settings.protect_whitelist_search_users=Benutzer suchen…
|
||||||
settings.protect_whitelist_teams=Teams, die pushen dürfen:
|
settings.protect_whitelist_teams=Teams, die pushen dürfen:
|
||||||
settings.protect_whitelist_search_teams=Suche nach Teams…
|
settings.protect_whitelist_search_teams=Suche nach Teams…
|
||||||
settings.protect_merge_whitelist_committers=Merge-Whitelist aktivieren
|
settings.protect_merge_whitelist_committers=Merge-Whitelist aktivieren
|
||||||
settings.protect_merge_whitelist_committers_desc=Erlaube Nutzern oder Teams auf der Whitelist Pull-Requests in diese Branch zu mergen.
|
settings.protect_merge_whitelist_committers_desc=Erlaube Nutzern oder Teams auf der Whitelist Pull-Requests in diesen Branch zu mergen.
|
||||||
settings.protect_merge_whitelist_users=Nutzer, die mergen dürfen:
|
settings.protect_merge_whitelist_users=Nutzer, die mergen dürfen:
|
||||||
settings.protect_merge_whitelist_teams=Teams, die mergen dürfen:
|
settings.protect_merge_whitelist_teams=Teams, die mergen dürfen:
|
||||||
settings.add_protected_branch=Schutz aktivieren
|
settings.add_protected_branch=Schutz aktivieren
|
||||||
settings.delete_protected_branch=Schutz deaktivieren
|
settings.delete_protected_branch=Schutz deaktivieren
|
||||||
settings.update_protect_branch_success=Branch-Schutz für den Branch „%s“ wurde geändert.
|
settings.update_protect_branch_success=Branch-Schutz für den Branch „%s“ wurde geändert.
|
||||||
settings.remove_protected_branch_success=Branch-Schutz für den Branch „%s“ wurde deaktiviert.
|
settings.remove_protected_branch_success=Branch-Schutz für den Branch „%s“ wurde deaktiviert.
|
||||||
settings.protected_branch_deletion=Brach-Schutz deaktivieren
|
settings.protected_branch_deletion=Branch-Schutz deaktivieren
|
||||||
settings.protected_branch_deletion_desc=Wenn du den Branch-Schutz deaktivierst, können alle Nutzer mit Schreibrechten auf den Branch pushen. Fortfahren?
|
settings.protected_branch_deletion_desc=Wenn du den Branch-Schutz deaktivierst, können alle Nutzer mit Schreibrechten auf den Branch pushen. Fortfahren?
|
||||||
settings.default_branch_desc=Wähle eine Standardbranch für Pull-Requests und Code-Commits:
|
settings.default_branch_desc=Wähle einen Standardbranch für Pull-Requests und Code-Commits:
|
||||||
settings.choose_branch=Wähle einen Branch …
|
settings.choose_branch=Wähle einen Branch …
|
||||||
settings.no_protected_branch=Es gibt keine geschützten Branches.
|
settings.no_protected_branch=Es gibt keine geschützten Branches.
|
||||||
|
|
||||||
|
|
@ -1238,7 +1238,7 @@ teams.update_settings=Einstellungen aktualisieren
|
||||||
teams.delete_team=Team löschen
|
teams.delete_team=Team löschen
|
||||||
teams.add_team_member=Teammitglied hinzufügen
|
teams.add_team_member=Teammitglied hinzufügen
|
||||||
teams.delete_team_title=Team löschen
|
teams.delete_team_title=Team löschen
|
||||||
teams.delete_team_desc=Das Löschen eines Teams wiederruft den Repository-Zugriff für seine Mitglieder. Fortfahren?
|
teams.delete_team_desc=Das Löschen eines Teams widerruft den Repository-Zugriff für seine Mitglieder. Fortfahren?
|
||||||
teams.delete_team_success=Das Team wurde gelöscht.
|
teams.delete_team_success=Das Team wurde gelöscht.
|
||||||
teams.read_permission_desc=Dieses Team hat <strong>Lesezugriff</strong>: Mitglieder können Team-Repositories einsehen und klonen.
|
teams.read_permission_desc=Dieses Team hat <strong>Lesezugriff</strong>: Mitglieder können Team-Repositories einsehen und klonen.
|
||||||
teams.write_permission_desc=Dieses Team hat <strong>Schreibzugriff</strong>: Mitglieder können Team-Repositories einsehen und darauf pushen.
|
teams.write_permission_desc=Dieses Team hat <strong>Schreibzugriff</strong>: Mitglieder können Team-Repositories einsehen und darauf pushen.
|
||||||
|
|
|
||||||
|
|
@ -1167,6 +1167,8 @@ branch.protected_deletion_failed = Branch '%s' is protected. It cannot be delete
|
||||||
|
|
||||||
topic.manage_topics = Manage Topics
|
topic.manage_topics = Manage Topics
|
||||||
topic.done = Done
|
topic.done = Done
|
||||||
|
topic.count_prompt = You can't select more than 25 topics
|
||||||
|
topic.format_prompt = Topics must start with a letter or number, can include hyphens(-) and must be no more than 35 characters long
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder = Organization Name
|
org_name_holder = Organization Name
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ non_local_account=Нелокальні акаунти не можуть змін
|
||||||
verify=Підтвердити
|
verify=Підтвердити
|
||||||
scratch_code=Одноразовий пароль
|
scratch_code=Одноразовий пароль
|
||||||
use_scratch_code=Використовувати одноразовий пароль
|
use_scratch_code=Використовувати одноразовий пароль
|
||||||
twofa_scratch_used=Ви використовували одноразовий пароль. Ви були перенаправлені на сторінку налаштувань для генерації нового коду або відключення двуфакторной аутентифікації.
|
twofa_scratch_used=Ви використовували одноразовий пароль. Ви були перенаправлені на сторінку налаштувань для генерації нового коду або відключення двуфакторної автентифікації.
|
||||||
twofa_passcode_incorrect=Ваш пароль є невірним. Якщо ви втратили пристрій, використовуйте ваш одноразовий пароль.
|
twofa_passcode_incorrect=Ваш пароль є невірним. Якщо ви втратили пристрій, використовуйте ваш одноразовий пароль.
|
||||||
twofa_scratch_token_incorrect=Невірний одноразовий пароль.
|
twofa_scratch_token_incorrect=Невірний одноразовий пароль.
|
||||||
login_userpass=Увійти
|
login_userpass=Увійти
|
||||||
|
|
@ -399,20 +399,25 @@ generate_token=Згенерувати токен
|
||||||
delete_token=Видалити
|
delete_token=Видалити
|
||||||
access_token_deletion=Видалити токен доступу
|
access_token_deletion=Видалити токен доступу
|
||||||
|
|
||||||
twofa_desc=Двофакторна аутентифікація підвищує безпеку вашого облікового запису.
|
twofa_desc=Двофакторна автентифікація підвищує безпеку вашого облікового запису.
|
||||||
twofa_is_enrolled=Ваш обліковий запис на даний час <strong>використовує</strong> двофакторну автентифікацію.
|
twofa_is_enrolled=Ваш обліковий запис на даний час <strong>використовує</strong> двофакторну автентифікацію.
|
||||||
twofa_disable=Вимкнути двофакторну автентифікацію
|
twofa_disable=Вимкнути двофакторну автентифікацію
|
||||||
|
twofa_scratch_token_regenerate=Перестворити токен одноразового пароля
|
||||||
twofa_enroll=Увімкнути двофакторну автентифікацію
|
twofa_enroll=Увімкнути двофакторну автентифікацію
|
||||||
|
twofa_disable_note=При необхідності можна відключити двофакторну автентифікацію.
|
||||||
|
regenerate_scratch_token_desc=Якщо ви втратили свій токен одноразового пароля або вже використовували його для входу, ви можете скинути його тут.
|
||||||
twofa_disabled=Двофакторна автентифікація вимкнена.
|
twofa_disabled=Двофакторна автентифікація вимкнена.
|
||||||
scan_this_image=Проскануйте це зображення вашим додатком для двуфакторної аутентифікації:
|
scan_this_image=Проскануйте це зображення вашим додатком для двуфакторної автентифікації:
|
||||||
or_enter_secret=Або введіть секрет: %s
|
or_enter_secret=Або введіть секрет: %s
|
||||||
passcode_invalid=Некоректний пароль. Спробуй ще раз.
|
passcode_invalid=Некоректний пароль. Спробуй ще раз.
|
||||||
|
|
||||||
|
u2f_desc=Ключами безпеки є апаратні пристрої, що містять криптографічні ключі. Вони можуть використовуватися для двофакторної автентифікації. Ключ безпеки повинен підтримувати стандарт <a href="https://fidoalliance.org/">FIDO U2F</a>.
|
||||||
u2f_register_key=Додати ключ безпеки
|
u2f_register_key=Додати ключ безпеки
|
||||||
u2f_nickname=Псевдонім
|
u2f_nickname=Псевдонім
|
||||||
u2f_delete_key=Видалити ключ безпеки
|
u2f_delete_key=Видалити ключ безпеки
|
||||||
|
|
||||||
manage_account_links=Керування обліковими записами
|
manage_account_links=Керування обліковими записами
|
||||||
|
manage_account_links_desc=Ці зовнішні акаунти прив'язані до вашого аккаунту Gitea.
|
||||||
remove_account_link=Видалити облікові записи
|
remove_account_link=Видалити облікові записи
|
||||||
|
|
||||||
orgs_none=Ви не є учасником будь-якої організації.
|
orgs_none=Ви не є учасником будь-якої організації.
|
||||||
|
|
@ -428,6 +433,7 @@ owner=Власник
|
||||||
repo_name=Назва репозиторію
|
repo_name=Назва репозиторію
|
||||||
visibility=Видимість
|
visibility=Видимість
|
||||||
visiblity_helper=Зробити репозиторій приватним
|
visiblity_helper=Зробити репозиторій приватним
|
||||||
|
visiblity_fork_helper=(Зміна цього вплине на всі форки.)
|
||||||
clone_helper=Потрібна допомога у клонуванні? Відвідайте <a target="_blank" rel="noopener" href="%s">Допомогу</a>.
|
clone_helper=Потрібна допомога у клонуванні? Відвідайте <a target="_blank" rel="noopener" href="%s">Допомогу</a>.
|
||||||
fork_repo=Форкнути репозиторій
|
fork_repo=Форкнути репозиторій
|
||||||
fork_from=Форк з
|
fork_from=Форк з
|
||||||
|
|
@ -607,6 +613,7 @@ issues.filter_sort.leastcomment=Найменш коментовані
|
||||||
issues.filter_sort.moststars=Найбільш обраних
|
issues.filter_sort.moststars=Найбільш обраних
|
||||||
issues.filter_sort.feweststars=Найменш обраних
|
issues.filter_sort.feweststars=Найменш обраних
|
||||||
issues.filter_sort.mostforks=Найбільше форків
|
issues.filter_sort.mostforks=Найбільше форків
|
||||||
|
issues.filter_sort.fewestforks=Найменше форків
|
||||||
issues.action_open=Відкрити
|
issues.action_open=Відкрити
|
||||||
issues.action_close=Закрити
|
issues.action_close=Закрити
|
||||||
issues.action_label=Мітка
|
issues.action_label=Мітка
|
||||||
|
|
@ -971,6 +978,7 @@ topic.done=Готово
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Назва організації
|
org_name_holder=Назва організації
|
||||||
org_full_name_holder=Повна назва організації
|
org_full_name_holder=Повна назва організації
|
||||||
|
org_name_helper=Назва організації має бути простою та зрозумілою.
|
||||||
create_org=Створити організацію
|
create_org=Створити організацію
|
||||||
repo_updated=Оновлено
|
repo_updated=Оновлено
|
||||||
people=Учасники
|
people=Учасники
|
||||||
|
|
@ -1133,7 +1141,7 @@ repos.forks=Форки
|
||||||
repos.issues=Проблеми
|
repos.issues=Проблеми
|
||||||
repos.size=Розмір
|
repos.size=Розмір
|
||||||
|
|
||||||
auths.auth_manage_panel=Керування джерелом аутентифікації
|
auths.auth_manage_panel=Керування джерелом автентифікації
|
||||||
auths.new=Додати джерело автентифікації
|
auths.new=Додати джерело автентифікації
|
||||||
auths.name=Ім'я
|
auths.name=Ім'я
|
||||||
auths.type=Тип
|
auths.type=Тип
|
||||||
|
|
@ -1170,8 +1178,8 @@ auths.oauth2_profileURL=URL профілю
|
||||||
auths.oauth2_emailURL=URL електронної пошти
|
auths.oauth2_emailURL=URL електронної пошти
|
||||||
auths.enable_auto_register=Увімкнути автоматичну реєстрацію
|
auths.enable_auto_register=Увімкнути автоматичну реєстрацію
|
||||||
auths.tips=Поради
|
auths.tips=Поради
|
||||||
auths.tips.oauth2.general=OAuth2 аутентифікація
|
auths.tips.oauth2.general=OAuth2 автентифікація
|
||||||
auths.tips.oauth2.general.tip=При додаванні нового OAuth2 провайдера, URL адреса переадресації по завершенні аутентифікації повинена виглядати так:<host>/user/oauth2/<Authentication Name>/callback
|
auths.tips.oauth2.general.tip=При додаванні нового OAuth2 провайдера, URL адреса переадресації по завершенні автентифікації повинена виглядати так:<host>/user/oauth2/<Authentication Name>/callback
|
||||||
auths.tip.oauth2_provider=Постачальник OAuth2
|
auths.tip.oauth2_provider=Постачальник OAuth2
|
||||||
auths.tip.dropbox=Додайте новий додаток на https://www.dropbox.com/developers/apps
|
auths.tip.dropbox=Додайте новий додаток на https://www.dropbox.com/developers/apps
|
||||||
auths.tip.facebook=Створіть новий додаток на https://developers.facebook.com/apps і додайте модуль "Facebook Login
|
auths.tip.facebook=Створіть новий додаток на https://developers.facebook.com/apps і додайте модуль "Facebook Login
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,7 @@ send_reset_mail=单击此处(重新)发送您的密码重置邮件
|
||||||
reset_password=重置密码
|
reset_password=重置密码
|
||||||
invalid_code=此确认密钥无效或已过期。
|
invalid_code=此确认密钥无效或已过期。
|
||||||
reset_password_helper=单击此处重置密码
|
reset_password_helper=单击此处重置密码
|
||||||
|
password_too_short=密码长度不能少于 %d 位。
|
||||||
non_local_account=非本地帐户不能通过 Gitea 的 web 界面更改密码。
|
non_local_account=非本地帐户不能通过 Gitea 的 web 界面更改密码。
|
||||||
verify=验证
|
verify=验证
|
||||||
scratch_code=验证口令
|
scratch_code=验证口令
|
||||||
|
|
|
||||||
|
|
@ -2336,8 +2336,10 @@ function initTopicbar() {
|
||||||
}).done(function() {
|
}).done(function() {
|
||||||
editDiv.hide();
|
editDiv.hide();
|
||||||
viewDiv.show();
|
viewDiv.show();
|
||||||
|
}).fail(function(xhr) {
|
||||||
|
alert(xhr.responseJSON.message)
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
|
|
||||||
$('#topic_edit .dropdown').dropdown({
|
$('#topic_edit .dropdown').dropdown({
|
||||||
allowAdditions: true,
|
allowAdditions: true,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
@ -165,7 +166,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
|
||||||
// "$ref": "#/responses/Issue"
|
// "$ref": "#/responses/Issue"
|
||||||
|
|
||||||
var deadlineUnix util.TimeStamp
|
var deadlineUnix util.TimeStamp
|
||||||
if form.Deadline != nil {
|
if form.Deadline != nil && ctx.Repo.IsWriter() {
|
||||||
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
|
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,8 +179,11 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
|
||||||
DeadlineUnix: deadlineUnix,
|
DeadlineUnix: deadlineUnix,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all assignee IDs
|
var assigneeIDs = make([]int64, 0)
|
||||||
assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
|
var err error
|
||||||
|
if ctx.Repo.IsWriter() {
|
||||||
|
issue.MilestoneID = form.Milestone
|
||||||
|
assigneeIDs, err = models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if models.IsErrUserNotExist(err) {
|
if models.IsErrUserNotExist(err) {
|
||||||
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
|
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
|
||||||
|
|
@ -188,6 +192,10 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// setting labels is not allowed if user is not a writer
|
||||||
|
form.Labels = make([]int64, 0)
|
||||||
|
}
|
||||||
|
|
||||||
if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, assigneeIDs, nil); err != nil {
|
if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, assigneeIDs, nil); err != nil {
|
||||||
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
|
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,14 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
|
||||||
Authorize: models.ParseAccessMode(form.Permission),
|
Authorize: models.ParseAccessMode(form.Permission),
|
||||||
}
|
}
|
||||||
if t.Authorize < models.AccessModeAdmin {
|
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
|
ctx.Data["Team"] = t
|
||||||
|
|
@ -264,9 +271,17 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
|
||||||
}
|
}
|
||||||
t.Description = form.Description
|
t.Description = form.Description
|
||||||
if t.Authorize < models.AccessModeAdmin {
|
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 {
|
} else {
|
||||||
t.UnitTypes = nil
|
models.UpdateTeamUnits(t, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TopicPost response for creating repository
|
// TopicsPost response for creating repository
|
||||||
func TopicPost(ctx *context.Context) {
|
func TopicsPost(ctx *context.Context) {
|
||||||
if ctx.User == nil {
|
if ctx.User == nil {
|
||||||
ctx.JSON(403, map[string]interface{}{
|
ctx.JSON(403, map[string]interface{}{
|
||||||
"message": "Only owners could change the topics.",
|
"message": "Only owners could change the topics.",
|
||||||
|
|
@ -27,6 +27,37 @@ func TopicPost(ctx *context.Context) {
|
||||||
topics = strings.Split(topicsStr, ",")
|
topics = strings.Split(topicsStr, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalidTopics := make([]string, 0)
|
||||||
|
i := 0
|
||||||
|
for _, topic := range topics {
|
||||||
|
topic = strings.TrimSpace(strings.ToLower(topic))
|
||||||
|
// ignore empty string
|
||||||
|
if len(topic) > 0 {
|
||||||
|
topics[i] = topic
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if !models.ValidateTopic(topic) {
|
||||||
|
invalidTopics = append(invalidTopics, topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
topics = topics[:i]
|
||||||
|
|
||||||
|
if len(topics) > 25 {
|
||||||
|
ctx.JSON(422, map[string]interface{}{
|
||||||
|
"invalidTopics": topics[:0],
|
||||||
|
"message": ctx.Tr("repo.topic.count_prompt"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(invalidTopics) > 0 {
|
||||||
|
ctx.JSON(422, map[string]interface{}{
|
||||||
|
"invalidTopics": invalidTopics,
|
||||||
|
"message": ctx.Tr("repo.topic.format_prompt"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := models.SaveTopics(ctx.Repo.Repository.ID, topics...)
|
err := models.SaveTopics(ctx.Repo.Repository.ID, topics...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(2, "SaveTopics failed: %v", err)
|
log.Error(2, "SaveTopics failed: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ func GogsHooksNewPost(ctx *context.Context, form auth.NewGogshookForm) {
|
||||||
Secret: form.Secret,
|
Secret: form.Secret,
|
||||||
HookEvent: ParseHookEvent(form.WebhookForm),
|
HookEvent: ParseHookEvent(form.WebhookForm),
|
||||||
IsActive: form.Active,
|
IsActive: form.Active,
|
||||||
HookTaskType: models.GITEA,
|
HookTaskType: models.GOGS,
|
||||||
OrgID: orCtx.OrgID,
|
OrgID: orCtx.OrgID,
|
||||||
}
|
}
|
||||||
if err := w.UpdateEvent(); err != nil {
|
if err := w.UpdateEvent(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -486,7 +486,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Get("/:id", repo.WebHooksEdit)
|
m.Get("/:id", repo.WebHooksEdit)
|
||||||
m.Post("/:id/test", repo.TestWebhook)
|
m.Post("/:id/test", repo.TestWebhook)
|
||||||
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||||
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
|
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
|
||||||
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||||
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
|
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
|
||||||
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
|
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
|
||||||
|
|
@ -626,7 +626,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
}, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits(), context.CheckUnit(models.UnitTypeReleases))
|
}, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits(), context.CheckUnit(models.UnitTypeReleases))
|
||||||
|
|
||||||
m.Group("/:username/:reponame", func() {
|
m.Group("/:username/:reponame", func() {
|
||||||
m.Post("/topics", repo.TopicPost)
|
m.Post("/topics", repo.TopicsPost)
|
||||||
}, context.RepoAssignment(), reqRepoAdmin)
|
}, context.RepoAssignment(), reqRepoAdmin)
|
||||||
|
|
||||||
m.Group("/:username/:reponame", func() {
|
m.Group("/:username/:reponame", func() {
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,11 @@ func Issues(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
userRepoIDs, err = ctxUser.GetAccessRepoIDs()
|
unitType := models.UnitTypeIssues
|
||||||
|
if isPullList {
|
||||||
|
unitType = models.UnitTypePullRequests
|
||||||
|
}
|
||||||
|
userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ctxUser.GetAccessRepoIDs", err)
|
ctx.ServerError("ctxUser.GetAccessRepoIDs", err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ func TestIssues(t *testing.T) {
|
||||||
Issues(ctx)
|
Issues(ctx)
|
||||||
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
|
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.EqualValues(t, true, ctx.Data["IsShowClosed"])
|
||||||
assert.Len(t, ctx.Data["Issues"], 1)
|
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}}
|
{{range $t, $unit := $.Units}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui toggle checkbox">
|
<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>
|
<label>{{$.i18n.Tr $unit.NameKey}}</label>
|
||||||
<span class="help">{{$.i18n.Tr $unit.DescKey}}</span>
|
<span class="help">{{$.i18n.Tr $unit.DescKey}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user