Merge branch 'master' into master

This commit is contained in:
kolaente 2017-12-06 18:50:36 +01:00 committed by GitHub
commit cdd322f2e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 530 additions and 35 deletions

View File

@ -11,7 +11,7 @@
- [Translation](#translation) - [Translation](#translation)
- [Code review](#code-review) - [Code review](#code-review)
- [Styleguide](#styleguide) - [Styleguide](#styleguide)
- [Sign your work](#sign-your-work) - [Sign-off your work](#sign-off-your-work)
- [Release Cycle](#release-cycle) - [Release Cycle](#release-cycle)
- [Maintainers](#maintainers) - [Maintainers](#maintainers)
- [Owners](#owners) - [Owners](#owners)

View File

@ -39,8 +39,8 @@ services:
volumes: volumes:
- ./gitea:/data - ./gitea:/data
ports: ports:
- 3000:3000 - "3000:3000"
- 222:22 - "222:22"
``` ```
## Custom port ## Custom port
@ -63,10 +63,10 @@ services:
volumes: volumes:
- ./gitea:/data - ./gitea:/data
ports: ports:
- - 3000:3000 - - "3000:3000"
- - 222:22 - - "222:22"
+ - 8080:3000 + - "8080:3000"
+ - 2221:22 + - "2221:22"
``` ```
## MySQL database ## MySQL database
@ -89,8 +89,8 @@ services:
volumes: volumes:
- ./gitea:/data - ./gitea:/data
ports: ports:
- 3000:3000 - "3000:3000"
- 222:22 - "222:22"
+ depends_on: + depends_on:
+ - db + - db
+ +
@ -128,8 +128,8 @@ services:
volumes: volumes:
- ./gitea:/data - ./gitea:/data
ports: ports:
- 3000:3000 - "3000:3000"
- 222:22 - "222:22"
+ depends_on: + depends_on:
+ - db + - db
+ +
@ -171,8 +171,8 @@ services:
- - ./gitea:/data - - ./gitea:/data
+ - gitea:/data + - gitea:/data
ports: ports:
- 3000:3000 - "3000:3000"
- 222:22 - "222:22"
``` ```
If you are using MySQL or PostgreSQL it's up to you to create named volumes for these containers as well. If you are using MySQL or PostgreSQL it's up to you to create named volumes for these containers as well.

View File

@ -0,0 +1,73 @@
// 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 (
"fmt"
"net/http"
"testing"
"code.gitea.io/gitea/models"
api "code.gitea.io/sdk/gitea"
)
func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
prepareTestEnv(t)
// user1 is an admin user
session := loginUser(t, "user1")
keyOwner := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User)
urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", keyOwner.Name)
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n",
"title": "test-key",
})
resp := session.MakeRequest(t, req, http.StatusCreated)
var newPublicKey api.PublicKey
DecodeJSON(t, resp, &newPublicKey)
models.AssertExistsAndLoadBean(t, &models.PublicKey{
ID: newPublicKey.ID,
Name: newPublicKey.Title,
Content: newPublicKey.Key,
Fingerprint: newPublicKey.Fingerprint,
OwnerID: keyOwner.ID,
})
req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d",
keyOwner.Name, newPublicKey.ID)
session.MakeRequest(t, req, http.StatusNoContent)
models.AssertNotExistsBean(t, &models.PublicKey{ID: newPublicKey.ID})
}
func TestAPIAdminDeleteMissingSSHKey(t *testing.T) {
prepareTestEnv(t)
// user1 is an admin user
session := loginUser(t, "user1")
req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d", models.NonexistentID)
session.MakeRequest(t, req, http.StatusNotFound)
}
func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) {
prepareTestEnv(t)
adminUsername := "user1"
normalUsername := "user2"
session := loginUser(t, adminUsername)
urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", adminUsername)
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n",
"title": "test-key",
})
resp := session.MakeRequest(t, req, http.StatusCreated)
var newPublicKey api.PublicKey
DecodeJSON(t, resp, &newPublicKey)
session = loginUser(t, normalUsername)
req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d",
adminUsername, newPublicKey.ID)
session.MakeRequest(t, req, http.StatusForbidden)
}

View File

@ -9,10 +9,10 @@ import (
"fmt" "fmt"
"time" "time"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/builder" "github.com/go-xorm/builder"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
"code.gitea.io/gitea/modules/setting"
) )
// Reaction represents a reactions on issues and comments. // Reaction represents a reactions on issues and comments.

View File

