Merge remote-tracking branch 'origin'

This commit is contained in:
Lanre Adelowo 2018-07-05 15:49:33 +01:00
commit 93b187ae19
47 changed files with 1154 additions and 106 deletions

View File

@ -4,7 +4,7 @@ workspace:
clone:
git:
image: plugins/git:1
image: plugins/git:next
depth: 50
tags: true
@ -255,6 +255,18 @@ pipeline:
when:
event: [ push, tag ]
gpg-sign:
image: plugins/gpgsign:1
pull: true
secrets: [ gpgsign_key, gpgsign_passphrase ]
detach_sign: true
files:
- dist/release/*
excludes:
- dist/release/*.sha256
when:
event: [ push, tag ]
release:
image: plugins/s3:1
pull: true

View File

@ -4,6 +4,18 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.4.3](https://github.com/go-gitea/gitea/releases/tag/v1.4.3) - 2018-06-26
* SECURITY
* HTML-escape plain-text READMEs (#4192) (#4214)
* Fix open redirect vulnerability on login screen (#4312) (#4312)
* BUGFIXES
* Fix broken monitoring page when running processes are shown (#4203) (#4208)
* Fix delete comment bug (#4216) (#4228)
* Delete reactions added to issues and comments when deleting repository (#4232) (#4237)
* Fix wiki URL encoding bug (#4091) (#4254)
* Fix code tab link when viewing tags (#3908) (#4263)
* Fix webhook type conflation (#4285) (#4285)
## [1.4.2](https://github.com/go-gitea/gitea/releases/tag/v1.4.2) - 2018-06-04
* BUGFIXES
* Adjust z-index for floating labels (#3939) (#3950)

View File

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type="manifest" name="export">
<service name="gitea" type="service" version="1">
<create_default_instance enabled="false"/>
<dependency name="network" grouping="require_all" restart_on="refresh" type="service">
<service_fmri value="svc:/milestone/network:default"/>
</dependency>
<dependency name="filesystem" grouping="require_all" restart_on="refresh" type="service">
<service_fmri value="svc:/system/filesystem/local"/>
</dependency>
<exec_method
type="method"
name="start"
exec="/opt/local/bin/gitea web"
timeout_seconds="60">
<method_context>
<method_credential user="git" group="git" />
<method_environment>
<envvar name='GITEA_WORK_DIR' value='/opt/local/share/gitea'/>
<envvar name='GITEA_CUSTOM' value='/opt/local/etc/gitea'/>
<envvar name='HOME' value='/var/db/gitea'/>
<envvar name='PATH' value='/opt/local/bin:${PATH}'/>
<envvar name='USER' value='git'/>
</method_environment>
</method_context>
</exec_method>
<exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/>
<property_group name="application" type="application"></property_group>
<property_group name="startd" type="framework">
<propval name="duration" type="astring" value="child"/>
<propval name="ignore_error" type="astring" value="core,signal"/>
</property_group>
<template>
<common_name>
<loctext xml:lang="C">A painless, self-hosted Git service</loctext>
</common_name>
</template>
</service>
</service_bundle>

View File

@ -1,7 +1,5 @@
#!/bin/bash
/usr/sbin/update-ca-certificates
if [ ! -d /data/git/.ssh ]; then
mkdir -p /data/git/.ssh
chmod 700 /data/git/.ssh

View File

@ -0,0 +1,75 @@
---
date: "2018-06-24:00:00+02:00"
title: "API Usage"
slug: "api-usage"
weight: 40
toc: true
draft: false
menu:
sidebar:
parent: "advanced"
name: "API Usage"
weight: 40
identifier: "api-usage"
---
# Gitea API Usage
## Enabling/configuring API access
By default, `ENABLE_SWAGGER_ENDPOINT` is true, and
`MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat
Sheet](https://docs.gitea.io/en-us/config-cheat-sheet/) for more
information.
## Authentication via the API
Gitea supports these methods of API authentication:
- HTTP basic authentication
- `token=...` parameter in URL query string
- `access_token=...` parameter in URL query string
- `Authorization: token ...` header in HTTP headers
All of these methods accept the same apiKey token type. You can
better understand this by looking at the code -- as of this writing,
Gitea parses queries and headers to find the token in
[modules/auth/auth.go](https://github.com/go-gitea/gitea/blob/6efdcaed86565c91a3dc77631372a9cc45a58e89/modules/auth/auth.go#L47).
You can create an apiKey token via your gitea install's web interface:
`Settings | Applications | Generate New Token`.
### More on the `Authorization:` header
For historical reasons, Gitea needs the word `token` included before
the apiKey token in an authorization header, like this:
```
Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675
```
In a `curl` command, for instance, this would look like:
```
curl -X POST "http://localhost:4000/api/v1/repos/test1/test1/issues" \
-H "accept: application/json" \
-H "Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675" \
-H "Content-Type: application/json" -d "{ \"body\": \"testing\", \"title\": \"test 20\"}" -i
```
As mentioned above, the token used is the same one you would use in
the `token=` string in a GET request.
## Listing your issued tokens via the API
As mentioned in
[#3842](https://github.com/go-gitea/gitea/issues/3842#issuecomment-397743346),
`/users/:name/tokens` is special and requires you to authenticate
using BasicAuth, as follows:
### Using basic authentication:
```
$ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens
[{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}]
```

View File

@ -245,6 +245,8 @@ You can configure some of Gitea's settings via environment variables:
* `SECRET_KEY`: **""**: Global secret key. This should be changed. If this has a value and `INSTALL_LOCK` is empty, `INSTALL_LOCK` will automatically set to `true`.
* `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users.
* `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page.
* `USER_UID`: **1000**: The UID (Unix user ID) of the user that runs Gitea within the container. Match this to the UID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
* `USER_GID`: **1000**: The GID (Unix group ID) of the user that runs Gitea within the container. Match this to the GID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
# Customization

View File

@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
expectedResults
}{
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
nil: {count: 15},
user: {count: 15},
user2: {count: 15}},
nil: {count: 16},
user: {count: 16},
user2: {count: 16}},
},
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
nil: {count: 10},

View File

@ -22,8 +22,12 @@ func TestAccessLevel(t *testing.T) {
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
// A public repository owned by User 2
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
assert.False(t, repo1.IsPrivate)
// A private repository owned by Org 3
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.True(t, repo2.IsPrivate)
level, err := AccessLevel(user1.ID, repo1)
assert.NoError(t, err)
@ -47,8 +51,12 @@ func TestHasAccess(t *testing.T) {
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
// A public repository owned by User 2
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
assert.False(t, repo1.IsPrivate)
// A private repository owned by Org 3
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.True(t, repo2.IsPrivate)
for _, accessMode := range accessModes {
has, err := HasAccess(user1.ID, repo1, accessMode)

View File

@ -351,7 +351,7 @@
is_mirror: true
num_forks: 1
is_fork: false
-
id: 29
fork_id: 27
@ -365,7 +365,7 @@
num_closed_pulls: 0
is_mirror: false
is_fork: true
-
id: 30
fork_id: 28
@ -389,3 +389,14 @@
num_forks: 0
num_issues: 0
is_mirror: false
-
id: 32
owner_id: 3
lower_name: repo21
name: repo21
is_private: false
num_stars: 0
num_forks: 0
num_issues: 0
is_mirror: false

View File

@ -4,9 +4,8 @@
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 2
num_repos: 3
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'
-
id: 2
@ -16,7 +15,6 @@
authorize: 2 # write
num_repos: 1
num_members: 2
unit_types: '[1,2,3,4,5,6,7]'
-
id: 3
@ -26,7 +24,6 @@
authorize: 4 # owner
num_repos: 0
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'
-
id: 4
@ -36,7 +33,6 @@
authorize: 4 # owner
num_repos: 0
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'
-
id: 5
@ -46,7 +42,6 @@
authorize: 4 # owner
num_repos: 2
num_members: 2
unit_types: '[1,2,3,4,5,6,7]'
-
id: 6
@ -56,4 +51,3 @@
authorize: 4 # owner
num_repos: 2
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'

View File

@ -33,9 +33,15 @@
org_id: 19
team_id: 6
repo_id: 27
-
id: 7
org_id: 19
team_id: 6
repo_id: 28
repo_id: 28
-
id: 8
org_id: 3
team_id: 1
repo_id: 32

View 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

View File

@ -45,7 +45,7 @@
is_admin: false
avatar: avatar3
avatar_email: user3@example.com
num_repos: 2
num_repos: 3
num_members: 2
num_teams: 2

View File

@ -188,6 +188,10 @@ var migrations = []Migration{
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

View File

@ -25,10 +25,15 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
Created time.Time `xorm:"-"`
}
type Team struct {
ID int64
UnitTypes []int `xorm:"json"`
}
// Update team unit types
const batchSize = 100
for start := 0; ; start += batchSize {
teams := make([]*models.Team, 0, batchSize)
teams := make([]*Team, 0, batchSize)
if err := x.Limit(batchSize, start).Find(&teams); err != nil {
return err
}
@ -36,7 +41,7 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
break
}
for _, team := range teams {
ut := make([]models.UnitType, 0, len(team.UnitTypes))
ut := make([]int, 0, len(team.UnitTypes))
for _, u := range team.UnitTypes {
if u < V16UnitTypeCommits {
ut = append(ut, u)

213
models/migrations/v68.go Normal file
View File

@ -0,0 +1,213 @@
// 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"
"regexp"
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
func validateTopic(topic string) bool {
return len(topic) <= 35 && topicPattern.MatchString(topic)
}
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"`
RepoCount int
CreatedUnix int64 `xorm:"INDEX created"`
UpdatedUnix int64 `xorm:"INDEX updated"`
}
type RepoTopic struct {
RepoID int64 `xorm:"UNIQUE(s)"`
TopicID int64 `xorm:"UNIQUE(s)"`
}
type Repository struct {
ID int64 `xorm:"pk autoincr"`
Topics []string `xorm:"TEXT JSON"`
}
if err := x.Sync2(new(Topic)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
if err := x.Sync2(new(RepoTopic)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
sess := x.NewSession()
defer sess.Close()
const batchSize = 100
touchedRepo := make(map[int64]struct{})
delTopicIDs := make([]int64, 0, batchSize)
log.Info("Validating existed topics...")
if err := sess.Begin(); err != nil {
return err
}
for start := 0; ; start += batchSize {
topics := make([]*Topic, 0, batchSize)
if err := x.Cols("id", "name").Asc("id").Limit(batchSize, start).Find(&topics); err != nil {
return err
}
if len(topics) == 0 {
break
}
for _, topic := range topics {
if validateTopic(topic.Name) {
continue
}
log.Info("Incorrect topic: id = %v, name = %q", topic.ID, topic.Name)
topic.Name = strings.Replace(strings.TrimSpace(strings.ToLower(topic.Name)), " ", "-", -1)
ids := make([]int64, 0, 30)
if err := sess.Table("repo_topic").Cols("repo_id").
Where("topic_id = ?", topic.ID).Find(&ids); err != nil {
return err
}
log.Info("Touched repo ids: %v", ids)
for _, id := range ids {
touchedRepo[id] = struct{}{}
}
if validateTopic(topic.Name) {
unifiedTopic := Topic{Name: topic.Name}
exists, err := sess.Cols("id", "name").Get(&unifiedTopic)
log.Info("Exists topic with the name %q? %v, id = %v", topic.Name, exists, unifiedTopic.ID)
if err != nil {
return err
}
if exists {
log.Info("Updating repo_topic rows with topic_id = %v to topic_id = %v", topic.ID, unifiedTopic.ID)
if _, err := sess.Where("topic_id = ? AND repo_id NOT IN "+
"(SELECT rt1.repo_id FROM repo_topic rt1 INNER JOIN repo_topic rt2 "+
"ON rt1.repo_id = rt2.repo_id WHERE rt1.topic_id = ? AND rt2.topic_id = ?)",
topic.ID, topic.ID, unifiedTopic.ID).Update(&RepoTopic{TopicID: unifiedTopic.ID}); err != nil {
return err
}
log.Info("Updating topic `repo_count` field")
if _, err := sess.Exec(
"UPDATE topic SET repo_count = (SELECT COUNT(*) FROM repo_topic WHERE topic_id = ? GROUP BY topic_id) WHERE id = ?",
unifiedTopic.ID, unifiedTopic.ID); err != nil {
return err
}
} else {
log.Info("Updating topic: id = %v, name = %q", topic.ID, topic.Name)
if _, err := sess.Table("topic").ID(topic.ID).
Update(&Topic{Name: topic.Name}); err != nil {
return err
}
continue
}
}
delTopicIDs = append(delTopicIDs, topic.ID)
}
}
if err := sess.Commit(); err != nil {
return err
}
sess.Init()
log.Info("Deleting incorrect topics...")
if err := sess.Begin(); err != nil {
return err
}
log.Info("Deleting 'repo_topic' rows for topics with ids = %v", delTopicIDs)
if _, err := sess.In("topic_id", delTopicIDs).Delete(&RepoTopic{}); err != nil {
return err
}
log.Info("Deleting topics with id = %v", delTopicIDs)
if _, err := sess.In("id", delTopicIDs).Delete(&Topic{}); err != nil {
return err
}
if err := sess.Commit(); err != nil {
return err
}
delRepoTopics := make([]*RepoTopic, 0, batchSize)
log.Info("Checking the number of topics in the repositories...")
for start := 0; ; start += batchSize {
repoTopics := make([]*RepoTopic, 0, batchSize)
if err := x.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 := make([]*RepoTopic, 0, 30)
if err := x.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])
}
}
}
sess.Init()
log.Info("Deleting superfluous topics for repositories (more than 25 topics)...")
if err := sess.Begin(); err != nil {
return err
}
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(&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
}
}
log.Info("Updating repositories 'topics' fields...")
for repoID := range touchedRepo {
topicNames := make([]string, 0, 30)
if err := sess.Table("topic").Cols("name").
Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id").
Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").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(&Repository{Topics: topicNames}); err != nil {
return err
}
}
if err := sess.Commit(); err != nil {
return err
}
return nil
}

80
models/migrations/v69.go Normal file
View 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()
}

View File

@ -1,4 +1,5 @@
// 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
// license that can be found in the LICENSE file.
@ -121,6 +122,7 @@ func init() {
new(Reaction),
new(IssueAssignees),
new(U2FRegistration),
new(TeamUnit),
)
gonicNames := []string{"SSL", "UID"}
@ -184,6 +186,18 @@ func parsePostgreSQLHostPort(info string) (string, string) {
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) {
host, port := "127.0.0.1", "1433"
if strings.Contains(info, ":") {
@ -214,14 +228,7 @@ func getEngine() (*xorm.Engine, error) {
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
}
case "postgres":
host, port := parsePostgreSQLHostPort(DbCfg.Host)
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)
}
connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode)
case "mssql":
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)

View File

@ -1,4 +1,5 @@
// 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
// 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)
}
}
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)
}
}

View File

@ -119,7 +119,17 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
}
}
issue.loadRepo(e)
for _, watch := range watches {
issue.Repo.Units = nil
if issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypePullRequests) {
continue
}
if !issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypeIssues) {
continue
}
if err := notifyUser(watch.UserID); err != nil {
return err
}

View File

@ -154,12 +154,26 @@ func CreateOrganization(org, owner *User) (err error) {
Name: ownerTeamName,
Authorize: AccessModeOwner,
NumMembers: 1,
UnitTypes: allRepUnitTypes,
}
if _, err = sess.Insert(t); err != nil {
return fmt.Errorf("insert owner team: %v", err)
}
// insert units for team
var units = make([]TeamUnit, 0, len(allRepUnitTypes))
for _, tp := range allRepUnitTypes {
units = append(units, TeamUnit{
OrgID: org.ID,
TeamID: t.ID,
Type: tp,
})
}
if _, err = sess.Insert(&units); err != nil {
sess.Rollback()
return err
}
if _, err = sess.Insert(&TeamUser{
UID: owner.ID,
OrgID: org.ID,
@ -238,6 +252,7 @@ func deleteOrg(e *xorm.Session, u *User) error {
&Team{OrgID: u.ID},
&OrgUser{OrgID: u.ID},
&TeamUser{OrgID: u.ID},
&TeamUnit{OrgID: u.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}

View File

@ -1,3 +1,4 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -10,7 +11,6 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)
@ -28,15 +28,16 @@ type Team struct {
Members []*User `xorm:"-"`
NumRepos int
NumMembers int
UnitTypes []UnitType `xorm:"json"`
Units []*TeamUnit `xorm:"-"`
}
// GetUnitTypes returns unit types the team owned, empty means all the unit types
func (t *Team) GetUnitTypes() []UnitType {
if len(t.UnitTypes) == 0 {
return allRepUnitTypes
func (t *Team) getUnits(e Engine) (err error) {
if t.Units != nil {
return nil
}
return t.UnitTypes
t.Units, err = getUnitsByTeamID(e, t.ID)
return err
}
// HasWriteAccess returns true if team has at least write level access mode.
@ -214,11 +215,12 @@ func (t *Team) RemoveRepository(repoID int64) error {
// UnitEnabled returns if the team has the given unit type enabled
func (t *Team) UnitEnabled(tp UnitType) bool {
if len(t.UnitTypes) == 0 {
return true
if err := t.getUnits(x); err != nil {
log.Warn("Error loading repository (ID: %d) units: %s", t.ID, err.Error())
}
for _, u := range t.UnitTypes {
if u == tp {
for _, unit := range t.Units {
if unit.Type == tp {
return true
}
}
@ -275,6 +277,17 @@ func NewTeam(t *Team) (err error) {
return err
}
// insert units for team
if len(t.Units) > 0 {
for _, unit := range t.Units {
unit.TeamID = t.ID
}
if _, err = sess.Insert(&t.Units); err != nil {
sess.Rollback()
return err
}
}
// Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
sess.Rollback()
@ -424,6 +437,13 @@ func DeleteTeam(t *Team) error {
return err
}
// Delete team-unit.
if _, err := sess.
Where("team_id=?", t.ID).
Delete(new(TeamUnit)); err != nil {
return err
}
// Delete team.
if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
return err
@ -695,3 +715,47 @@ func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, er
And("team_repo.repo_id = ?", repoID).
Find(&teams)
}
// ___________ ____ ___ .__ __
// \__ ___/___ _____ _____ | | \____ |__|/ |_
// | |_/ __ \\__ \ / \| | / \| \ __\
// | |\ ___/ / __ \| Y Y \ | / | \ || |
// |____| \___ >____ /__|_| /______/|___| /__||__|
// \/ \/ \/ \/
// TeamUnit describes all units of a repository
type TeamUnit struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
TeamID int64 `xorm:"UNIQUE(s)"`
Type UnitType `xorm:"UNIQUE(s)"`
}
// Unit returns Unit
func (t *TeamUnit) Unit() Unit {
return Units[t.Type]
}
func getUnitsByTeamID(e Engine, teamID int64) (units []*TeamUnit, err error) {
return units, e.Where("team_id = ?", teamID).Find(&units)
}
// UpdateTeamUnits updates a teams's units
func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil {
return err
}
if _, err = sess.Insert(units); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
}

View File

@ -489,8 +489,8 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, expectedCount, count)
}
testSuccess(2, 2)
testSuccess(4, 1)
testSuccess(2, 3)
testSuccess(4, 2)
}
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
@ -503,8 +503,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expectedRepoIDs, repoIDs)
}
testSuccess(2, 1, 100, []int64{3, 5})
testSuccess(4, 0, 100, []int64{3})
testSuccess(2, 1, 100, []int64{3, 5, 32})
testSuccess(4, 0, 100, []int64{3, 32})
}
func TestAccessibleReposEnv_Repos(t *testing.T) {
@ -522,8 +522,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) {
}
assert.Equal(t, expectedRepos, repos)
}
testSuccess(2, []int64{3, 5})
testSuccess(4, []int64{3})
testSuccess(2, []int64{3, 5, 32})
testSuccess(4, []int64{3, 32})
}
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {

View File

@ -365,22 +365,14 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) (
return err
}
var allTypes = make(map[UnitType]struct{}, len(allRepUnitTypes))
for _, team := range teams {
// Administrators can not be limited
if team.Authorize >= AccessModeAdmin {
return nil
}
for _, unitType := range team.UnitTypes {
allTypes[unitType] = struct{}{}
}
}
// unique
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units))
for _, u := range repo.Units {
if _, ok := allTypes[u.Type]; ok {
newRepoUnits = append(newRepoUnits, u)
for _, team := range teams {
if team.UnitEnabled(u.Type) {
newRepoUnits = append(newRepoUnits, u)
break
}
}
}

View File

@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 14},
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
count: 15},
count: 16},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 19},
count: 20},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 13},
@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 11},
{name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
count: 15},
count: 16},
}
for _, testCase := range testCases {

View File

@ -109,6 +109,23 @@ func notifyWatchers(e Engine, act *Action) error {
act.ID = 0
act.UserID = watches[i].UserID
act.Repo.Units = nil
switch act.OpType {
case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch:
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeCode) {
continue
}
case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue:
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeIssues) {
continue
}
case ActionCreatePullRequest, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest:
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypePullRequests) {
continue
}
}
if _, err = e.InsertOne(act); err != nil {
return fmt.Errorf("insert new action: %v", err)
}

View File

@ -6,6 +6,7 @@ package models
import (
"fmt"
"regexp"
"strings"
"code.gitea.io/gitea/modules/util"
@ -20,10 +21,12 @@ func init() {
)
}
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
// Topic represents a topic of repositories
type Topic struct {
ID int64
Name string `xorm:"unique"`
Name string `xorm:"UNIQUE"`
RepoCount int
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
@ -31,8 +34,8 @@ type Topic struct {
// RepoTopic represents associated repositories and topics
type RepoTopic struct {
RepoID int64 `xorm:"unique(s)"`
TopicID int64 `xorm:"unique(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
TopicID int64 `xorm:"UNIQUE(s)"`
}
// ErrTopicNotExist represents an error that a topic is not exist
@ -51,6 +54,11 @@ func (err ErrTopicNotExist) Error() string {
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
func GetTopicByName(name string) (*Topic, error) {
var topic Topic
@ -182,6 +190,13 @@ func SaveTopics(repoID int64, topicNames ...string) error {
}
}
topicNames = make([]string, 0, 25)
if err := sess.Table("topic").Cols("name").
Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id").
Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil {
return err
}
if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{
Topics: topicNames,
}); err != nil {

View File

@ -55,3 +55,16 @@ func TestAddTopic(t *testing.T) {
assert.NoError(t, err)
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"))
}

View File

@ -546,28 +546,46 @@ func (u *User) GetRepositories(page, pageSize int) (err error) {
return err
}
// GetRepositoryIDs returns repositories IDs where user owned
func (u *User) GetRepositoryIDs() ([]int64, error) {
// GetRepositoryIDs returns repositories IDs where user owned and has unittypes
func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) {
var ids []int64
return ids, x.Table("repository").Cols("id").Where("owner_id = ?", u.ID).Find(&ids)
sess := x.Table("repository").Cols("repository.id")
if len(units) > 0 {
sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id")
sess = sess.In("repo_unit.type", units)
}
return ids, sess.Where("owner_id = ?", u.ID).Find(&ids)
}
// GetOrgRepositoryIDs returns repositories IDs where user's team owned
func (u *User) GetOrgRepositoryIDs() ([]int64, error) {
// GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes
func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) {
var ids []int64
return ids, x.Table("repository").
sess := x.Table("repository").
Cols("repository.id").
Join("INNER", "team_user", "repository.owner_id = team_user.org_id AND team_user.uid = ?", u.ID).
Join("INNER", "team_user", "repository.owner_id = team_user.org_id").
Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true)
if len(units) > 0 {
sess = sess.Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id")
sess = sess.In("team_unit.type", units)
}
return ids, sess.
Where("team_user.uid = ?", u.ID).
GroupBy("repository.id").Find(&ids)
}
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
func (u *User) GetAccessRepoIDs() ([]int64, error) {
ids, err := u.GetRepositoryIDs()
func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) {
ids, err := u.GetRepositoryIDs(units...)
if err != nil {
return nil, err
}
ids2, err := u.GetOrgRepositoryIDs()
ids2, err := u.GetOrgRepositoryIDs(units...)
if err != nil {
return nil, err
}

View File

@ -159,3 +159,25 @@ func BenchmarkHashPassword(b *testing.B) {
u.HashPassword(pass)
}
}
func TestGetOrgRepositoryIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
accessibleRepos, err := user2.GetOrgRepositoryIDs()
assert.NoError(t, err)
// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization
assert.Equal(t, []int64{3, 5, 32}, accessibleRepos)
accessibleRepos, err = user4.GetOrgRepositoryIDs()
assert.NoError(t, err)
// User 4's team has access to private repo 3, repo 32 is a public repo of the organization
assert.Equal(t, []int64{3, 32}, accessibleRepos)
accessibleRepos, err = user5.GetOrgRepositoryIDs()
assert.NoError(t, err)
// User 5's team has no access to any repo
assert.Len(t, accessibleRepos, 0)
}

View File

@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// OptionalBool a boolean that can be "null"
@ -78,6 +79,18 @@ func URLJoin(base string, elems ...string) string {
return joinedURL
}
// IsExternalURL checks if rawURL points to an external URL like http://example.com
func IsExternalURL(rawURL string) bool {
parsed, err := url.Parse(rawURL)
if err != nil {
return true
}
if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(setting.Domain, "www.", "", 1) {
return true
}
return false
}
// Min min of two ints
func Min(a, b int) int {
if a > b {

View File

@ -7,6 +7,8 @@ package util
import (
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
@ -42,3 +44,36 @@ func TestURLJoin(t *testing.T) {
assert.Equal(t, test.Expected, URLJoin(test.Base, test.Elements...))
}
}
func TestIsExternalURL(t *testing.T) {
setting.Domain = "try.gitea.io"
type test struct {
Expected bool
RawURL string
}
newTest := func(expected bool, rawURL string) test {
return test{Expected: expected, RawURL: rawURL}
}
for _, test := range []test{
newTest(false,
"https://try.gitea.io"),
newTest(true,
"https://example.com/"),
newTest(true,
"//example.com"),
newTest(true,
"http://example.com"),
newTest(false,
"a/"),
newTest(false,
"https://try.gitea.io/test?param=false"),
newTest(false,
"test?param=false"),
newTest(false,
"//try.gitea.io/test?param=false"),
newTest(false,
"/hey/hey/hey#3244"),
} {
assert.Equal(t, test.Expected, IsExternalURL(test.RawURL))
}
}

View File

@ -97,7 +97,7 @@ app_name=Seitentitel
app_name_helper=Du kannst hier den Namen deines Unternehmens eingeben.
repo_path=Repository-Verzeichnis
repo_path_helper=Remote-Git-Repositories werden in diesem Verzeichnis gespeichert.
lfs_path=Git LFS-Wurzelpfad
lfs_path=Git-LFS-Wurzelpfad
lfs_path_helper=In diesem Verzeichnis werden die Dateien von Git LFS abgespeichert. Leer lassen um LFS zu deaktivieren.
run_user=Ausführen als
run_user_helper=Gebe den Betriebssystem-Benutzernamen ein, unter welchem Gitea laufen soll. Beachte, dass dieser Nutzer Zugriff auf den Repository-Ordner haben muss.
@ -588,7 +588,7 @@ editor.edit_file=Datei bearbeiten
editor.preview_changes=Vorschau der Änderungen
editor.cannot_edit_non_text_files=Binärdateien können nicht im Webinterface bearbeitet werden.
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.delete_this_file=Datei löschen
editor.must_have_write_access=Du benötigst Schreibzugriff, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen.
@ -658,7 +658,7 @@ issues.new_label_placeholder=Labelname
issues.new_label_desc_placeholder=Beschreibung
issues.create_label=Label erstellen
issues.label_templates.title=Lade vordefinierte Label
issues.label_templates.info=Es existieren noch keine Labels. Erstelle ein neues Label ("Neues Label") oder verwende das Standard Label-Set:
issues.label_templates.info=Es existieren noch keine Label. Erstelle ein neues Label („Neues Label“) oder verwende das Standard-Label-Set:
issues.label_templates.helper=Wähle ein Label
issues.label_templates.use=Label-Set verwenden
issues.label_templates.fail_to_load_file=Fehler beim Laden der Label Template Datei '%s': %v
@ -676,7 +676,7 @@ issues.delete_branch_at=`löschte die Branch <b>%s</b> %s`
issues.open_tab=%d offen
issues.close_tab=%d geschlossen
issues.filter_label=Label
issues.filter_label_no_select=Alle Labels
issues.filter_label_no_select=Alle Label
issues.filter_milestone=Meilenstein
issues.filter_milestone_no_select=Alle Meilensteine
issues.filter_assignee=Zuständig
@ -1085,16 +1085,16 @@ settings.protect_whitelist_search_users=Benutzer suchen…
settings.protect_whitelist_teams=Teams, die pushen dürfen:
settings.protect_whitelist_search_teams=Suche nach Teams…
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_teams=Teams, die mergen dürfen:
settings.add_protected_branch=Schutz aktivieren
settings.delete_protected_branch=Schutz deaktivieren
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.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.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.no_protected_branch=Es gibt keine geschützten Branches.
@ -1167,6 +1167,8 @@ branch.protected_deletion_failed=Branch „%s“ ist geschützt und kann nicht g
topic.manage_topics=Themen verwalten
topic.done=Fertig
topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen
topic.format_prompt=Themen müssen mit einem Buchstaben oder einer Zahl beginnen. Sie können Bindestriche (-) enthalten und dürfen nicht länger als 35 Zeichen sein
[org]
org_name_holder=Name der Organisation
@ -1238,7 +1240,7 @@ teams.update_settings=Einstellungen aktualisieren
teams.delete_team=Team löschen
teams.add_team_member=Teammitglied hinzufügen
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.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.

View File

@ -1167,6 +1167,8 @@ branch.protected_deletion_failed = Branch '%s' is protected. It cannot be delete
topic.manage_topics = Manage Topics
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_name_holder = Organization Name

View File

@ -54,13 +54,14 @@ password=Parole
db_name=Datu bāzes nosaukums
path=Ceļš
repo_path=Repozitoriju glabāšanas vieta
log_root_path=Žurnalizēšanas direktorija
repo_path=Repozitoriju glabāšanas ceļš
log_root_path=Žurnalizēšanas ceļš
optional_title=Neobligātie iestatījumi
smtp_host=SMTP resursdators
federated_avatar_lookup_popup=Iespējot apvienoto profila bilžu meklētāju, lai izmantotu atvērtā koda apvienoto servisu balstītu uz libravatar.
openid_signin=Iespējot OpenID autorizāciju
openid_signin_popup=Iespējot lietotāju autorizāciju ar OpenID.
enable_captcha_popup=Lietotājam reģistrējoties, pieprasīt ievadīt drošības kodu.
admin_password=Parole
confirm_password=Apstipriniet paroli

View File

@ -1167,6 +1167,8 @@ branch.protected_deletion_failed=A branch '%s' está protegida. Ela não pode se
topic.manage_topics=Gerenciar Tópicos
topic.done=Feito
topic.count_prompt=Você não pode selecionar mais de 25 tópicos
topic.format_prompt=Tópicos devem começar com uma letra ou um número, podem incluir hífens (-) e não devem ter mais de 35 caracteres
[org]
org_name_holder=Nome da organização

View File

@ -1167,6 +1167,8 @@ branch.protected_deletion_failed=Ветка '%s' защищена. Её нель
topic.manage_topics=Редактировать тематические метки
topic.done=Сохранить
topic.count_prompt=Вы не можете выбрать более 25 тем
topic.format_prompt=Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов
[org]
org_name_holder=Название организации

View File

@ -1,4 +1,4 @@
app_desc=Зручний сервіс, власного Git хостінгу
app_desc=Зручний сервіс, власного Git хостингу
home=Головна
dashboard=Панель управління
@ -40,6 +40,8 @@ u2f_unsupported_browser=Ваш браузер не підтримує U2F клю
u2f_error_1=Сталася невідома помилка. Спробуйте ще раз.
u2f_error_2=Переконайтеся, що ви використовуєте зашифроване з'єднання (https://) та відвідуєте правильну URL-адресу.
u2f_error_3=Сервер не може обробити, ваш запит.
u2f_error_4=Представлений ключ не дає право на цей запит. Якщо спробуєте зареєструвати його, переконайтеся, що ключ ще не зареєстровано.
u2f_error_5=Таймаут досягнуто до того, як ваш ключ можна буде прочитати. Перезавантажте, щоб повторити спробу.
u2f_reload=Оновити
repository=Репозиторій
@ -73,7 +75,7 @@ cancel=Відмінити
[install]
install=Встановлення
title=Початкова конфігурація
docker_helper=Якщо ви запускаєте Gitea всередині Docker, будь ласка уважно прочитайте <a target="_blank" rel="noopener" href="%s">документацію</a> перед тим, як що-небудь змінити на цій сторінці.
docker_helper=Якщо ви запускаєте Gitea всередині Docker, будь ласка уважно прочитайте <a target="_blank" rel="noopener" href="%s">документацію</a> перед тим, як щось змінити на цій сторінці.
requite_db_desc=Gitea потребує MySQL, PostgreSQL, MSSQL, SQLite3 або TiDB.
db_title=Налаштування бази даних
db_type=Тип бази даних
@ -105,6 +107,7 @@ ssh_port_helper=Номер порту, який використовує SSH с
http_port=Gitea HTTP порт
http_port_helper=Номер порту, який буде прослуховуватися Giteas веб-сервером.
app_url=Базова URL-адреса Gitea
app_url_helper=Базова адреса для HTTP(S) клонування через URL та повідомлень електронної пошти.
log_root_path=Шлях до лог файлу
log_root_path_helper=Файли журналу будуть записані в цей каталог.
@ -112,6 +115,7 @@ optional_title=Додаткові налаштування
email_title=Налаштування Email
smtp_host=SMTP хост
smtp_from=Відправляти Email від імені
smtp_from_helper=Електронна пошта для використання в Gіtea. Введіть звичайну електронну адресу або використовуйте формат: "Ім'я" <email@example.com>.
mailer_user=SMTP Ім'я кристувача
mailer_password=SMTP Пароль
register_confirm=Потрібно підтвердити електронну пошту для реєстрації
@ -125,9 +129,11 @@ federated_avatar_lookup=Увімкнути федеративні аватари
federated_avatar_lookup_popup=Увімкнути зовнішний Аватар за допомогою Libravatar.
disable_registration=Вимкнути самостійну реєстрацію
disable_registration_popup=Вимкнути самостійну реєстрацію користувачів, тільки адміністратор може створювати нові облікові записи.
allow_only_external_registration_popup=Включити реєстрацію тільки через зовнішні сервіси.
openid_signin=Увімкнути реєстрацію за допомогою OpenID
openid_signin_popup=Увімкнути вхід за допомогою OpenID.
openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID
openid_signup_popup=Увімкнути самореєстрацію користувачів на основі OpenID.
enable_captcha=Увімкнути CAPTCHA
enable_captcha_popup=Вимагати перевірку CAPTCHA при самостійній реєстрації користувача.
require_sign_in_view=Вимагати авторизації для перегляду сторінок
@ -140,7 +146,9 @@ confirm_password=Підтвердження пароля
admin_email=Адреса електронної пошти
install_btn_confirm=Встановлення Gitea
test_git_failed=Не в змозі перевірити 'git' команду: %v
sqlite3_not_available=Ця версія Gitea не підтримує SQLite3. Будь ласка, завантажте офіційну бінарну версію з %s (не версію gobuild).
invalid_db_setting=Налаштування бази даних є некоректними: %v
invalid_repo_path=Помилковий шлях до кореня репозиторію: %v
save_config_failed=Не в змозі зберегти конфігурацію: %v
invalid_admin_setting=Неприпустимі налаштування облікового запису адміністратора: %v
install_success=Ласкаво просимо! Дякуємо вам за вибір Gitea. Розважайтеся, і будьте обережні!
@ -152,6 +160,7 @@ default_allow_create_organization_popup=Дозволити новим облік
default_enable_timetracking=Увімкнути відстеження часу за замовчуванням
default_enable_timetracking_popup=Включити відстеження часу для нових репозиторіїв за замовчуванням.
no_reply_address=Прихований поштовий домен
no_reply_address_helper=Доменне ім'я для користувачів із прихованою електронною адресою. Наприклад, ім'я користувача 'joe' буде входити в Git як 'joe@noreply.example.org', якщо для прихованого домену електронної пошти встановлено 'noreply.example.org'.
[home]
uname_holder=Ім'я користувача або Ел. пошта
@ -176,11 +185,13 @@ code=Код
repo_no_results=Відповідних репозиторіїв не знайдено.
user_no_results=Відповідних користувачів не знайдено.
org_no_results=Відповідних організацій не знайдено.
code_no_results=Відповідний пошуковому запитанню код не знайдено.
code_search_results=Результати пошуку '%s'
[auth]
create_new_account=Реєстрація облікового запису
register_helper_msg=Вже зареєстровані? Увійдіть зараз!
social_register_helper_msg=Вже є аккаунт? Зв'яжіть його зараз!
disable_register_prompt=Вибачте, можливість реєстрації відключена. Будь ласка, зв'яжіться з адміністратором сайту.
disable_register_mail=Підтвердження реєстрації електронною поштою вимкнено.
remember_me=Запам'ятати мене
@ -200,6 +211,7 @@ send_reset_mail=Натисніть сюди, щоб відправити лис
reset_password=Скинути пароль
invalid_code=Цей код підтвердження недійсний або закінчився.
reset_password_helper=Натисніть тут для скидання пароля
password_too_short=Довжина пароля не може бути меншою за %d символів.
non_local_account=Нелокальні акаунти не можуть змінити пароль через Gitea.
verify=Підтвердити
scratch_code=Одноразовий пароль
@ -211,7 +223,10 @@ login_userpass=Увійти
login_openid=OpenID
openid_connect_submit=Під’єднатися
openid_connect_title=Підключитися до існуючого облікового запису
openid_connect_desc=Вибраний OpenID URI невідомий. Пов'яжіть його з новим обліковим записом тут.
openid_register_title=Створити новий обліковий запис
openid_register_desc=Вибраний OpenID URI невідомий. Пов'яжіть йогоз новим обліковим записом тут.
openid_signin_desc=Введіть свій ідентифікатор OpenID. Наприклад: https://anne.me, bob.openid.org.cn або gnusocial.net/carry.
disable_forgot_password_mail=На жаль скидання пароля відключене. Будь ласка, зв'яжіться з адміністратором сайту.
[mail]
@ -247,6 +262,8 @@ TreeName=Шлях до файлу
Content=Зміст
require_error=` не може бути пустим.`
alpha_dash_error=` повинен містити тільки літерно-цифрові символи, дефіс ('-') та підкреслення ('_'). `
alpha_dash_dot_error=` повинен містити тільки літерно-цифрові символи, дефіс ('-') , підкреслення ('_') та точки ('.'). `
git_ref_name_error=` повинен бути правильним посилальним ім'ям Git.`
size_error=` повинен бути розмір %s.`
min_size_error=` повинен бути принаймні %s символів.`
@ -262,6 +279,7 @@ username_been_taken=Ім'я користувача вже зайнято.
repo_name_been_taken=Ім'я репозіторію вже використовується.
org_name_been_taken=Назва організації вже зайнято.
team_name_been_taken=Назва команди вже зайнято.
team_no_units_error=Дозволити доступ до принаймні одного розділу репозитарію.
email_been_used=Ця електронна адреса вже використовується.
openid_been_used=OpenID адреса '%s' вже використовується.
username_password_incorrect=Неправильне ім'я користувача або пароль.
@ -269,12 +287,17 @@ enterred_invalid_repo_name=Невірно введено ім'я репозит
enterred_invalid_owner_name=Ім'я нового власника не є дійсним.
enterred_invalid_password=Введений вами пароль некоректний.
user_not_exist=Даний користувач не існує.
last_org_owner=Ви не можете вилучити останнього користувача з команди 'власники'. У кожній команді має бути хоча б один власник.
cannot_add_org_to_team=Організацію неможливо додати як учасника команди.
invalid_ssh_key=Неможливо перевірити ваш SSH ключ: %s
invalid_gpg_key=Неможливо перевірити ваш GPG ключ: %s
unable_verify_ssh_key=Не вдається підтвердити ключ SSH; подвійно перевірте його на наявність похибки.
auth_failed=Помилка автентифікації: %v
still_own_repo=Ваш обліковий запис володіє одним або декількома репозиторіями; видаліть або перенесіть їх в першу чергу.
still_has_org=Ваш обліковий запис є учасником однієї чи декількох організацій; вийдіть з них в першу чергу.
org_still_own_repo=Ця організація як і раніше володіє одним або декількома репозиторіями; спочатку видаліть або перенесіть їх.
target_branch_not_exist=Цільової гілки не існує.
@ -290,6 +313,7 @@ follow=Підписатися
unfollow=Відписатися
form.name_reserved=Ім'я користувача "%s" зарезервовано.
form.name_pattern_not_allowed=Шаблон '%s' не дозволено в імені користувача.
[settings]
profile=Профіль
@ -311,6 +335,7 @@ u2f=Ключі безпеки
public_profile=Загальнодоступний профіль
profile_desc=Ваша адреса електронної пошти використовуватиметься для сповіщення та інших операцій.
password_username_disabled=Нелокальним користувачам заборонено змінювати ім'я користувача. Щоб отримати докладнішу інформацію, зв'яжіться з адміністратором сайту.
full_name=Повне ім'я
website=Веб-сайт
location=Місцезнаходження
@ -340,28 +365,37 @@ password_change_disabled=Нелокальні акаунти не можуть
emails=Адреса електронної пошти
manage_emails=Керування адресами ел. пошти
manage_openid=Керування OpenID
email_desc=Ваша основна адреса електронної пошти використовуватиметься для сповіщення та інших операцій.
primary=Основний
primary_email=Зробити основним
delete_email=Видалити
email_deletion=Видалити адресу електронної пошти
email_deletion_success=Адресу електронної пошти було видалено.
openid_deletion=Видалити адресу OpenID
openid_deletion_success=Адреса OpenID була видалена.
add_new_email=Додати нову адресу електронної пошти
add_new_openid=Додати новий OpenID URI
add_email=Додати адресу електронної пошти
add_openid=Додати OpenID URI
add_email_confirmation_sent=Електронний лист із підтвердженням було відправлено на '%s', будь ласка, перевірте вашу поштову скриньку протягом наступних %s, щоб підтвердити адресу.
add_email_success=Додано нову адресу електронної пошти.
add_openid_success=Нова адреса OpenID була додана.
keep_email_private=Приховати адресу електронної пошти
keep_email_private_popup=Вашу адресу електронної пошти буде приховано від інших користувачів.
manage_ssh_keys=Керувати SSH ключами
manage_gpg_keys=Керувати GPG ключами
add_key=Додати ключ
ssh_desc=Ці відкриті SSH-ключі пов'язані з вашим обліковим записом. Відповідні приватні ключі дозволяють отримати повний доступ до ваших репозиторіїв.
gpg_desc=Ці публічні ключі GPG пов'язані з вашим обліковим записом. Тримайте свої приватні ключі в безпеці, оскільки вони дозволяють здійснювати перевірку комітів.
ssh_helper=<strong>Потрібна допомога?</strong> Дивіться гід на GitHub з <a href="%s"> генерації ключів SSH</a> або виправлення <a href="%s">типових неполадок SSH</a>.
gpg_helper=<strong> Потрібна допомога? </strong> Перегляньте посібник GitHub <a href="%s"> про GPG </a>.
add_new_key=Додати SSH ключ
add_new_gpg_key=Додати GPG ключ
ssh_key_been_used=Цей ключ SSH вже додано до вашого облікового запису.
ssh_key_name_used=Ключ SSH з таким самим ім'ям вже додано до вашого облікового запису.
gpg_key_id_used=Публічний ключ GPG з таким самим ідентифікатором вже існує.
subkeys=Підключі
key_id=ID ключа
key_name=Ім'я ключа
@ -371,6 +405,7 @@ add_gpg_key_success=GPG ключ '%s' додано.
delete_key=Видалити
ssh_key_deletion=Видалити SSH ключ
gpg_key_deletion=Видалити GPG ключ
ssh_key_deletion_desc=Видалення ключа SSH скасовує доступ до вашого облікового запису. Продовжити?
gpg_key_deletion_desc=Видалення GPG ключа скасовує перевірку підписаних ним комітів. Продовжити?
ssh_key_deletion_success=SSH було видалено.
gpg_key_deletion_success=GPG було видалено.
@ -401,6 +436,7 @@ access_token_deletion=Видалити токен доступу
twofa_desc=Двофакторна автентифікація підвищує безпеку вашого облікового запису.
twofa_is_enrolled=Ваш обліковий запис на даний час <strong>використовує</strong> двофакторну автентифікацію.
twofa_not_enrolled=Ваш обліковий запис наразі не використовує двофакторну автентифікаціїю.
twofa_disable=Вимкнути двофакторну автентифікацію
twofa_scratch_token_regenerate=Перестворити токен одноразового пароля
twofa_enroll=Увімкнути двофакторну автентифікацію
@ -560,8 +596,10 @@ commits.date=Дата
commits.older=Давніше
commits.newer=Новіше
commits.signed_by=Підписано
commits.gpg_key_id=Ідентифікатор GPG ключа
ext_issues=Зов. Проблеми
ext_issues.desc=Посилання на зовнішню систему відстеження проблем.
issues.new=Нова проблема
issues.new.labels=Мітки
@ -583,6 +621,7 @@ issues.new_label_desc_placeholder=Опис
issues.create_label=Створити мітку
issues.label_templates.title=Завантажити визначений набір міток
issues.label_templates.helper=Оберіть набір міток
issues.label_templates.use=Використовувати набір міток
issues.label_templates.fail_to_load_file=Не вдалося завантажити файл шаблона мітки '%s': %v
issues.add_label_at=додав(ла) мітку <div class="ui label" style="color: %s\; background-color: %s">%s</div> %s
issues.add_milestone_at=`додав(ла) до <b>%s</b> етапу %s`
@ -590,6 +629,7 @@ issues.deleted_milestone=`(видалено)`
issues.add_assignee_at=`був призначений <b>%s</b> %s`
issues.remove_assignee_at=`видалили із призначених %s`
issues.change_title_at=`змінив(ла) заголовок з <b>%s</b> на <b>%s</b> %s`
issues.delete_branch_at=`видалена гілка <b>%s</b> %s`
issues.open_tab=%d відкрито
issues.close_tab=%d закрито
issues.filter_label=Мітка
@ -671,9 +711,11 @@ issues.start_tracking=Почати відстеження часу
issues.start_tracking_history=`почав працювати %s`
issues.tracking_already_started=`Ви вже почали відстежувати час для цієї <a href="%s"> проблеми</a>!`
issues.stop_tracking=Стоп
issues.stop_tracking_history=`перестав(-ла) працювати %s`
issues.add_time=Вручну додати час
issues.add_time_short=Додати час
issues.add_time_cancel=Відмінити
issues.add_time_history=`додав(-ла) витрачений час %s`
issues.add_time_hours=Години
issues.add_time_minutes=Хвилини
issues.add_time_sum_to_small=Час не введено.
@ -682,6 +724,8 @@ issues.cancel_tracking_history=`скасував відстеження часу
issues.time_spent_total=Загальний витрачений час
issues.time_spent_from_all_authors=`Загальний витрачений час: %s`
issues.due_date=Дата завершення
issues.invalid_due_date_format=Дата закінчення має бути в форматі 'ррр-мм-дд'.
issues.error_modifying_due_date=Не вдалося змінити дату завершення.
issues.due_date_form=рррр-мм-дд
issues.due_date_form_add=Додати дату завершення
issues.due_date_form_update=Оновити дату завершення
@ -731,10 +775,13 @@ milestones.edit=Редагувати етап
milestones.cancel=Відмінити
milestones.modify=Оновити етап
milestones.deletion=Видалити етап
milestones.filter_sort.closest_due_date=Найближче за датою
milestones.filter_sort.furthest_due_date=Далі за датою
milestones.filter_sort.most_issues=Найбільш проблем
milestones.filter_sort.least_issues=Найменш проблем
ext_wiki=Зов. Вікі
ext_wiki.desc=Посилання на зовнішню вікі.
wiki=Вікі
wiki.welcome=Ласкаво просимо до Вікі.
@ -784,6 +831,7 @@ activity.closed_issue_label=Закрито
activity.new_issues_count_1=Нова Проблема
activity.new_issues_count_n=%d Проблем
activity.new_issue_label=Відкриті
activity.title.unresolved_conv_1=%d Незавершене обговорення
activity.unresolved_conv_label=Відкрити
activity.title.releases_1=%d Реліз
activity.title.releases_n=%d Релізів
@ -1107,7 +1155,9 @@ users.admin=Адміністратор
users.repos=Репозиторії
users.created=Створено
users.last_login=Останній вхід
users.never_login=Ніколи не входив
users.send_register_notify=Надіслати повідомлення про реєстрацію користувача
users.new_success=Обліковий запис '%s' створений.
users.edit=Редагувати
users.auth_source=Джерело автентифікації
users.local=Локальні
@ -1243,7 +1293,7 @@ config.default_keep_email_private=Приховати адресу електро
config.default_allow_create_organization=Дозволити створення організацій за замовчуванням
config.enable_timetracking=Увімкнути відстеження часу
config.default_enable_timetracking=Увімкнути відстеження часу за замовчуванням
config.no_reply_address=Прихований домен е-пошти
config.no_reply_address=Прихований домен електронної пошти
config.webhook_config=Конфігурація web-хуків
config.queue_length=Довжина черги

View File

@ -2336,8 +2336,10 @@ function initTopicbar() {
}).done(function() {
editDiv.hide();
viewDiv.show();
}).fail(function(xhr) {
alert(xhr.responseJSON.message)
})
})
});
$('#topic_edit .dropdown').dropdown({
allowAdditions: true,

View File

@ -182,7 +182,14 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
Authorize: models.ParseAccessMode(form.Permission),
}
if t.Authorize < models.AccessModeAdmin {
t.UnitTypes = form.Units
var units = make([]*models.TeamUnit, 0, len(form.Units))
for _, tp := range form.Units {
units = append(units, &models.TeamUnit{
OrgID: ctx.Org.Organization.ID,
Type: tp,
})
}
t.Units = units
}
ctx.Data["Team"] = t
@ -264,9 +271,17 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
}
t.Description = form.Description
if t.Authorize < models.AccessModeAdmin {
t.UnitTypes = form.Units
var units = make([]models.TeamUnit, 0, len(form.Units))
for _, tp := range form.Units {
units = append(units, models.TeamUnit{
OrgID: t.OrgID,
TeamID: t.ID,
Type: tp,
})
}
models.UpdateTeamUnits(t, units)
} else {
t.UnitTypes = nil
models.UpdateTeamUnits(t, nil)
}
if ctx.HasError() {

View File

@ -12,8 +12,8 @@ import (
"code.gitea.io/gitea/modules/log"
)
// TopicPost response for creating repository
func TopicPost(ctx *context.Context) {
// TopicsPost response for creating repository
func TopicsPost(ctx *context.Context) {
if ctx.User == nil {
ctx.JSON(403, map[string]interface{}{
"message": "Only owners could change the topics.",
@ -27,6 +27,37 @@ func TopicPost(ctx *context.Context) {
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...)
if err != nil {
log.Error(2, "SaveTopics failed: %v", err)

View File

@ -210,7 +210,7 @@ func GogsHooksNewPost(ctx *context.Context, form auth.NewGogshookForm) {
Secret: form.Secret,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
HookTaskType: models.GITEA,
HookTaskType: models.GOGS,
OrgID: orCtx.OrgID,
}
if err := w.UpdateEvent(); err != nil {

View File

@ -486,7 +486,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/:id", repo.WebHooksEdit)
m.Post("/:id/test", repo.TestWebhook)
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("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
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))
m.Group("/:username/:reponame", func() {
m.Post("/topics", repo.TopicPost)
m.Post("/topics", repo.TopicsPost)
}, context.RepoAssignment(), reqRepoAdmin)
m.Group("/:username/:reponame", func() {

View File

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/go-macaron/captcha"
"github.com/markbates/goth"
@ -474,7 +475,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
return setting.AppSubURL + "/"
}
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
if obeyRedirect {
ctx.RedirectToFirst(redirectTo)

View File

@ -203,7 +203,11 @@ func Issues(ctx *context.Context) {
return
}
} else {
userRepoIDs, err = ctxUser.GetAccessRepoIDs()
unitType := models.UnitTypeIssues
if isPullList {
unitType = models.UnitTypePullRequests
}
userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType)
if err != nil {
ctx.ServerError("ctxUser.GetAccessRepoIDs", err)
return

View File

@ -26,8 +26,8 @@ func TestIssues(t *testing.T) {
Issues(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"])
assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"])
assert.EqualValues(t, true, ctx.Data["IsShowClosed"])
assert.Len(t, ctx.Data["Issues"], 1)
assert.Len(t, ctx.Data["Repos"], 2)
assert.Len(t, ctx.Data["Repos"], 1)
}

View File

@ -57,7 +57,7 @@
{{range $t, $unit := $.Units}}
<div class="field">
<div class="ui toggle checkbox">
<input type="checkbox" class="hidden" name="units" value="{{$unit.Type}}"{{if $.Team.UnitEnabled $unit.Type}} checked{{end}}>
<input type="checkbox" class="hidden" name="units" value="{{$unit.Type}}"{{if or (eq $.Team.ID 0) ($.Team.UnitEnabled $unit.Type)}} checked{{end}}>
<label>{{$.i18n.Tr $unit.NameKey}}</label>
<span class="help">{{$.i18n.Tr $unit.DescKey}}</span>
</div>