This commit is contained in:
Konrad 2017-12-29 14:27:23 +01:00
commit ed4a47dcc8
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
38 changed files with 762 additions and 274 deletions

View File

@ -183,15 +183,15 @@ test-pgsql: integrations.test generate-ini
.PHONY: bench-sqlite
bench-sqlite: integrations.sqlite.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.bench .
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-mysql
bench-mysql: integrations.test generate-ini
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.test -test.bench .
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-pgsql
bench-pgsql: integrations.test generate-ini
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test -test.bench .
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: integration-test-coverage

View File

@ -51,6 +51,26 @@ and it takes care of all the other things for you`,
},
}
func runHTTPRedirector() {
source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect)
dest := strings.TrimSuffix(setting.AppURL, "/")
log.Info("Redirecting: %s to %s", source, dest)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
target := dest + r.URL.Path
if len(r.URL.RawQuery) > 0 {
target += "?" + r.URL.RawQuery
}
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
})
var err = runHTTP(source, context2.ClearHandler(handler))
if err != nil {
log.Fatal(4, "Failed to start port redirection: %v", err)
}
}
func runWeb(ctx *cli.Context) error {
if ctx.IsSet("config") {
setting.CustomConf = ctx.String("config")
@ -124,6 +144,9 @@ func runWeb(ctx *cli.Context) error {
case setting.HTTP:
err = runHTTP(listenAddr, context2.ClearHandler(m))
case setting.HTTPS:
if setting.RedirectOtherPort {
go runHTTPRedirector()
}
err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
case setting.FCGI:
listener, err := net.Listen("tcp", listenAddr)

View File

@ -16,6 +16,8 @@ SCRIPT_TYPE = bash
ANSI_CHARSET =
; Force every new repository to be private
FORCE_PRIVATE = false
; Default private when create a new repository, could be: last, private, public. Default is last which means last user repo visiblity.
DEFAULT_PRIVATE = last
; Global maximum creation limit of repository per user, -1 means no limit
MAX_CREATION_LIMIT = -1
; Mirror sync queue length, increase if mirror syncing starts hanging
@ -107,6 +109,12 @@ ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
; Listen address. Either a IPv4/IPv6 address or the path to a unix socket.
HTTP_ADDR = 0.0.0.0
HTTP_PORT = 3000
; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
; will be started on PORT_TO_REDIRECT and redirect request to the main
; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
; PORT_TO_REDIRECT.
REDIRECT_OTHER_PORT = false
PORT_TO_REDIRECT = 80
; Permission for unix socket
UNIX_SOCKET_PERMISSION = 666
; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service.

View File

@ -39,6 +39,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `SCRIPT_TYPE`: The script type your server supports, usually this is `bash`, but some customers report that they only have `sh`.
- `ANSI_CHARSET`: The default charset for an unrecognized charset.
- `FORCE_PRIVATE`: Force every new repository to be private.
- `DEFAULT_PRIVATE`: Default private when create a new repository, could be: `last`, `private` and `public`. Default is last which means last user repo visiblity.
- `MAX_CREATION_LIMIT`: Global maximum creation limit of repositories per user, `-1` means no limit.
- `PULL_REQUEST_QUEUE_LENGTH`:exclamation:: Length of pull request patch test queue, make it as large as possible.
- `MIRROR_QUEUE_LENGTH`: Patch test queue length, increase if pull request patch testing starts hanging. Defaults to 1000.
@ -86,6 +87,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `LFS_START_SERVER`: Enables git-lfs support. `true` or `false`, default is `false`.
- `LFS_CONTENT_PATH`: Where your lfs files put on, default is `data/lfs`.
- `LFS_JWT_SECRET`: LFS authentication secret, changed this to yourself.
- `REDIRECT_OTHER_PORT`: If true and `PROTOCOL` is https, redirects http requests on another port to `ROOT_URL`, default is `false`.
- `PORT_TO_REDIRECT`: Port used when `REDIRECT_OTHER_PORT` is true, default is `80`.
## Database (`database`)

View File

@ -29,6 +29,7 @@ menu:
- `SCRIPT_TYPE`: 服务器支持的Shell类型通常是 `bash`,但有些服务器也有可能是 `sh`
- `ANSI_CHARSET`: 默认字符编码。
- `FORCE_PRIVATE`: 强制所有git工程必须私有。
- `DEFAULT_PRIVATE`: 默认创建的git工程为私有。 可以是`last`, `private``public`。默认值是 `last`表示用户最后创建的Repo的选择。
- `MAX_CREATION_LIMIT`: 全局最大每个用户创建的git工程数目 `-1` 表示没限制。
- `PULL_REQUEST_QUEUE_LENGTH`: 小心:合并请求测试队列的长度,尽量放大。

View File

@ -0,0 +1,113 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"math/rand"
"net/http"
"testing"
"code.gitea.io/gitea/models"
api "code.gitea.io/sdk/gitea"
)
func BenchmarkRepo(b *testing.B) {
samples := []struct {
url string
name string
skipShort bool
}{
{url: "https://github.com/go-gitea/gitea.git", name: "gitea"},
{url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"},
{url: "https://github.com/moby/moby.git", name: "moby", skipShort: true},
{url: "https://github.com/golang/go.git", name: "go", skipShort: true},
{url: "https://github.com/torvalds/linux.git", name: "linux", skipShort: true},
}
prepareTestEnv(b)
session := loginUser(b, "user2")
b.ResetTimer()
for _, s := range samples {
b.Run(s.name, func(b *testing.B) {
if testing.Short() && s.skipShort {
b.Skip("skipping test in short mode.")
}
b.Run("Migrate", func(b *testing.B) {
for i := 0; i < b.N; i++ {
testRepoMigrate(b, session, s.url, s.name)
}
})
b.Run("Access", func(b *testing.B) {
var branches []*api.Branch
b.Run("APIBranchList", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req := NewRequestf(b, "GET", "/api/v1/repos/%s/%s/branches", "user2", s.name)
resp := session.MakeRequest(b, req, http.StatusOK)
b.StopTimer()
if len(branches) == 0 {
DecodeJSON(b, resp, &branches) //Store for next phase
}
b.StartTimer()
}
})
branchCount := len(branches)
b.Run("WebViewCommit", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req := NewRequestf(b, "GET", "/%s/%s/commit/%s", "user2", s.name, branches[i%branchCount].Commit.ID)
session.MakeRequest(b, req, http.StatusOK)
}
})
})
})
}
}
//StringWithCharset random string (from https://www.calhoun.io/creating-random-strings-in-go/)
func StringWithCharset(length int, charset string) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
func BenchmarkRepoBranchCommit(b *testing.B) {
samples := []int64{1, 3, 15, 16}
prepareTestEnv(b)
b.ResetTimer()
for _, repoID := range samples {
b.StopTimer()
repo := models.AssertExistsAndLoadBean(b, &models.Repository{ID: repoID}).(*models.Repository)
b.StartTimer()
b.Run(repo.Name, func(b *testing.B) {
owner := models.AssertExistsAndLoadBean(b, &models.User{ID: repo.OwnerID}).(*models.User)
session := loginUser(b, owner.LoginName)
b.ResetTimer()
b.Run("Create", func(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
branchName := StringWithCharset(5+rand.Intn(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b.StartTimer()
testCreateBranch(b, session, owner.LoginName, repo.Name, "branch/master", branchName, http.StatusFound)
}
})
b.Run("Access", func(b *testing.B) {
var branches []*api.Branch
req := NewRequestf(b, "GET", "/api/v1/%s/branches", repo.FullName())
resp := session.MakeRequest(b, req, http.StatusOK)
DecodeJSON(b, resp, &branches)
branchCount := len(branches)
b.ResetTimer() //We measure from here
for i := 0; i < b.N; i++ {
req := NewRequestf(b, "GET", "/%s/%s/commits/%s", owner.Name, repo.Name, branches[i%branchCount])
session.MakeRequest(b, req, http.StatusOK)
}
})
})
}
}
//TODO list commits /repos/{owner}/{repo}/commits

View File

@ -16,7 +16,7 @@ import (
"github.com/stretchr/testify/assert"
)
func testCreateBranch(t *testing.T, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string {
func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string {
var csrf string
if expectedStatus == http.StatusNotFound {
csrf = GetCSRF(t, session, path.Join(user, repo, "src/branch/master"))

View File

@ -40,29 +40,3 @@ func TestRepoMigrate(t *testing.T) {
session := loginUser(t, "user2")
testRepoMigrate(t, session, "https://github.com/go-gitea/git.git", "git")
}
func BenchmarkRepoMigrate(b *testing.B) {
samples := []struct {
url string
name string
}{
{url: "https://github.com/go-gitea/gitea.git", name: "gitea"},
{url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"},
{url: "https://github.com/moby/moby.git", name: "moby"},
{url: "https://github.com/golang/go.git", name: "go"},
{url: "https://github.com/torvalds/linux.git", name: "linux"},
}
prepareTestEnv(b)
session := loginUser(b, "user2")
b.ResetTimer()
for _, s := range samples {
b.Run(s.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
testRepoMigrate(b, session, s.url, s.name)
}
})
}
}

View File

@ -135,19 +135,28 @@ func DeleteAttachment(a *Attachment, remove bool) error {
// DeleteAttachments deletes the given attachments and optionally the associated files.
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
for i, a := range attachments {
if remove {
if len(attachments) == 0 {
return 0, nil
}
var ids = make([]int64, 0, len(attachments))
for _, a := range attachments {
ids = append(ids, a.ID)
}
cnt, err := x.In("id", ids).NoAutoCondition().Delete(attachments[0])
if err != nil {
return 0, err
}
if remove {
for i, a := range attachments {
if err := os.Remove(a.LocalPath()); err != nil {
return i, err
}
}
if _, err := x.Delete(a); err != nil {
return i, err
}
}
return len(attachments), nil
return int(cnt), nil
}
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.

View File

@ -47,6 +47,8 @@
content: content for the fourth issue
is_closed: true
is_pull: false
created_unix: 946684830
updated_unix: 978307200
-
id: 5
@ -57,6 +59,9 @@
content: content for the fifth issue
is_closed: true
is_pull: false
created_unix: 946684840
updated_unix: 978307200
-
id: 6
repo_id: 3
@ -68,5 +73,5 @@
is_closed: false
is_pull: false
num_comments: 0
created_unix: 946684800
created_unix: 946684850
updated_unix: 978307200

View File

@ -10,14 +10,15 @@ import (
"sort"
"strings"
api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
)
// Issue represents an issue or pull request of repository.
@ -1022,12 +1023,11 @@ func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) {
// IssuesOptions represents options of an issue.
type IssuesOptions struct {
RepoID int64
RepoIDs []int64 // include all repos if empty
AssigneeID int64
PosterID int64
MentionedID int64
MilestoneID int64
RepoIDs []int64
Page int
PageSize int
IsClosed util.OptionalBool
@ -1073,9 +1073,7 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
sess.In("issue.id", opts.IssueIDs)
}
if opts.RepoID > 0 {
sess.And("issue.repo_id=?", opts.RepoID)
} else if len(opts.RepoIDs) > 0 {
if len(opts.RepoIDs) > 0 {
// In case repository IDs are provided but actually no repository has issue.
sess.In("issue.repo_id", opts.RepoIDs)
}
@ -1339,58 +1337,92 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
return stats, err
}
// UserIssueStatsOptions contains parameters accepted by GetUserIssueStats.
type UserIssueStatsOptions struct {
UserID int64
RepoID int64
UserRepoIDs []int64
FilterMode int
IsPull bool
IsClosed bool
}
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPull bool) *IssueStats {
func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
var err error
stats := &IssueStats{}
countSession := func(isClosed, isPull bool, repoID int64, repoIDs []int64) *xorm.Session {
sess := x.
Where("issue.is_closed = ?", isClosed).
And("issue.is_pull = ?", isPull)
if repoID > 0 {
sess.And("repo_id = ?", repoID)
} else if len(repoIDs) > 0 {
sess.In("repo_id", repoIDs)
}
return sess
cond := builder.NewCond()
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull})
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID})
}
stats.AssignCount, _ = countSession(false, isPull, repoID, nil).
And("assignee_id = ?", uid).
Count(new(Issue))
stats.CreateCount, _ = countSession(false, isPull, repoID, nil).
And("poster_id = ?", uid).
Count(new(Issue))
stats.YourRepositoriesCount, _ = countSession(false, isPull, repoID, repoIDs).
Count(new(Issue))
switch filterMode {
switch opts.FilterMode {
case FilterModeAll:
stats.OpenCount, _ = countSession(false, isPull, repoID, repoIDs).
stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, repoIDs).
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
return nil, err
}
case FilterModeAssign:
stats.OpenCount, _ = countSession(false, isPull, repoID, nil).
And("assignee_id = ?", uid).
stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false).
And("assignee_id = ?", opts.UserID).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, nil).
And("assignee_id = ?", uid).
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true).
And("assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
case FilterModeCreate:
stats.OpenCount, _ = countSession(false, isPull, repoID, nil).
And("poster_id = ?", uid).
stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false).
And("poster_id = ?", opts.UserID).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, nil).
And("poster_id = ?", uid).
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true).
And("poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
}
return stats
cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed})
stats.AssignCount, err = x.Where(cond).
And("assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.CreateCount, err = x.Where(cond).
And("poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.YourRepositoriesCount, err = x.Where(cond).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
return nil, err
}
return stats, nil
}
// GetRepoIssueStats returns number of open and closed repository issues by given filter mode.

View File

@ -42,7 +42,7 @@ func populateIssueIndexer() error {
}
for _, repo := range repos {
issues, err := Issues(&IssuesOptions{
RepoID: repo.ID,
RepoIDs: []int64{repo.ID},
IsClosed: util.OptionalBoolNone,
IsPull: util.OptionalBoolNone,
})

View File

@ -168,3 +168,114 @@ func TestUpdateIssueCols(t *testing.T) {
assert.EqualValues(t, prevContent, updatedIssue.Content)
AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
}
func TestIssues(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
for _, test := range []struct {
Opts IssuesOptions
ExpectedIssueIDs []int64
}{
{
IssuesOptions{
AssigneeID: 1,
SortType: "oldest",
},
[]int64{1, 6},
},
{
IssuesOptions{
RepoIDs: []int64{1, 3},
SortType: "oldest",
Page: 1,
PageSize: 4,
},
[]int64{1, 2, 3, 5},
},
{
IssuesOptions{
Labels: "1,2",
Page: 1,
PageSize: 4,
},
[]int64{5, 2, 1},
},
} {
issues, err := Issues(&test.Opts)
assert.NoError(t, err)
if assert.Len(t, issues, len(test.ExpectedIssueIDs)) {
for i, issue := range issues {
assert.EqualValues(t, test.ExpectedIssueIDs[i], issue.ID)
}
}
}
}
func TestGetUserIssueStats(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
for _, test := range []struct {
Opts UserIssueStatsOptions
ExpectedIssueStats IssueStats
}{
{
UserIssueStatsOptions{
UserID: 1,
RepoID: 1,
FilterMode: FilterModeAll,
},
IssueStats{
YourRepositoriesCount: 0,
AssignCount: 1,
CreateCount: 1,
OpenCount: 0,
ClosedCount: 0,
},
},
{
UserIssueStatsOptions{
UserID: 1,
FilterMode: FilterModeAssign,
},
IssueStats{
YourRepositoriesCount: 0,
AssignCount: 2,
CreateCount: 2,
OpenCount: 2,
ClosedCount: 0,
},
},
{
UserIssueStatsOptions{
UserID: 1,
FilterMode: FilterModeCreate,
},
IssueStats{
YourRepositoriesCount: 0,
AssignCount: 2,
CreateCount: 2,
OpenCount: 2,
ClosedCount: 0,
},
},
{
UserIssueStatsOptions{
UserID: 2,
UserRepoIDs: []int64{1, 2},
FilterMode: FilterModeAll,
IsClosed: true,
},
IssueStats{
YourRepositoriesCount: 2,
AssignCount: 0,
CreateCount: 2,
OpenCount: 1,
ClosedCount: 2,
},
},
} {
stats, err := GetUserIssueStats(test.Opts)
if !assert.NoError(t, err) {
continue
}
assert.Equal(t, test.ExpectedIssueStats, *stats)
}
}

View File

@ -21,13 +21,13 @@ var (
)
// IsOwnedBy returns true if given user is in the owner team.
func (org *User) IsOwnedBy(uid int64) bool {
func (org *User) IsOwnedBy(uid int64) (bool, error) {
return IsOrganizationOwner(org.ID, uid)
}
// IsOrgMember returns true if given user is member of organization.
func (org *User) IsOrgMember(uid int64) bool {
return org.IsOrganization() && IsOrganizationMember(org.ID, uid)
func (org *User) IsOrgMember(uid int64) (bool, error) {
return IsOrganizationMember(org.ID, uid)
}
func (org *User) getTeam(e Engine, name string) (*Team, error) {
@ -285,32 +285,32 @@ type OrgUser struct {
}
// IsOrganizationOwner returns true if given user is in the owner team.
func IsOrganizationOwner(orgID, uid int64) bool {
has, _ := x.
func IsOrganizationOwner(orgID, uid int64) (bool, error) {
return x.
Where("is_owner=?", true).
And("uid=?", uid).
And("org_id=?", orgID).
Get(new(OrgUser))
return has
Table("org_user").
Exist()
}
// IsOrganizationMember returns true if given user is member of organization.
func IsOrganizationMember(orgID, uid int64) bool {
has, _ := x.
func IsOrganizationMember(orgID, uid int64) (bool, error) {
return x.
Where("uid=?", uid).
And("org_id=?", orgID).
Get(new(OrgUser))
return has
Table("org_user").
Exist()
}
// IsPublicMembership returns true if given user public his/her membership.
func IsPublicMembership(orgID, uid int64) bool {
has, _ := x.
func IsPublicMembership(orgID, uid int64) (bool, error) {
return x.
Where("uid=?", uid).
And("org_id=?", orgID).
And("is_public=?", true).
Get(new(OrgUser))
return has
Table("org_user").
Exist()
}
func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) {
@ -401,8 +401,9 @@ func ChangeOrgUserStatus(orgID, uid int64, public bool) error {
// AddOrgUser adds new user to given organization.
func AddOrgUser(orgID, uid int64) error {
if IsOrganizationMember(orgID, uid) {
return nil
isAlreadyMember, err := IsOrganizationMember(orgID, uid)
if err != nil || isAlreadyMember {
return err
}
sess := x.NewSession()
@ -447,7 +448,9 @@ func RemoveOrgUser(orgID, userID int64) error {
}
// Check if the user to delete is the last member in owner team.
if IsOrganizationOwner(orgID, userID) {
if isOwner, err := IsOrganizationOwner(orgID, userID); err != nil {
return err
} else if isOwner {
t, err := org.GetOwnerTeam()
if err != nil {
return err

View File

@ -8,6 +8,8 @@ import (
"errors"
"fmt"
"strings"
"code.gitea.io/gitea/modules/log"
)
const ownerTeamName = "Owners"
@ -47,7 +49,12 @@ func (t *Team) IsOwnerTeam() bool {
// IsMember returns true if given user is a member of team.
func (t *Team) IsMember(userID int64) bool {
return IsTeamMember(t.OrgID, t.ID, userID)
isMember, err := IsTeamMember(t.OrgID, t.ID, userID)
if err != nil {
log.Error(4, "IsMember: %v", err)
return false
}
return isMember
}
func (t *Team) getRepositories(e Engine) error {
@ -413,17 +420,17 @@ type TeamUser struct {
UID int64 `xorm:"UNIQUE(s)"`
}
func isTeamMember(e Engine, orgID, teamID, userID int64) bool {
has, _ := e.
func isTeamMember(e Engine, orgID, teamID, userID int64) (bool, error) {
return e.
Where("org_id=?", orgID).
And("team_id=?", teamID).
And("uid=?", userID).
Get(new(TeamUser))
return has
Table("team_user").
Exist()
}
// IsTeamMember returns true if given user is a member of team.
func IsTeamMember(orgID, teamID, userID int64) bool {
func IsTeamMember(orgID, teamID, userID int64) (bool, error) {
return isTeamMember(x, orgID, teamID, userID)
}
@ -471,8 +478,9 @@ func GetUserTeams(orgID, userID int64) ([]*Team, error) {
// AddTeamMember adds new membership of given team to given organization,
// the user will have membership to given organization automatically when needed.
func AddTeamMember(team *Team, userID int64) error {
if IsTeamMember(team.OrgID, team.ID, userID) {
return nil
isAlreadyMember, err := IsTeamMember(team.OrgID, team.ID, userID)
if err != nil || isAlreadyMember {
return err
}
if err := AddOrgUser(team.OrgID, userID); err != nil {
@ -529,8 +537,9 @@ func AddTeamMember(team *Team, userID int64) error {
}
func removeTeamMember(e Engine, team *Team, userID int64) error {
if !isTeamMember(e, team.OrgID, team.ID, userID) {
return nil
isMember, err := isTeamMember(e, team.OrgID, team.ID, userID)
if err != nil || !isMember {
return err
}
// Check if the user to delete is the last member in owner team.
@ -566,7 +575,7 @@ func removeTeamMember(e Engine, team *Team, userID int64) error {
// This must exist.
ou := new(OrgUser)
_, err := e.
_, err = e.
Where("uid = ?", userID).
And("org_id = ?", team.OrgID).
Get(ou)

View File

@ -250,16 +250,21 @@ func TestDeleteTeam(t *testing.T) {
func TestIsTeamMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(orgID, teamID, userID int64, expected bool) {
isMember, err := IsTeamMember(orgID, teamID, userID)
assert.NoError(t, err)
assert.Equal(t, expected, isMember)
}
assert.True(t, IsTeamMember(3, 1, 2))
assert.False(t, IsTeamMember(3, 1, 4))
assert.False(t, IsTeamMember(3, 1, NonexistentID))
test(3, 1, 2, true)
test(3, 1, 4, false)
test(3, 1, NonexistentID, false)
assert.True(t, IsTeamMember(3, 2, 2))
assert.True(t, IsTeamMember(3, 2, 4))
test(3, 2, 2, true)
test(3, 2, 4, true)
assert.False(t, IsTeamMember(3, NonexistentID, NonexistentID))
assert.False(t, IsTeamMember(NonexistentID, NonexistentID, NonexistentID))
test(3, NonexistentID, NonexistentID, false)
test(NonexistentID, NonexistentID, NonexistentID, false)
}
func TestGetTeamMembers(t *testing.T) {

View File

@ -12,28 +12,44 @@ import (
func TestUser_IsOwnedBy(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.True(t, org.IsOwnedBy(2))
assert.False(t, org.IsOwnedBy(1))
assert.False(t, org.IsOwnedBy(3))
assert.False(t, org.IsOwnedBy(4))
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.False(t, nonOrg.IsOwnedBy(2))
assert.False(t, nonOrg.IsOwnedBy(3))
for _, testCase := range []struct {
OrgID int64
UserID int64
ExpectedOwner bool
}{
{3, 2, true},
{3, 1, false},
{3, 3, false},
{3, 4, false},
{2, 2, false}, // user2 is not an organization
{2, 3, false},
} {
org := AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User)
isOwner, err := org.IsOwnedBy(testCase.UserID)
assert.NoError(t, err)
assert.Equal(t, testCase.ExpectedOwner, isOwner)
}
}
func TestUser_IsOrgMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.True(t, org.IsOrgMember(2))
assert.True(t, org.IsOrgMember(4))
assert.False(t, org.IsOrgMember(1))
assert.False(t, org.IsOrgMember(3))
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.False(t, nonOrg.IsOrgMember(2))
assert.False(t, nonOrg.IsOrgMember(3))
for _, testCase := range []struct {
OrgID int64
UserID int64
ExpectedMember bool
}{
{3, 2, true},
{3, 4, true},
{3, 1, false},
{3, 3, false},
{2, 2, false}, // user2 is not an organization
{2, 3, false},
} {
org := AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User)
isMember, err := org.IsOrgMember(testCase.UserID)
assert.NoError(t, err)
assert.Equal(t, testCase.ExpectedMember, isMember)
}
}
func TestUser_GetTeam(t *testing.T) {
@ -257,31 +273,46 @@ func TestDeleteOrganization(t *testing.T) {
func TestIsOrganizationOwner(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsOrganizationOwner(3, 2))
assert.False(t, IsOrganizationOwner(3, 3))
assert.True(t, IsOrganizationOwner(6, 5))
assert.False(t, IsOrganizationOwner(6, 4))
assert.False(t, IsOrganizationOwner(NonexistentID, NonexistentID))
test := func(orgID, userID int64, expected bool) {
isOwner, err := IsOrganizationOwner(orgID, userID)
assert.NoError(t, err)
assert.EqualValues(t, expected, isOwner)
}
test(3, 2, true)
test(3, 3, false)
test(6, 5, true)
test(6, 4, false)
test(NonexistentID, NonexistentID, false)
}
func TestIsOrganizationMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsOrganizationMember(3, 2))
assert.False(t, IsOrganizationMember(3, 3))
assert.True(t, IsOrganizationMember(3, 4))
assert.True(t, IsOrganizationMember(6, 5))
assert.False(t, IsOrganizationMember(6, 4))
assert.False(t, IsOrganizationMember(NonexistentID, NonexistentID))
test := func(orgID, userID int64, expected bool) {
isMember, err := IsOrganizationMember(orgID, userID)
assert.NoError(t, err)
assert.EqualValues(t, expected, isMember)
}
test(3, 2, true)
test(3, 3, false)
test(3, 4, true)
test(6, 5, true)
test(6, 4, false)
test(NonexistentID, NonexistentID, false)
}
func TestIsPublicMembership(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsPublicMembership(3, 2))
assert.False(t, IsPublicMembership(3, 3))
assert.False(t, IsPublicMembership(3, 4))
assert.True(t, IsPublicMembership(6, 5))
assert.False(t, IsPublicMembership(6, 4))
assert.False(t, IsPublicMembership(NonexistentID, NonexistentID))
test := func(orgID, userID int64, expected bool) {
isMember, err := IsPublicMembership(orgID, userID)
assert.NoError(t, err)
assert.EqualValues(t, expected, isMember)
}
test(3, 2, true)
test(3, 3, false)
test(3, 4, false)
test(6, 5, true)
test(6, 4, false)
test(NonexistentID, NonexistentID, false)
}
func TestGetOrgsByUserID(t *testing.T) {

View File

@ -1497,30 +1497,22 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
// Dummy object.
collaboration := &Collaboration{RepoID: repo.ID}
for _, c := range collaborators {
collaboration.UserID = c.ID
if c.ID == newOwner.ID || newOwner.IsOrgMember(c.ID) {
if _, err = sess.Delete(collaboration); err != nil {
return fmt.Errorf("remove collaborator '%d': %v", c.ID, err)
if c.ID != newOwner.ID {
isMember, err := newOwner.IsOrgMember(c.ID)
if err != nil {
return fmt.Errorf("IsOrgMember: %v", err)
} else if !isMember {
continue
}
}
collaboration.UserID = c.ID
if _, err = sess.Delete(collaboration); err != nil {
return fmt.Errorf("remove collaborator '%d': %v", c.ID, err)
}
}
// Remove old team-repository relations.
if owner.IsOrganization() {
if err = owner.getTeams(sess); err != nil {
return fmt.Errorf("getTeams: %v", err)
}
for _, t := range owner.Teams {
if !t.hasRepository(sess, repo.ID) {
continue
}
t.NumRepos--
if _, err := sess.ID(t.ID).Cols("num_repos").Update(t); err != nil {
return fmt.Errorf("decrease team repository count '%d': %v", t.ID, err)
}
}
if err = owner.removeOrgRepo(sess, repo.ID); err != nil {
return fmt.Errorf("removeOrgRepo: %v", err)
}

View File

@ -153,3 +153,26 @@ func TestRepoLocalCopyPath(t *testing.T) {
setting.Repository.Local.LocalCopyPath = tempPath
assert.Equal(t, expected, repo.LocalCopyPath())
}
func TestTransferOwnership(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
repo.Owner = AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
assert.NoError(t, TransferOwnership(doer, "user2", repo))
transferredRepo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.EqualValues(t, 2, transferredRepo.OwnerID)
assert.False(t, com.IsExist(RepoPath("user3", "repo3")))
assert.True(t, com.IsExist(RepoPath("user2", "repo3")))
AssertExistsAndLoadBean(t, &Action{
OpType: ActionTransferRepo,
ActUserID: 2,
RepoID: 3,
Content: "user3/repo3",
})
CheckConsistencyFor(t, &Repository{}, &User{}, &Team{})
}

View File

@ -115,7 +115,7 @@ func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error)
}
// BeanExists for testing, check if a bean exists
func BeanExists(t *testing.T, bean interface{}, conditions ...interface{}) bool {
func BeanExists(t testing.TB, bean interface{}, conditions ...interface{}) bool {
exists, err := loadBeanIfExists(bean, conditions...)
assert.NoError(t, err)
return exists
@ -123,7 +123,7 @@ func BeanExists(t *testing.T, bean interface{}, conditions ...interface{}) bool
// AssertExistsAndLoadBean assert that a bean exists and load it from the test
// database
func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...interface{}) interface{} {
func AssertExistsAndLoadBean(t testing.TB, bean interface{}, conditions ...interface{}) interface{} {
exists, err := loadBeanIfExists(bean, conditions...)
assert.NoError(t, err)
assert.True(t, exists,
@ -133,7 +133,7 @@ func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...inter
}
// GetCount get the count of a bean
func GetCount(t *testing.T, bean interface{}, conditions ...interface{}) int {
func GetCount(t testing.TB, bean interface{}, conditions ...interface{}) int {
sess := x.NewSession()
defer sess.Close()
whereConditions(sess, conditions)
@ -143,7 +143,7 @@ func GetCount(t *testing.T, bean interface{}, conditions ...interface{}) int {
}
// AssertNotExistsBean assert that a bean does not exist in the test database
func AssertNotExistsBean(t *testing.T, bean interface{}, conditions ...interface{}) {
func AssertNotExistsBean(t testing.TB, bean interface{}, conditions ...interface{}) {
exists, err := loadBeanIfExists(bean, conditions...)
assert.NoError(t, err)
assert.False(t, exists)
@ -158,18 +158,18 @@ func AssertExistsIf(t *testing.T, expected bool, bean interface{}, conditions ..
}
// AssertSuccessfulInsert assert that beans is successfully inserted
func AssertSuccessfulInsert(t *testing.T, beans ...interface{}) {
func AssertSuccessfulInsert(t testing.TB, beans ...interface{}) {
_, err := x.Insert(beans...)
assert.NoError(t, err)
}
// AssertCount assert the count of a bean
func AssertCount(t *testing.T, bean interface{}, expected interface{}) {
func AssertCount(t testing.TB, bean interface{}, expected interface{}) {
assert.EqualValues(t, expected, GetCount(t, bean))
}
// AssertInt64InRange assert value is in range [low, high]
func AssertInt64InRange(t *testing.T, low, high, value int64) {
func AssertInt64InRange(t testing.TB, low, high, value int64) {
assert.True(t, value >= low && value <= high,
"Expected value in range [%d, %d], found %d", low, high, value)
}

View File

@ -487,12 +487,22 @@ func (u *User) IsOrganization() bool {
// IsUserOrgOwner returns true if user is in the owner team of given organization.
func (u *User) IsUserOrgOwner(orgID int64) bool {
return IsOrganizationOwner(orgID, u.ID)
isOwner, err := IsOrganizationOwner(orgID, u.ID)
if err != nil {
log.Error(4, "IsOrganizationOwner: %v", err)
return false
}
return isOwner
}
// IsPublicMember returns true if user public his/her membership in given organization.
func (u *User) IsPublicMember(orgID int64) bool {
return IsPublicMembership(orgID, u.ID)
isMember, err := IsPublicMembership(orgID, u.ID)
if err != nil {
log.Error(4, "IsPublicMembership: %v", err)
return false
}
return isMember
}
func (u *User) getOrganizationCount(e Engine) (int64, error) {

View File

@ -73,14 +73,21 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
} else if ctx.IsSigned {
ctx.Org.IsOwner = org.IsOwnedBy(ctx.User.ID)
ctx.Org.IsOwner, err = org.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.Handle(500, "IsOwnedBy", err)
return
}
if ctx.Org.IsOwner {
ctx.Org.IsMember = true
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
} else {
if org.IsOrgMember(ctx.User.ID) {
ctx.Org.IsMember = true
ctx.Org.IsMember, err = org.IsOrgMember(ctx.User.ID)
if err != nil {
ctx.Handle(500, "IsOrgMember", err)
return
}
}
} else {

View File

@ -70,6 +70,13 @@ type MarkupParser struct {
IsInputFile bool
}
// enumerates all the policy repository creating
const (
RepoCreatingLastUserVisibility = "last"
RepoCreatingPrivate = "private"
RepoCreatingPublic = "public"
)
// settings
var (
// AppVer settings
@ -89,6 +96,8 @@ var (
HTTPAddr string
HTTPPort string
LocalURL string
RedirectOtherPort bool
PortToRedirect string
OfflineMode bool
DisableRouterLog bool
CertFile string
@ -180,6 +189,7 @@ var (
Repository = struct {
AnsiCharset string
ForcePrivate bool
DefaultPrivate string
MaxCreationLimit int
MirrorQueueLength int
PullRequestQueueLength int
@ -209,6 +219,7 @@ var (
}{
AnsiCharset: "",
ForcePrivate: false,
DefaultPrivate: RepoCreatingLastUserVisibility,
MaxCreationLimit: -1,
MirrorQueueLength: 1000,
PullRequestQueueLength: 1000,
@ -732,6 +743,8 @@ func NewContext() {
defaultLocalURL += ":" + HTTPPort + "/"
}
LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL)
RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false)
PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80")
OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(AppWorkPath)

View File

@ -489,6 +489,8 @@ mirror_last_synced=Zuletzt synchronisiert
watchers=Beobachter
stargazers=In Favoriten von
forks=Forks
pick_reaction=Wähle eine Reaktion
reactions_more=und %d weitere
form.reach_limit_of_creation=Du hast bereits dein Limit von %d Repositories erreicht.
form.name_reserved=Der Repository-Name '%s' ist reserviert.
@ -539,6 +541,7 @@ pulls=Pull-Requests
labels=Label
milestones=Meilensteine
commits=Commits
commit=Committen
releases=Releases
file_raw=Originalformat
file_history=Verlauf
@ -1544,6 +1547,7 @@ no_read=Du hast momentan keine gelesenen Benachrichtigungen.
pin=Benachrichtigung pinnen
mark_as_read=Als gelesen markieren
mark_as_unread=Als ungelesen markieren
mark_all_as_read=Alle als gelesen markieren
[gpg]
error.extract_sign=Die Signatur konnte nicht extrahiert werden

View File

@ -8,7 +8,7 @@ sign_in=Bejelentkezés
sign_in_with=Bejelentkezés a következővel
sign_out=Kijelentkezés
sign_up=Regisztráció
link_account=Fiók Összekötés
link_account=Fiók kapcsolása
link_account_signin_or_signup=Bejelentkezés meglévő felhasználóval, hogy a meglévő fiókodhoz kösd, vagy új fiók létrehozása
register=Regisztráció
website=Webhely
@ -49,22 +49,22 @@ your_settings=Beállításaid
all=Összes
sources=Saját
mirrors=Tükör
mirrors=Tükrök
collaborative=Közreműködő
forks=Másolat
forks=Másolatok
activities=Tevékenységek
pull_requests=Egyesítési Kérések
issues=Hibajegyek
cancel=Mégsem
cancel=Mégse
[install]
install=Telepítés
title=Kezdeti konfiguráció
docker_helper=Hogyha a Gitea-t Docker-en belül futtatod, akkor kérlek olvasd el az <a target="_blank" rel="noopener" href="%s">irányelveket</a> mielőtt bármit megváltoztatnál ezen az oldalon.
requite_db_desc=Giteához szükséges MySQL, PostgreSQL, SQLite3 vagy TiDB.
db_title=Adatbázis beállításai
docker_helper=Ha Docker-en belül futtatja a Gitea-t, kérjük olvassa el az <a target="_blank" rel="noopener" href="%s">irányelveket</a> mielőtt bármit megváltoztatna ezen az oldalon.
requite_db_desc=A Giteához MySQL, PostgreSQL, SQLite3 vagy TiDB adatbázis szükséges.
db_title=Adatbázis beállítások
db_type=Adatbázis típusa
host=Kiszolgáló
user=Felhasználó
@ -103,7 +103,7 @@ optional_title=További beállítások
email_title=E-mail szolgáltatás beállításai
smtp_host=SMTP kiszolgáló
smtp_from=Feladó
smtp_from_helper=E-Mail feladójának a címe az RFC 5322 formátumban. Lehet megadni csak egy e-mail címet, vagy "Név" <emailcím@example.com> formátumban.
smtp_from_helper=Az e-mail feladójának a címe, RFC 5322 formátumban, amely lehet csak az e-mail cím, vagy a "Név" <nev@palda.hu> formátum is.
mailer_user=Feladó Felhasználója
mailer_password=Feladó Jelszava
register_confirm=Regisztráció megerősítésének engedélyezése
@ -140,7 +140,7 @@ run_user_not_match=A futtató felhasználó nem az aktuális felhasználó: %s -
save_config_failed=Hiba történt a konfiguráció mentése közben: %v
invalid_admin_setting=Rendszergazda fiók beállítása helytelen: %v
install_success=Üdvözlünk! Köszönjük, hogy a Gitea-t választotta és jó szórakozást kívánunk a használatához!
invalid_log_root_path=Napó gyökérkönyvtára helytelen: %v
invalid_log_root_path=Napló gyökérkönyvtára helytelen: %v
default_keep_email_private=Email cím ne látszódjon" alapértelmezett értéke
default_keep_email_private_popup=Ez az alapértelmezett értéke a felhasználó e-mail cím láthatóságának. Ha értéke igaz, akkor minden új felhasználó e-mail címe rejtve marad, amíg a felhasználó nem módosítja a beállítását.
default_allow_create_organization=Felhasználó létrehozhat Szervezeteket" jogosultság alapértelmezett értéke

View File

@ -177,7 +177,10 @@ func reqOrgMembership() macaron.Handler {
return
}
if !models.IsOrganizationMember(orgID, ctx.User.ID) {
if isMember, err := models.IsOrganizationMember(orgID, ctx.User.ID); err != nil {
ctx.Error(500, "IsOrganizationMember", err)
return
} else if !isMember {
if ctx.Org.Organization != nil {
ctx.Error(403, "", "Must be an organization member")
} else {
@ -200,7 +203,10 @@ func reqOrgOwnership() macaron.Handler {
return
}
if !models.IsOrganizationOwner(orgID, ctx.User.ID) {
isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOrganizationOwner", err)
} else if !isOwner {
if ctx.Org.Organization != nil {
ctx.Error(403, "", "Must be an organization owner")
} else {

View File

@ -67,7 +67,15 @@ func ListMembers(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/UserList"
publicOnly := ctx.User == nil || !ctx.Org.Organization.IsOrgMember(ctx.User.ID)
publicOnly := true
if ctx.User != nil {
isMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOrgMember", err)
return
}
publicOnly = !isMember
}
listMembers(ctx, publicOnly)
}
@ -119,19 +127,30 @@ func IsMember(ctx *context.APIContext) {
if ctx.Written() {
return
}
if ctx.User != nil && ctx.Org.Organization.IsOrgMember(ctx.User.ID) {
if ctx.Org.Organization.IsOrgMember(userToCheck.ID) {
ctx.Status(204)
} else {
if ctx.User != nil {
userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOrgMember", err)
return
} else if userIsMember {
userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOrgMember", err)
} else if userToCheckIsMember {
ctx.Status(204)
} else {
ctx.Status(404)
}
return
} else if ctx.User.ID == userToCheck.ID {
ctx.Status(404)
return
}
} else if ctx.User != nil && ctx.User.ID == userToCheck.ID {
ctx.Status(404)
} else {
redirectURL := fmt.Sprintf("%sapi/v1/orgs/%s/public_members/%s",
setting.AppURL, ctx.Org.Organization.Name, userToCheck.Name)
ctx.Redirect(redirectURL, 302)
}
redirectURL := fmt.Sprintf("%sapi/v1/orgs/%s/public_members/%s",
setting.AppURL, ctx.Org.Organization.Name, userToCheck.Name)
ctx.Redirect(redirectURL, 302)
}
// IsPublicMember check if a user is a public member of an organization

View File

@ -176,7 +176,11 @@ func GetTeamMembers(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/UserList"
if !models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID) {
isMember, err := models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOrganizationMember", err)
return
} else if !isMember {
ctx.Status(404)
return
}

View File

@ -89,7 +89,11 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) {
}
return
}
if !org.IsOrgMember(ctx.User.ID) {
isMember, err := org.IsOrgMember(ctx.User.ID)
if err != nil {
ctx.Handle(500, "IsOrgMember", err)
return
} else if !isMember {
ctx.Status(403)
return
}

View File

@ -56,7 +56,7 @@ func ListIssues(ctx *context.APIContext) {
}
issues, err := models.Issues(&models.IssuesOptions{
RepoID: ctx.Repo.Repository.ID,
RepoIDs: []int64{ctx.Repo.Repository.ID},
Page: ctx.QueryInt("page"),
PageSize: setting.UI.IssuePagingNum,
IsClosed: isClosed,

View File

@ -108,8 +108,19 @@ func Search(ctx *context.APIContext) {
}
// Check visibility.
if ctx.IsSigned && (ctx.User.ID == repoOwner.ID || (repoOwner.IsOrganization() && repoOwner.IsOwnedBy(ctx.User.ID))) {
opts.Private = true
if ctx.IsSigned {
if ctx.User.ID == repoOwner.ID {
opts.Private = true
} else if repoOwner.IsOrganization() {
opts.Private, err = repoOwner.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.JSON(500, api.SearchError{
OK: false,
Error: err.Error(),
})
return
}
}
}
}
@ -245,7 +256,11 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
return
}
if !org.IsOwnedBy(ctx.User.ID) {
isOwner, err := org.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.Handle(500, "IsOwnedBy", err)
return
} else if !isOwner {
ctx.Error(403, "", "Given user is not owner of organization.")
return
}
@ -292,7 +307,11 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
if ctxUser.IsOrganization() && !ctx.User.IsAdmin {
// Check ownership of organization.
if !ctxUser.IsOwnedBy(ctx.User.ID) {
isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOwnedBy", err)
return
} else if !isOwner {
ctx.Error(403, "", "Given user is not owner of organization.")
return
}
@ -431,9 +450,15 @@ func Delete(ctx *context.APIContext) {
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
if owner.IsOrganization() && !owner.IsOwnedBy(ctx.User.ID) {
ctx.Error(403, "", "Given user is not owner of organization.")
return
if owner.IsOrganization() {
isOwner, err := owner.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOwnedBy", err)
return
} else if !isOwner {
ctx.Error(403, "", "Given user is not owner of organization.")
return
}
}
if err := models.DeleteRepository(ctx.User, owner.ID, repo.ID); err != nil {

View File

@ -191,8 +191,8 @@ func Issues(ctx *context.Context) {
issues = []*models.Issue{}
} else {
issues, err = models.Issues(&models.IssuesOptions{
RepoIDs: []int64{repo.ID},
AssigneeID: assigneeID,
RepoID: repo.ID,
PosterID: posterID,
MentionedID: mentionedID,
MilestoneID: milestoneID,
@ -476,6 +476,26 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
}
// commentTag returns the CommentTag for a comment in/with the given repo, poster and issue
func commentTag(repo *models.Repository, poster *models.User, issue *models.Issue) (models.CommentTag, error) {
if repo.IsOwnedBy(poster.ID) {
return models.CommentTagOwner, nil
} else if repo.Owner.IsOrganization() {
isOwner, err := repo.Owner.IsOwnedBy(poster.ID)
if err != nil {
return models.CommentTagNone, err
} else if isOwner {
return models.CommentTagOwner, nil
}
}
if poster.IsWriterOfRepo(repo) {
return models.CommentTagWriter, nil
} else if poster.ID == issue.PosterID {
return models.CommentTagPoster, nil
}
return models.CommentTagNone, nil
}
// ViewIssue render issue view page
func ViewIssue(ctx *context.Context) {
ctx.Data["RequireHighlightJS"] = true
@ -648,15 +668,11 @@ func ViewIssue(ctx *context.Context) {
continue
}
if repo.IsOwnedBy(comment.PosterID) ||
(repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) {
comment.ShowTag = models.CommentTagOwner
} else if comment.Poster.IsWriterOfRepo(repo) {
comment.ShowTag = models.CommentTagWriter
} else if comment.PosterID == issue.PosterID {
comment.ShowTag = models.CommentTagPoster
comment.ShowTag, err = commentTag(repo, comment.Poster, issue)
if err != nil {
ctx.Handle(500, "commentTag", err)
return
}
marked[comment.PosterID] = comment.ShowTag
isAdded := false

View File

@ -173,7 +173,11 @@ func ForkPost(ctx *context.Context, form auth.CreateRepoForm) {
// Check ownership of organization.
if ctxUser.IsOrganization() {
if !ctxUser.IsOwnedBy(ctx.User.ID) {
isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.Handle(500, "IsOwnedBy", err)
return
} else if !isOwner {
ctx.Error(403)
return
}

View File

@ -74,13 +74,36 @@ func checkContextUser(ctx *context.Context, uid int64) *models.User {
}
// Check ownership of organization.
if !org.IsOrganization() || !(ctx.User.IsAdmin || org.IsOwnedBy(ctx.User.ID)) {
if !org.IsOrganization() {
ctx.Error(403)
return nil
}
if !ctx.User.IsAdmin {
isOwner, err := org.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.Handle(500, "IsOwnedBy", err)
return nil
} else if !isOwner {
ctx.Error(403)
return nil
}
}
return org
}
func getRepoPrivate(ctx *context.Context) bool {
switch strings.ToLower(setting.Repository.DefaultPrivate) {
case setting.RepoCreatingLastUserVisibility:
return ctx.User.LastRepoVisibility
case setting.RepoCreatingPrivate:
return true
case setting.RepoCreatingPublic:
return false
default:
return ctx.User.LastRepoVisibility
}
}
// Create render creating repository page
func Create(ctx *context.Context) {
if !ctx.User.CanCreateRepo() {
@ -94,7 +117,7 @@ func Create(ctx *context.Context) {
ctx.Data["Licenses"] = models.Licenses
ctx.Data["Readmes"] = models.Readmes
ctx.Data["readme"] = "Default"
ctx.Data["private"] = ctx.User.LastRepoVisibility
ctx.Data["private"] = getRepoPrivate(ctx)
ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
ctxUser := checkContextUser(ctx, ctx.QueryInt64("org"))
@ -170,7 +193,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
// Migrate render migration of repository page
func Migrate(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_migrate")
ctx.Data["private"] = ctx.User.LastRepoVisibility
ctx.Data["private"] = getRepoPrivate(ctx)
ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
ctx.Data["mirror"] = ctx.Query("mirror") == "1"
ctx.Data["LFSActive"] = setting.LFS.StartServer

View File

@ -235,13 +235,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return
}
if ctx.Repo.Owner.IsOrganization() {
if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
ctx.Error(404)
return
}
}
if !repo.IsMirror {
ctx.Error(404)
return
@ -269,13 +262,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return
}
if ctx.Repo.Owner.IsOrganization() {
if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
ctx.Error(404)
return
}
}
newOwner := ctx.Query("new_owner_name")
isExist, err := models.IsUserExist(0, newOwner)
if err != nil {
@ -308,13 +294,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return
}
if ctx.Repo.Owner.IsOrganization() {
if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
ctx.Error(404)
return
}
}
if err := models.DeleteRepository(ctx.User, ctx.Repo.Owner.ID, repo.ID); err != nil {
ctx.Handle(500, "DeleteRepository", err)
return
@ -334,13 +313,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return
}
if ctx.Repo.Owner.IsOrganization() {
if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
ctx.Error(404)
return
}
}
repo.DeleteWiki()
log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
@ -394,10 +366,16 @@ func CollaborationPost(ctx *context.Context) {
}
// Check if user is organization member.
if ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgMember(u.ID) {
ctx.Flash.Info(ctx.Tr("repo.settings.user_is_org_member"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
return
if ctx.Repo.Owner.IsOrganization() {
isMember, err := ctx.Repo.Owner.IsOrgMember(u.ID)
if err != nil {
ctx.Handle(500, "IsOrgMember", err)
return
} else if isMember {
ctx.Flash.Info(ctx.Tr("repo.settings.user_is_org_member"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
return
}
}
if err = ctx.Repo.Repository.AddCollaborator(u); err != nil {

View File

@ -9,13 +9,14 @@ import (
"fmt"
"sort"
"github.com/Unknwon/paginater"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/Unknwon/paginater"
)
const (
@ -231,21 +232,30 @@ func Issues(ctx *context.Context) {
return
}
}
if len(userRepoIDs) <= 0 {
userRepoIDs = []int64{-1}
}
opts := &models.IssuesOptions{
RepoID: repoID,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: util.OptionalBoolOf(isPullList),
SortType: sortType,
}
if repoID > 0 {
opts.RepoIDs = []int64{repoID}
}
switch filterMode {
case models.FilterModeAll:
opts.RepoIDs = userRepoIDs
if repoID > 0 {
if !com.IsSliceContainsInt64(userRepoIDs, repoID) {
// force an empty result
opts.RepoIDs = []int64{-1}
}
} else {
opts.RepoIDs = userRepoIDs
}
case models.FilterModeAssign:
opts.AssigneeID = ctxUser.ID
case models.FilterModeCreate:
@ -308,7 +318,18 @@ func Issues(ctx *context.Context) {
issue.Repo = showReposMap[issue.RepoID]
}
issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList)
issueStats, err := models.GetUserIssueStats(models.UserIssueStatsOptions{
UserID: ctxUser.ID,
RepoID: repoID,
UserRepoIDs: userRepoIDs,
FilterMode: filterMode,
IsPull: isPullList,
IsClosed: isShowClosed,
})
if err != nil {
ctx.Handle(500, "GetUserIssueStats", err)
return
}
var total int
if !isShowClosed {

View File

@ -79,7 +79,7 @@ func targetedSearch(state *getCommitsInfoState, done chan error) {
done <- nil
return
}
command := NewCommand("rev-list", "-1", "HEAD", "--", entryPath)
command := NewCommand("rev-list", "-1", state.headCommit.ID.String(), "--", entryPath)
output, err := command.RunInDir(state.headCommit.repo.Path)
if err != nil {
done <- err
@ -192,7 +192,7 @@ func getCommitsInfo(state *getCommitsInfoState) error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
args := []string{"log", getCommitsInfoPretty, "--name-status", "-c"}
args := []string{"log", state.headCommit.ID.String(), getCommitsInfoPretty, "--name-status", "-c"}
if len(state.treePath) > 0 {
args = append(args, "--", state.treePath)
}
@ -207,6 +207,10 @@ func getCommitsInfo(state *getCommitsInfoState) error {
if err := cmd.Start(); err != nil {
return err
}
// it's okay to ignore the error returned by cmd.Wait(); we expect the
// subprocess to sometimes have a non-zero exit status, since we may
// prematurely close stdout, resulting in a broken pipe.
defer cmd.Wait()
numThreads := runtime.NumCPU()
done := make(chan error, numThreads)
@ -216,6 +220,14 @@ func getCommitsInfo(state *getCommitsInfoState) error {
scanner := bufio.NewScanner(readCloser)
err = state.processGitLogOutput(scanner)
// it is important that we close stdout here; if we do not close
// stdout, the subprocess will keep running, and the deffered call
// cmd.Wait() may block for a long time.
if closeErr := readCloser.Close(); closeErr != nil && err == nil {
err = closeErr
}
for i := 0; i < numThreads; i++ {
doneErr := <-done
if doneErr != nil && err == nil {

6
vendor/vendor.json vendored
View File

@ -3,10 +3,10 @@
"ignore": "test appengine",
"package": [
{
"checksumSHA1": "UnJFMWkh0ulYlOV0etJYgt5SzJY=",
"checksumSHA1": "Em29XiKkOh5rFFXdkCjqqsQ7fe4=",
"path": "code.gitea.io/git",
"revision": "4768133d10fa395278f545f3bf3ce44552b30ad6",
"revisionTime": "2017-12-10T10:06:09Z"
"revision": "4ec3654064ef7eef4f05f891073a38039ad8d0f7",
"revisionTime": "2017-12-22T02:43:26Z"
},
{
"checksumSHA1": "QQ7g7B9+EIzGjO14KCGEs9TNEzM=",