@ -0,0 +1,166 @@
// 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 models
import (
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func addReaction(t *testing.T, doer *User, issue *Issue, comment *Comment, content string) {
var reaction *Reaction
var err error
if comment == nil {
reaction, err = CreateIssueReaction(doer, issue, content)
} else {
reaction, err = CreateCommentReaction(doer, issue, comment, content)
}
assert.NoError(t, err)
assert.NotNil(t, reaction)
}
func TestIssueAddReaction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
addReaction(t, user1, issue1, nil, "heart")
AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID})
}
func TestIssueAddDuplicateReaction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
addReaction(t, user1, issue1, nil, "heart")
reaction, err := CreateReaction(&ReactionOptions{
Doer: user1,
Issue: issue1,
Type: "heart",
})
assert.Error(t, err)
assert.Nil(t, reaction)
AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID})
}
func TestIssueDeleteReaction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
addReaction(t, user1, issue1, nil, "heart")
err := DeleteIssueReaction(user1, issue1, "heart")
assert.NoError(t, err)
AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID})
}
func TestIssueReactionCount(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
setting.UI.ReactionMaxUserNum = 2
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
ghost := NewGhostUser()
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
addReaction(t, user1, issue1, nil, "heart")
addReaction(t, user2, issue1, nil, "heart")
addReaction(t, user3, issue1, nil, "heart")
addReaction(t, user3, issue1, nil, "+1")
addReaction(t, user4, issue1, nil, "+1")
addReaction(t, user4, issue1, nil, "heart")
addReaction(t, ghost, issue1, nil, "-1")
err := issue1.loadReactions(x)
assert.NoError(t, err)
assert.Len(t, issue1.Reactions, 7)
reactions := issue1.Reactions.GroupByType()
assert.Len(t, reactions["heart"], 4)
assert.Equal(t, 2, reactions["heart"].GetMoreUserCount())
assert.Equal(t, "User One, User Two", reactions["heart"].GetFirstUsers())
assert.True(t, reactions["heart"].HasUser(1))
assert.False(t, reactions["heart"].HasUser(5))
assert.False(t, reactions["heart"].HasUser(0))
assert.Len(t, reactions["+1"], 2)
assert.Equal(t, 0, reactions["+1"].GetMoreUserCount())
assert.Len(t, reactions["-1"], 1)
}
func TestIssueCommentAddReaction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
addReaction(t, user1, issue1, comment1, "heart")
AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID})
}
func TestIssueCommentDeleteReaction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
ghost := NewGhostUser()
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
addReaction(t, user1, issue1, comment1, "heart")
addReaction(t, user2, issue1, comment1, "heart")
addReaction(t, user3, issue1, comment1, "heart")
addReaction(t, user4, issue1, comment1, "+1")
addReaction(t, ghost, issue1, comment1, "heart")
err := comment1.LoadReactions()
assert.NoError(t, err)
assert.Len(t, comment1.Reactions, 5)
reactions := comment1.Reactions.GroupByType()
assert.Len(t, reactions["heart"], 4)
assert.Len(t, reactions["+1"], 1)
}
func TestIssueCommentReactionCount(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
addReaction(t, user1, issue1, comment1, "heart")
DeleteCommentReaction(user1, issue1, comment1, "heart")
AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID})
}

View File

@ -506,10 +506,7 @@ func deletePublicKeys(e *xorm.Session, keyIDs ...int64) error {
func DeletePublicKey(doer *User, id int64) (err error) { func DeletePublicKey(doer *User, id int64) (err error) {
key, err := GetPublicKeyByID(id) key, err := GetPublicKeyByID(id)
if err != nil { if err != nil {
if IsErrKeyNotExist(err) { return err
return nil
}
return fmt.Errorf("GetPublicKeyByID: %v", err)
} }
// Check if user has access to delete this key. // Check if user has access to delete this key.

View File

@ -1401,7 +1401,7 @@ func newSessionService() {
SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory", SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory",
[]string{"memory", "file", "redis", "mysql"}) []string{"memory", "file", "redis", "mysql"})
SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ") SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
if !filepath.IsAbs(SessionConfig.ProviderConfig) { if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig) SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
} }
SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gitea") SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gitea")

View File

@ -489,6 +489,8 @@ mirror_last_synced=Utoljára szinkronizálva
watchers=Figyelők watchers=Figyelők
stargazers=Csillagozók stargazers=Csillagozók
forks=Másolások forks=Másolások
pick_reaction=Válasszon reakciót
reactions_more=és további %d
form.reach_limit_of_creation=Elérte a tárolók maximális számát (%d). form.reach_limit_of_creation=Elérte a tárolók maximális számát (%d).
form.name_reserved=A tárolónév ('%s') a rendszernek van fenntartva. form.name_reserved=A tárolónév ('%s') a rendszernek van fenntartva.
@ -539,6 +541,7 @@ pulls=Egyesítési kérések
labels=Címkék labels=Címkék
milestones=Mérföldkövek milestones=Mérföldkövek
commits=Commit-ok commits=Commit-ok
commit=Commit
releases=Kiadások releases=Kiadások
file_raw=Nyers file_raw=Nyers
file_history=Előzmények file_history=Előzmények

File diff suppressed because one or more lines are too long

View File

@ -151,7 +151,13 @@ function initReactionSelector(parent) {
react.remove(); react.remove();
} }
if (!resp.empty) { if (!resp.empty) {
react = $('<div class="ui attached segment reactions"></div>').appendTo(content); react = $('<div class="ui attached segment reactions"></div>');
var attachments = content.find('.segment.bottom:first');
if (attachments.length > 0) {
react.insertBefore(attachments);
} else {
react.appendTo(content);
}
react.html(resp.html); react.html(resp.html);
var hasEmoji = react.find('.has-emoji'); var hasEmoji = react.find('.has-emoji');
for (var i = 0; i < hasEmoji.length; i++) { for (var i = 0; i < hasEmoji.length; i++) {

View File

@ -146,11 +146,18 @@ pre, code {
} }
} }
&.menu, &.menu,
&.vertical.menu, &.vertical.menu,
&.segment { &.segment {
box-shadow: none; box-shadow: none;
} }
/* overide semantic selector '.ui.menu:not(.vertical) .item > .button' */
.menu:not(.vertical) .item .button {
padding-bottom: .78571429em;
padding-top: .78571429em;
font-size: 1em;
}
.text { .text {
&.red { &.red {

File diff suppressed because it is too large Load Diff

View File

@ -236,3 +236,48 @@ func CreatePublicKey(ctx *context.APIContext, form api.CreateKeyOption) {
} }
user.CreateUserPublicKey(ctx, form, u.ID) user.CreateUserPublicKey(ctx, form, u.ID)
} }
// DeleteUserPublicKey api for deleting a user's public key
func DeleteUserPublicKey(ctx *context.APIContext) {
// swagger:operation DELETE /admin/users/{username}/keys/{id} admin adminDeleteUserPublicKey
// ---
// summary: Delete a user's public key
// produces:
// - application/json
// parameters:
// - name: username
// in: path
// description: username of user
// type: string
// required: true
// - name: id
// in: path
// description: id of the key to delete
// type: integer
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
u := user.GetUserByParams(ctx)
if ctx.Written() {
return
}
if err := models.DeletePublicKey(u, ctx.ParamsInt64(":id")); err != nil {
if models.IsErrKeyNotExist(err) {
ctx.Status(404)
} else if models.IsErrKeyAccessDenied(err) {
ctx.Error(403, "", "You do not have access to this key")
} else {
ctx.Error(500, "DeleteUserPublicKey", err)
}
return
}
log.Trace("Key deleted by admin(%s): %s", ctx.User.Name, u.Name)
ctx.Status(204)
}

View File

@ -542,7 +542,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/:username", func() { m.Group("/:username", func() {
m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser). m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
Delete(admin.DeleteUser) Delete(admin.DeleteUser)
m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey) m.Group("/keys", func() {
m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
m.Delete("/:id", admin.DeleteUserPublicKey)
})
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
}) })

View File

@ -178,8 +178,12 @@ func DeletePublicKey(ctx *context.APIContext) {
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
if err := models.DeletePublicKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { if err := models.DeletePublicKey(ctx.User, ctx.ParamsInt64(":id")); err != nil {
if models.IsErrKeyAccessDenied(err) { if models.IsErrKeyNotExist(err) {
ctx.Status(404)
} else if models.IsErrKeyAccessDenied(err) {
ctx.Error(403, "", "You do not have access to this key") ctx.Error(403, "", "You do not have access to this key")
} else { } else {
ctx.Error(500, "DeletePublicKey", err) ctx.Error(500, "DeletePublicKey", err)

View File

@ -2,7 +2,7 @@
{{range .Repos}} {{range .Repos}}
<div class="item"> <div class="item">
<div class="ui header"> <div class="ui header">
<a class="name" href="{{AppSubUrl}}/{{if .Owner}}{{.Owner.Name}}{{else if $.Org}}{{$.Org.Name}}{{else}}{{$.Owner.Name}}{{end}}/{{.Name}}">{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}</a> <a class="name" href="{{.HTMLURL}}">{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}</a>
{{if .IsPrivate}} {{if .IsPrivate}}
<span class="text gold"><i class="octicon octicon-lock"></i></span> <span class="text gold"><i class="octicon octicon-lock"></i></span>
{{else if .IsFork}} {{else if .IsFork}}