Merge branch 'master' into telegram_webhook

This commit is contained in:
techknowlogick 2018-07-21 11:05:19 -04:00 committed by GitHub
commit b6c7ce32a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 2898 additions and 1408 deletions

10
Gopkg.lock generated
View File

@ -288,17 +288,19 @@
[[projects]] [[projects]]
name = "github.com/go-xorm/builder" name = "github.com/go-xorm/builder"
packages = ["."] packages = ["."]
revision = "488224409dd8aa2ce7a5baf8d10d55764a913738" revision = "dc8bf48f58fab2b4da338ffd25191905fd741b8f"
version = "v0.3.0"
[[projects]] [[projects]]
name = "github.com/go-xorm/core" name = "github.com/go-xorm/core"
packages = ["."] packages = ["."]
revision = "cb1d0ca71f42d3ee1bf4aba7daa16099bc31a7e9" revision = "c10e21e7e1cec20e09398f2dfae385e58c8df555"
version = "v0.6.0"
[[projects]] [[projects]]
name = "github.com/go-xorm/xorm" name = "github.com/go-xorm/xorm"
packages = ["."] packages = ["."]
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03" revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -701,6 +703,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "59451a3ad1d449f75c5e9035daf542a377c5c4a397e219bebec0aa0007ab9c39" inputs-digest = "5ae18d543bbb8186589c003422b333097d67bb5fed8b4c294be70c012ccffc94"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -33,7 +33,7 @@ ignored = ["google.golang.org/appengine*"]
[[override]] [[override]]
name = "github.com/go-xorm/xorm" name = "github.com/go-xorm/xorm"
#version = "0.6.5" #version = "0.6.5"
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03" revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
[[override]] [[override]]
name = "github.com/go-sql-driver/mysql" name = "github.com/go-sql-driver/mysql"

View File

@ -316,6 +316,9 @@ DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for AllowCreateOrganization ; Default value for AllowCreateOrganization
; Every new user will have rights set to create organizations depending on this setting ; Every new user will have rights set to create organizations depending on this setting
DEFAULT_ALLOW_CREATE_ORGANIZATION = true DEFAULT_ALLOW_CREATE_ORGANIZATION = true
; Default value for EnableDependencies
; Repositories will use depencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true
; Enable Timetracking ; Enable Timetracking
ENABLE_TIMETRACKING = true ENABLE_TIMETRACKING = true
; Default value for EnableTimetracking ; Default value for EnableTimetracking

View File

@ -182,6 +182,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\] - `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha - `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha - `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha
- `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default.
## Webhook (`webhook`) ## Webhook (`webhook`)

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
// register supported doc types // register supported doc types
_ "code.gitea.io/gitea/modules/markup/csv"
_ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode" _ "code.gitea.io/gitea/modules/markup/orgmode"

View File

@ -477,6 +477,10 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
} }
if err = issue.ChangeStatus(doer, repo, true); err != nil { if err = issue.ChangeStatus(doer, repo, true); err != nil {
// Don't return an error when dependencies are open as this would let the push fail
if IsErrDependenciesLeft(err) {
return nil
}
return err return err
} }
} }

View File

@ -1259,3 +1259,88 @@ func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist) _, ok := err.(ErrU2FRegistrationNotExist)
return ok return ok
} }
// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
// | |\___ \ \___ \| | /\ ___/ | ` \ ___/| |_> > ___/| | \/ /_/ \ ___/| | \ \___| \ ___/ \___ \
// |___/____ >____ >____/ \___ >_______ /\___ > __/ \___ >___| /\____ |\___ >___| /\___ >__|\___ >____ >
// \/ \/ \/ \/ \/|__| \/ \/ \/ \/ \/ \/ \/ \/
// ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyExists checks if an error is a ErrDependencyExists.
func IsErrDependencyExists(err error) bool {
_, ok := err.(ErrDependencyExists)
return ok
}
func (err ErrDependencyExists) Error() string {
return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyNotExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyNotExists checks if an error is a ErrDependencyExists.
func IsErrDependencyNotExists(err error) bool {
_, ok := err.(ErrDependencyNotExists)
return ok
}
func (err ErrDependencyNotExists) Error() string {
return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrCircularDependency represents a "DependencyCircular" kind of error.
type ErrCircularDependency struct {
IssueID int64
DependencyID int64
}
// IsErrCircularDependency checks if an error is a ErrCircularDependency.
func IsErrCircularDependency(err error) bool {
_, ok := err.(ErrCircularDependency)
return ok
}
func (err ErrCircularDependency) Error() string {
return fmt.Sprintf("circular dependencies exists (two issues blocking each other) [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependenciesLeft represents an error where the issue you're trying to close still has dependencies left.
type ErrDependenciesLeft struct {
IssueID int64
}
// IsErrDependenciesLeft checks if an error is a ErrDependenciesLeft.
func IsErrDependenciesLeft(err error) bool {
_, ok := err.(ErrDependenciesLeft)
return ok
}
func (err ErrDependenciesLeft) Error() string {
return fmt.Sprintf("issue has open dependencies [issue id: %d]", err.IssueID)
}
// ErrUnknownDependencyType represents an error where an unknown dependency type was passed
type ErrUnknownDependencyType struct {
Type DependencyType
}
// IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
func IsErrUnknownDependencyType(err error) bool {
_, ok := err.(ErrUnknownDependencyType)
return ok
}
func (err ErrUnknownDependencyType) Error() string {
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
}

View File

@ -649,6 +649,20 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository,
if issue.IsClosed == isClosed { if issue.IsClosed == isClosed {
return nil return nil
} }
// Check for open dependencies
if isClosed && issue.Repo.IsDependenciesEnabled() {
// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
noDeps, err := IssueNoDependenciesLeft(issue)
if err != nil {
return err
}
if !noDeps {
return ErrDependenciesLeft{issue.ID}
}
}
issue.IsClosed = isClosed issue.IsClosed = isClosed
if isClosed { if isClosed {
issue.ClosedUnix = util.TimeStampNow() issue.ClosedUnix = util.TimeStampNow()
@ -1283,7 +1297,7 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) {
And("`comment`.type = ?", CommentTypeComment). And("`comment`.type = ?", CommentTypeComment).
And("`user`.is_active = ?", true). And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false). And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `comment`.poster_id"). Join("INNER", "`user`", "`user`.id = `comment`.poster_id").
Distinct("poster_id"). Distinct("poster_id").
Find(&userIDs); err != nil { Find(&userIDs); err != nil {
return nil, fmt.Errorf("get poster IDs: %v", err) return nil, fmt.Errorf("get poster IDs: %v", err)
@ -1598,3 +1612,33 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User)
return sess.Commit() return sess.Commit()
} }
// Get Blocked By Dependencies, aka all issues this issue is blocked by.
func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_id = ?", issue.ID).
Find(&issueDeps)
}
// Get Blocking Dependencies, aka all issues this issue blocks.
func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.issue_id").
Where("dependency_id = ?", issue.ID).
Find(&issueDeps)
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
func (issue *Issue) BlockedByDependencies() ([]*Issue, error) {
return issue.getBlockedByDependencies(x)
}
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks
func (issue *Issue) BlockingDependencies() ([]*Issue, error) {
return issue.getBlockingDependencies(x)
}

View File

@ -66,6 +66,10 @@ const (
CommentTypeModifiedDeadline CommentTypeModifiedDeadline
// Removed a due date // Removed a due date
CommentTypeRemovedDeadline CommentTypeRemovedDeadline
// Dependency added
CommentTypeAddDependency
//Dependency removed
CommentTypeRemoveDependency
) )
// CommentTag defines comment tag type // CommentTag defines comment tag type
@ -81,23 +85,25 @@ const (
// Comment represents a comment in commit and issue page. // Comment represents a comment in commit and issue page.
type Comment struct { type Comment struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Type CommentType Type CommentType
PosterID int64 `xorm:"INDEX"` PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"` Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"` IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"` Issue *Issue `xorm:"-"`
LabelID int64 LabelID int64
Label *Label `xorm:"-"` Label *Label `xorm:"-"`
OldMilestoneID int64 OldMilestoneID int64
MilestoneID int64 MilestoneID int64
OldMilestone *Milestone `xorm:"-"` OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"` Milestone *Milestone `xorm:"-"`
AssigneeID int64 AssigneeID int64
RemovedAssignee bool RemovedAssignee bool
Assignee *User `xorm:"-"` Assignee *User `xorm:"-"`
OldTitle string OldTitle string
NewTitle string NewTitle string
DependentIssueID int64
DependentIssue *Issue `xorm:"-"`
CommitID int64 CommitID int64
Line int64 Line int64
@ -281,6 +287,15 @@ func (c *Comment) LoadAssigneeUser() error {
return nil return nil
} }
// LoadDepIssueDetails loads Dependent Issue Details
func (c *Comment) LoadDepIssueDetails() (err error) {
if c.DependentIssueID <= 0 || c.DependentIssue != nil {
return nil
}
c.DependentIssue, err = getIssueByID(x, c.DependentIssueID)
return err
}
// MailParticipants sends new comment emails to repository watchers // MailParticipants sends new comment emails to repository watchers
// and mentioned people. // and mentioned people.
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
@ -332,22 +347,24 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
if opts.Label != nil { if opts.Label != nil {
LabelID = opts.Label.ID LabelID = opts.Label.ID
} }
comment := &Comment{ comment := &Comment{
Type: opts.Type, Type: opts.Type,
PosterID: opts.Doer.ID, PosterID: opts.Doer.ID,
Poster: opts.Doer, Poster: opts.Doer,
IssueID: opts.Issue.ID, IssueID: opts.Issue.ID,
LabelID: LabelID, LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID, OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID, MilestoneID: opts.MilestoneID,
RemovedAssignee: opts.RemovedAssignee, RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID, AssigneeID: opts.AssigneeID,
CommitID: opts.CommitID, CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA, CommitSHA: opts.CommitSHA,
Line: opts.LineNum, Line: opts.LineNum,
Content: opts.Content, Content: opts.Content,
OldTitle: opts.OldTitle, OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle, NewTitle: opts.NewTitle,
DependentIssueID: opts.DependentIssueID,
} }
if _, err = e.Insert(comment); err != nil { if _, err = e.Insert(comment); err != nil {
return nil, err return nil, err
@ -549,6 +566,39 @@ func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, is
}) })
} }
// Creates issue dependency comment
func createIssueDependencyComment(e *xorm.Session, doer *User, issue *Issue, dependentIssue *Issue, add bool) (err error) {
cType := CommentTypeAddDependency
if !add {
cType = CommentTypeRemoveDependency
}
// Make two comments, one in each issue
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
DependentIssueID: dependentIssue.ID,
})
if err != nil {
return
}
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: dependentIssue,
DependentIssueID: issue.ID,
})
if err != nil {
return
}
return
}
// CreateCommentOptions defines options for creating comment // CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct { type CreateCommentOptions struct {
Type CommentType Type CommentType
@ -557,17 +607,18 @@ type CreateCommentOptions struct {
Issue *Issue Issue *Issue
Label *Label Label *Label
OldMilestoneID int64 DependentIssueID int64
MilestoneID int64 OldMilestoneID int64
AssigneeID int64 MilestoneID int64
RemovedAssignee bool AssigneeID int64
OldTitle string RemovedAssignee bool
NewTitle string OldTitle string
CommitID int64 NewTitle string
CommitSHA string CommitID int64
LineNum int64 CommitSHA string
Content string LineNum int64
Attachments []string // UUIDs of attachments Content string
Attachments []string // UUIDs of attachments
} }
// CreateComment creates comment of issue or commit. // CreateComment creates comment of issue or commit.

137
models/issue_dependency.go Normal file
View File

@ -0,0 +1,137 @@
// 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 models
import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// IssueDependency represents an issue dependency
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
DependencyID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
CreatedUnix util.TimeStamp `xorm:"created"`
UpdatedUnix util.TimeStamp `xorm:"updated"`
}
// DependencyType Defines Dependency Type Constants
type DependencyType int
// Define Dependency Types
const (
DependencyTypeBlockedBy DependencyType = iota
DependencyTypeBlocking
)
// CreateIssueDependency creates a new dependency for an issue
func CreateIssueDependency(user *User, issue, dep *Issue) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// Check if it aleready exists
exists, err := issueDepExists(sess, issue.ID, dep.ID)
if err != nil {
return err
}
if exists {
return ErrDependencyExists{issue.ID, dep.ID}
}
// And if it would be circular
circular, err := issueDepExists(sess, dep.ID, issue.ID)
if err != nil {
return err
}
if circular {
return ErrCircularDependency{issue.ID, dep.ID}
}
if _, err := sess.Insert(&IssueDependency{
UserID: user.ID,
IssueID: issue.ID,
DependencyID: dep.ID,
}); err != nil {
return err
}
// Add comment referencing the new dependency
if err = createIssueDependencyComment(sess, user, issue, dep, true); err != nil {
return err
}
return sess.Commit()
}
// RemoveIssueDependency removes a dependency from an issue
func RemoveIssueDependency(user *User, issue *Issue, dep *Issue, depType DependencyType) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
var issueDepToDelete IssueDependency
switch depType {
case DependencyTypeBlockedBy:
issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
case DependencyTypeBlocking:
issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
default:
return ErrUnknownDependencyType{depType}
}
affected, err := sess.Delete(&issueDepToDelete)
if err != nil {
return err
}
// If we deleted nothing, the dependency did not exist
if affected <= 0 {
return ErrDependencyNotExists{issue.ID, dep.ID}
}
// Add comment referencing the removed dependency
if err = createIssueDependencyComment(sess, user, issue, dep, false); err != nil {
return err
}
return sess.Commit()
}
// Check if the dependency already exists
func issueDepExists(e Engine, issueID int64, depID int64) (bool, error) {
return e.Where("(issue_id = ? AND dependency_id = ?)", issueID, depID).Exist(&IssueDependency{})
}
// IssueNoDependenciesLeft checks if issue can be closed
func IssueNoDependenciesLeft(issue *Issue) (bool, error) {
exists, err := x.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_dependency.issue_id = ?", issue.ID).
And("issue.is_closed = ?", "0").
Exist(&Issue{})
return !exists, err
}
// IsDependenciesEnabled returns if dependecies are enabled and returns the default setting if not set.
func (repo *Repository) IsDependenciesEnabled() bool {
var u *RepoUnit
var err error
if u, err = repo.GetUnit(UnitTypeIssues); err != nil {
log.Trace("%s", err)
return setting.Service.DefaultEnableDependencies
}
return u.IssuesConfig().EnableDependencies
}

View File

@ -0,0 +1,57 @@
// 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 models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateIssueDependency(t *testing.T) {
// Prepare
assert.NoError(t, PrepareTestDatabase())
user1, err := GetUserByID(1)
assert.NoError(t, err)
issue1, err := GetIssueByID(1)
assert.NoError(t, err)
issue2, err := GetIssueByID(2)
assert.NoError(t, err)
// Create a dependency and check if it was successful
err = CreateIssueDependency(user1, issue1, issue2)
assert.NoError(t, err)
// Do it again to see if it will check if the dependency already exists
err = CreateIssueDependency(user1, issue1, issue2)
assert.Error(t, err)
assert.True(t, IsErrDependencyExists(err))
// Check for circular dependencies
err = CreateIssueDependency(user1, issue2, issue1)
assert.Error(t, err)
assert.True(t, IsErrCircularDependency(err))
_ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID})
// Check if dependencies left is correct
left, err := IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.False(t, left)
// Close #2 and check again
err = issue2.ChangeStatus(user1, issue2.Repo, true)
assert.NoError(t, err)
left, err = IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.True(t, left)
// Test removing the dependency
err = RemoveIssueDependency(user1, issue1, issue2, DependencyTypeBlockedBy)
assert.NoError(t, err)
}

View File

@ -166,7 +166,7 @@ func (issues IssueList) loadAssignees(e Engine) error {
var assignees = make(map[int64][]*User, len(issues)) var assignees = make(map[int64][]*User, len(issues))
rows, err := e.Table("issue_assignees"). rows, err := e.Table("issue_assignees").
Join("INNER", "user", "`user`.id = `issue_assignees`.assignee_id"). Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issues.getIssueIDs()). In("`issue_assignees`.issue_id", issues.getIssueIDs()).
Rows(new(AssigneeIssue)) Rows(new(AssigneeIssue))
if err != nil { if err != nil {

View File

@ -67,7 +67,7 @@ func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error
Where("`issue_watch`.issue_id = ?", issueID). Where("`issue_watch`.issue_id = ?", issueID).
And("`user`.is_active = ?", true). And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false). And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `issue_watch`.user_id"). Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id").
Find(&watches) Find(&watches)
return return
} }

View File

@ -192,6 +192,8 @@ var migrations = []Migration{
NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics), NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics),
// v69 -> v70 // v69 -> v70
NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable),
// v70 -> v71
NewMigration("add issue_dependencies", addIssueDependencies),
} }
// Migrate database to current version // Migrate database to current version

100
models/migrations/v70.go Normal file
View File

@ -0,0 +1,100 @@
// 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"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func addIssueDependencies(x *xorm.Engine) (err error) {
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"NOT NULL"`
DependencyID int64 `xorm:"NOT NULL"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"created"`
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"updated"`
}
if err = x.Sync(new(IssueDependency)); err != nil {
return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
}
// Update Comment definition
// This (copied) struct does only contain fields used by xorm as the only use here is to update the database
// CommentType defines the comment type
type CommentType int
// TimeStamp defines a timestamp
type TimeStamp int64
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
IssueID int64 `xorm:"INDEX"`
LabelID int64
OldMilestoneID int64
MilestoneID int64
OldAssigneeID int64
AssigneeID int64
OldTitle string
NewTitle string
DependentIssueID int64
CommitID int64
Line int64
Content string `xorm:"TEXT"`
CreatedUnix TimeStamp `xorm:"INDEX created"`
UpdatedUnix TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"`
}
if err = x.Sync(new(Comment)); err != nil {
return fmt.Errorf("Error updating issue_comment table column definition: %v", err)
}
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type int `xorm:"INDEX(s)"`
Config map[string]interface{} `xorm:"JSON"`
CreatedUnix int64 `xorm:"INDEX CREATED"`
Created time.Time `xorm:"-"`
}
//Updating existing issue units
units := make([]*RepoUnit, 0, 100)
err = x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
if err != nil {
return fmt.Errorf("Query repo units: %v", err)
}
for _, unit := range units {
if unit.Config == nil {
unit.Config = make(map[string]interface{})
}
if _, ok := unit.Config["EnableDependencies"]; !ok {
unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
}
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
return err
}
}
return err
}

View File

@ -118,6 +118,7 @@ func init() {
new(TrackedTime), new(TrackedTime),
new(DeletedBranch), new(DeletedBranch),
new(RepoIndexerStatus), new(RepoIndexerStatus),
new(IssueDependency),
new(LFSLock), new(LFSLock),
new(Reaction), new(Reaction),
new(IssueAssignees), new(IssueAssignees),

View File

@ -383,7 +383,7 @@ func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) { func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) {
ous := make([]*OrgUser, 0, 10) ous := make([]*OrgUser, 0, 10)
sess := x. sess := x.
Join("LEFT", "user", "`org_user`.org_id=`user`.id"). Join("LEFT", "`user`", "`org_user`.org_id=`user`.id").
Where("`org_user`.uid=?", uid) Where("`org_user`.uid=?", uid)
if !all { if !all {
// Only show public organizations // Only show public organizations
@ -575,7 +575,7 @@ func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team,
return teams, e. return teams, e.
Where("`team_user`.org_id = ?", org.ID). Where("`team_user`.org_id = ?", org.ID).
Join("INNER", "team_user", "`team_user`.team_id = team.id"). Join("INNER", "team_user", "`team_user`.team_id = team.id").
Join("INNER", "user", "`user`.id=team_user.uid"). Join("INNER", "`user`", "`user`.id=team_user.uid").
And("`team_user`.uid = ?", userID). And("`team_user`.uid = ?", userID).
Asc("`user`.name"). Asc("`user`.name").
Cols(cols...). Cols(cols...).

View File

@ -1345,7 +1345,11 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
units = append(units, RepoUnit{ units = append(units, RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: tp, Type: tp,
Config: &IssuesConfig{EnableTimetracker: setting.Service.DefaultEnableTimetracking, AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime}, Config: &IssuesConfig{
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
EnableDependencies: setting.Service.DefaultEnableDependencies,
},
}) })
} else if tp == UnitTypePullRequests { } else if tp == UnitTypePullRequests {
units = append(units, RepoUnit{ units = append(units, RepoUnit{
@ -1954,7 +1958,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) {
var repo Repository var repo Repository
has, err := x.Select("repository.*"). has, err := x.Select("repository.*").
Join("INNER", "user", "`user`.id = repository.owner_id"). Join("INNER", "`user`", "`user`.id = repository.owner_id").
Where("repository.lower_name = ?", strings.ToLower(repoName)). Where("repository.lower_name = ?", strings.ToLower(repoName)).
And("`user`.lower_name = ?", strings.ToLower(ownerName)). And("`user`.lower_name = ?", strings.ToLower(ownerName)).
Get(&repo) Get(&repo)

View File

@ -73,6 +73,7 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
type IssuesConfig struct { type IssuesConfig struct {
EnableTimetracker bool EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool AllowOnlyContributorsToTrackTime bool
EnableDependencies bool
} }
// FromDB fills up a IssuesConfig from serialized format. // FromDB fills up a IssuesConfig from serialized format.
@ -165,7 +166,6 @@ func (r *RepoUnit) IssuesConfig() *IssuesConfig {
func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
return r.Config.(*ExternalTrackerConfig) return r.Config.(*ExternalTrackerConfig)
} }
func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
return units, e.Where("repo_id = ?", repoID).Find(&units) return units, e.Where("repo_id = ?", repoID).Find(&units)
} }

View File

@ -54,7 +54,7 @@ func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
return watches, e.Where("`watch`.repo_id=?", repoID). return watches, e.Where("`watch`.repo_id=?", repoID).
And("`user`.is_active=?", true). And("`user`.is_active=?", true).
And("`user`.prohibit_login=?", false). And("`user`.prohibit_login=?", false).
Join("INNER", "user", "`user`.id = `watch`.user_id"). Join("INNER", "`user`", "`user`.id = `watch`.user_id").
Find(&watches) Find(&watches)
} }

View File

@ -374,9 +374,9 @@ func (u *User) GetFollowers(page int) ([]*User, error) {
Limit(ItemsPerPage, (page-1)*ItemsPerPage). Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("follow.follow_id=?", u.ID) Where("follow.follow_id=?", u.ID)
if setting.UsePostgreSQL { if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`) sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
} else { } else {
sess = sess.Join("LEFT", "follow", "user.id=follow.user_id") sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
} }
return users, sess.Find(&users) return users, sess.Find(&users)
} }
@ -393,9 +393,9 @@ func (u *User) GetFollowing(page int) ([]*User, error) {
Limit(ItemsPerPage, (page-1)*ItemsPerPage). Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("follow.user_id=?", u.ID) Where("follow.user_id=?", u.ID)
if setting.UsePostgreSQL { if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`) sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
} else { } else {
sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id") sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
} }
return users, sess.Find(&users) return users, sess.Find(&users)
} }

View File

@ -113,6 +113,7 @@ type RepoSettingForm struct {
PullsAllowSquash bool PullsAllowSquash bool
EnableTimetracker bool EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool
// Admin settings // Admin settings
EnableHealthCheck bool EnableHealthCheck bool

View File

@ -104,6 +104,11 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
r.IsWriter() || issue.IsPoster(user.ID) || isAssigned) r.IsWriter() || issue.IsPoster(user.ID) || isAssigned)
} }
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
return r.Repository.IsDependenciesEnabled() && r.IsWriter()
}
// GetCommitsCount returns cached commit count for current view // GetCommitsCount returns cached commit count for current view
func (r *Repository) GetCommitsCount() (int64, error) { func (r *Repository) GetCommitsCount() (int64, error) {
var contextName string var contextName string

View File

@ -85,9 +85,12 @@ type link struct {
var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`) var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`)
func isOidValid(oid string) bool {
return oidRegExp.MatchString(oid)
}
// ObjectOidHandler is the main request routing entry point into LFS server functions // ObjectOidHandler is the main request routing entry point into LFS server functions
func ObjectOidHandler(ctx *context.Context) { func ObjectOidHandler(ctx *context.Context) {
if !setting.LFS.StartServer { if !setting.LFS.StartServer {
writeStatus(ctx, 404) writeStatus(ctx, 404)
return return
@ -110,6 +113,11 @@ func ObjectOidHandler(ctx *context.Context) {
} }
func getAuthenticatedRepoAndMeta(ctx *context.Context, rv *RequestVars, requireWrite bool) (*models.LFSMetaObject, *models.Repository) { func getAuthenticatedRepoAndMeta(ctx *context.Context, rv *RequestVars, requireWrite bool) (*models.LFSMetaObject, *models.Repository) {
if !isOidValid(rv.Oid) {
writeStatus(ctx, 404)
return nil, nil
}
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo) repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
if err != nil { if err != nil {
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err) log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
@ -222,7 +230,7 @@ func PostHandler(ctx *context.Context) {
return return
} }
if !oidRegExp.MatchString(rv.Oid) { if !isOidValid(rv.Oid) {
writeStatus(ctx, 404) writeStatus(ctx, 404)
return return
} }
@ -249,7 +257,6 @@ func PostHandler(ctx *context.Context) {
// BatchHandler provides the batch api // BatchHandler provides the batch api
func BatchHandler(ctx *context.Context) { func BatchHandler(ctx *context.Context) {
if !setting.LFS.StartServer { if !setting.LFS.StartServer {
writeStatus(ctx, 404) writeStatus(ctx, 404)
return return
@ -266,6 +273,10 @@ func BatchHandler(ctx *context.Context) {
// Create a response object // Create a response object
for _, object := range bv.Objects { for _, object := range bv.Objects {
if !isOidValid(object.Oid) {
continue
}
repository, err := models.GetRepositoryByOwnerAndName(object.User, object.Repo) repository, err := models.GetRepositoryByOwnerAndName(object.User, object.Repo)
if err != nil { if err != nil {
@ -292,12 +303,10 @@ func BatchHandler(ctx *context.Context) {
continue continue
} }
if oidRegExp.MatchString(object.Oid) { // Object is not found
// Object is not found meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID}) if err == nil {
if err == nil { responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
}
} }
} }

58
modules/markup/csv/csv.go Normal file
View File

@ -0,0 +1,58 @@
// 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 markup
import (
"bytes"
"encoding/csv"
"html"
"io"
"code.gitea.io/gitea/modules/markup"
)
func init() {
markup.RegisterParser(Parser{})
}
// Parser implements markup.Parser for orgmode
type Parser struct {
}
// Name implements markup.Parser
func (Parser) Name() string {
return "csv"
}
// Extensions implements markup.Parser
func (Parser) Extensions() []string {
return []string{".csv"}
}
// Render implements markup.Parser
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
rd := csv.NewReader(bytes.NewReader(rawBytes))
var tmpBlock bytes.Buffer
tmpBlock.WriteString(`<table class="table">`)
for {
fields, err := rd.Read()
if err == io.EOF {
break
}
if err != nil {
continue
}
tmpBlock.WriteString("<tr>")
for _, field := range fields {
tmpBlock.WriteString("<td>")
tmpBlock.WriteString(html.EscapeString(field))
tmpBlock.WriteString("</td>")
}
tmpBlock.WriteString("<tr>")
}
tmpBlock.WriteString("</table>")
return tmpBlock.Bytes()
}

View File

@ -0,0 +1,25 @@
// 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 markup
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRenderCSV(t *testing.T) {
var parser Parser
var kases = map[string]string{
"a": "<table class=\"table\"><tr><td>a</td><tr></table>",
"1,2": "<table class=\"table\"><tr><td>1</td><td>2</td><tr></table>",
"<br/>": "<table class=\"table\"><tr><td>&lt;br/&gt;</td><tr></table>",
}
for k, v := range kases {
res := parser.Render([]byte(k), "", nil, false)
assert.EqualValues(t, v, string(res))
}
}

View File

@ -1180,6 +1180,7 @@ var Service struct {
DefaultAllowCreateOrganization bool DefaultAllowCreateOrganization bool
EnableTimetracking bool EnableTimetracking bool
DefaultEnableTimetracking bool DefaultEnableTimetracking bool
DefaultEnableDependencies bool
DefaultAllowOnlyContributorsToTrackTime bool DefaultAllowOnlyContributorsToTrackTime bool
NoReplyAddress string NoReplyAddress string
@ -1210,6 +1211,7 @@ func newService() {
if Service.EnableTimetracking { if Service.EnableTimetracking {
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true) Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
} }
Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true) Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org") Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")

View File

@ -32,16 +32,8 @@ twofa_scratch=Zwei-Faktor-Einmalpasswort
passcode=PIN passcode=PIN
u2f_insert_key=Hardware-Sicherheitsschlüssel einstecken u2f_insert_key=Hardware-Sicherheitsschlüssel einstecken
u2f_sign_in=Drücke den Knopf auf deinem Sicherheitsschlüssel. Wenn deiner keinen Knopf hat, stecke ihn erneut ein.
u2f_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel… u2f_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel…
u2f_use_twofa=Zwei-Faktor-Authentifizierung via Handy verwenden u2f_use_twofa=Zwei-Faktor-Authentifizierung via Handy verwenden
u2f_error=Wir können deinen Hardware-Sicherheitsschlüssel nicht lesen!
u2f_unsupported_browser=Dein Browser unterstützt keine U2F-Geräte. Bitte benutze einen anderen Browser.
u2f_error_1=Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.
u2f_error_2=Stelle sicher, dass du einen verschlüsselte Verbindung (https://) benutzt und die richtige URL eingeben hast.
u2f_error_3=Der Server kann deine Anfrage nicht bearbeiten.
u2f_error_4=Dieser Sicherheitsschlüssel ist nicht berechtigt. Wenn du versuchst, einen neuen Sicherheitsschlüssel zu registrieren, stelle bitte sicher, dass du ihn nicht bereits registriert hast.
u2f_error_5=Das Zeitlimit wurde erreicht bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite neu.
u2f_reload=Neu laden u2f_reload=Neu laden
repository=Repository repository=Repository
@ -130,7 +122,6 @@ federated_avatar_lookup=Föderierte Profilbilder einschalten
federated_avatar_lookup_popup=Föderierte Profilbilder via Libravatar aktivieren. federated_avatar_lookup_popup=Föderierte Profilbilder via Libravatar aktivieren.
disable_registration=Registrierung deaktivieren disable_registration=Registrierung deaktivieren
disable_registration_popup=Registrierung neuer Benutzer deaktivieren. Nur Administratoren werden neue Benutzerkonten anlegen können. disable_registration_popup=Registrierung neuer Benutzer deaktivieren. Nur Administratoren werden neue Benutzerkonten anlegen können.
allow_only_external_registration_popup=Registrierung nur über externe Services aktiveren.
openid_signin=OpenID-Anmeldung aktivieren openid_signin=OpenID-Anmeldung aktivieren
openid_signin_popup=Benutzeranmeldung via OpenID aktivieren. openid_signin_popup=Benutzeranmeldung via OpenID aktivieren.
openid_signup=OpenID-Selbstregistrierung aktivieren openid_signup=OpenID-Selbstregistrierung aktivieren
@ -463,13 +454,10 @@ then_enter_passcode=Und gebe dann die angezeigte PIN der Anwendung ein:
passcode_invalid=Die PIN ist falsch. Probiere es erneut. passcode_invalid=Die PIN ist falsch. Probiere es erneut.
twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre dein Einmalpasswort (%s) an einem sicheren Ort auf, da es nicht wieder angezeigt werden wird. twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre dein Einmalpasswort (%s) an einem sicheren Ort auf, da es nicht wieder angezeigt werden wird.
u2f_desc=Hardware-Sicherheitsschlüssel sind Geräte, die kryptografische Schlüssel beinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Der Sicherheitsschlüssel muss den Standard <a href="https://fidoalliance.org/">FIDO U2F</a> unterstützen.
u2f_require_twofa=Du musst die Zwei-Faktor-Authentifizierung aktivieren, um Hardware-Sicherheitsschlüssel nutzen zu können.
u2f_register_key=Sicherheitsschlüssel hinzufügen u2f_register_key=Sicherheitsschlüssel hinzufügen
u2f_nickname=Nickname u2f_nickname=Nickname
u2f_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel, um diesen zu registrieren. u2f_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel, um diesen zu registrieren.
u2f_delete_key=Sicherheitsschlüssel entfernen u2f_delete_key=Sicherheitsschlüssel entfernen
u2f_delete_key_desc=Wenn du den Sicherheitsschlüssel entfernst, kannst du dich nicht mehr mit diesem einloggen. Bist du sicher?
manage_account_links=Verknüpfte Accounts verwalten manage_account_links=Verknüpfte Accounts verwalten
manage_account_links_desc=Diese externen Accounts sind mit deinem Gitea-Account verknüpft. manage_account_links_desc=Diese externen Accounts sind mit deinem Gitea-Account verknüpft.
@ -650,7 +638,6 @@ issues.new.open_milestone=Offene Meilensteine
issues.new.closed_milestone=Geschlossene Meilensteine issues.new.closed_milestone=Geschlossene Meilensteine
issues.new.assignees=Zuständig issues.new.assignees=Zuständig
issues.new.clear_assignees=Zuständige entfernen issues.new.clear_assignees=Zuständige entfernen
issues.new.no_assignees=Niemand zugewiesen
issues.no_ref=Keine Branch/Tag angegeben issues.no_ref=Keine Branch/Tag angegeben
issues.create=Issue erstellen issues.create=Issue erstellen
issues.new_label=Neues Label issues.new_label=Neues Label
@ -1167,8 +1154,6 @@ branch.protected_deletion_failed=Branch „%s“ ist geschützt und kann nicht g
topic.manage_topics=Themen verwalten topic.manage_topics=Themen verwalten
topic.done=Fertig 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]
org_name_holder=Name der Organisation org_name_holder=Name der Organisation
@ -1273,8 +1258,6 @@ dashboard.operation_switch=Wechseln
dashboard.operation_run=Ausführen dashboard.operation_run=Ausführen
dashboard.clean_unbind_oauth=Nicht verbundene OAuth-Verbindungen löschen dashboard.clean_unbind_oauth=Nicht verbundene OAuth-Verbindungen löschen
dashboard.clean_unbind_oauth_success=Alle unverbundene OAuth-Verbindungen wurden gelöscht. dashboard.clean_unbind_oauth_success=Alle unverbundene OAuth-Verbindungen wurden gelöscht.
dashboard.delete_inactivate_accounts=Lösche alle nicht-aktivierten Accounts
dashboard.delete_inactivate_accounts_success=Alle nicht-aktivierten Accounts wurden gelöscht.
dashboard.delete_repo_archives=Alle Repository-Archive löschen dashboard.delete_repo_archives=Alle Repository-Archive löschen
dashboard.delete_repo_archives_success=Alle Repository-Archive wurden gelöscht. dashboard.delete_repo_archives_success=Alle Repository-Archive wurden gelöscht.
dashboard.delete_missing_repos=Alle Repository-Datensätze mit verlorenen gegangenen Git-Dateien löschen dashboard.delete_missing_repos=Alle Repository-Datensätze mit verlorenen gegangenen Git-Dateien löschen
@ -1482,7 +1465,6 @@ config.db_path=Verzeichnis
config.service_config=Service-Konfiguration config.service_config=Service-Konfiguration
config.register_email_confirm=E-Mail-Bestätigung benötigt zum Registrieren config.register_email_confirm=E-Mail-Bestätigung benötigt zum Registrieren
config.disable_register=Selbstegistrierung deaktivieren config.disable_register=Selbstegistrierung deaktivieren
config.allow_only_external_registration=Registrierung nur über externe Services aktiveren
config.enable_openid_signup=OpenID-Selbstregistrierung aktivieren config.enable_openid_signup=OpenID-Selbstregistrierung aktivieren
config.enable_openid_signin=OpenID-Anmeldung aktivieren config.enable_openid_signin=OpenID-Anmeldung aktivieren
config.show_registration_button=Schaltfläche zum Registrieren anzeigen config.show_registration_button=Schaltfläche zum Registrieren anzeigen

View File

@ -32,16 +32,16 @@ twofa_scratch = Two-Factor Scratch Code
passcode = Passcode passcode = Passcode
u2f_insert_key = Insert your security key u2f_insert_key = Insert your security key
u2f_sign_in = Press the button on your security key. If you can't find a button, re-insert it. u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it.
u2f_press_button = Please press the button on your security key… u2f_press_button = Please press the button on your security key…
u2f_use_twofa = Use a two-factor code from your phone u2f_use_twofa = Use a two-factor code from your phone
u2f_error = We can't read your security key! u2f_error = Could not read your security key.
u2f_unsupported_browser = Your browser don't support U2F keys. Please try another browser. u2f_unsupported_browser = Your browser does not support U2F security keys.
u2f_error_1 = An unknown error occured. Please retry. u2f_error_1 = An unknown error occurred. Please retry.
u2f_error_2 = Please make sure that you're using an encrypted connection (https://) and visiting the correct URL. u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL.
u2f_error_3 = The server could not proceed your request. u2f_error_3 = The server could not process your request.
u2f_error_4 = The presented key is not eligible for this request. If you try to register it, make sure that the key isn't already registered. u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered.
u2f_error_5 = Timeout reached before your key could be read. Please reload to retry. u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry.
u2f_reload = Reload u2f_reload = Reload
repository = Repository repository = Repository
@ -130,7 +130,7 @@ federated_avatar_lookup = Enable Federated Avatars
federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar. federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar.
disable_registration = Disable Self-Registration disable_registration = Disable Self-Registration
disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts. disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts.
allow_only_external_registration_popup=Enable the registration only through external services. allow_only_external_registration_popup = Allow Registration Only Through External Services
openid_signin = Enable OpenID Sign-In openid_signin = Enable OpenID Sign-In
openid_signin_popup = Enable user sign-in via OpenID. openid_signin_popup = Enable user sign-in via OpenID.
openid_signup = Enable OpenID Self-Registration openid_signup = Enable OpenID Self-Registration
@ -463,13 +463,13 @@ then_enter_passcode = And enter the passcode shown in the application:
passcode_invalid = The passcode is incorrect. Try again. passcode_invalid = The passcode is incorrect. Try again.
twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once! twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once!
u2f_desc = Security keys are hardware devices containing cryptographic keys. They could be used for two factor authentication. The security key must support the <a href="https://fidoalliance.org/">FIDO U2F</a> standard. u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" href="https://fidoalliance.org/">FIDO U2F</a> standard.
u2f_require_twofa = Two-Factor-Authentication must be enrolled in order to use security keys. u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys.
u2f_register_key = Add Security Key u2f_register_key = Add Security Key
u2f_nickname = Nickname u2f_nickname = Nickname
u2f_press_button = Press the button on your security key to register it. u2f_press_button = Press the button on your security key to register it.
u2f_delete_key = Remove Security Key u2f_delete_key = Remove Security Key
u2f_delete_key_desc= If you remove a security key you cannot login with it anymore. Are you sure? u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue?
manage_account_links = Manage Linked Accounts manage_account_links = Manage Linked Accounts
manage_account_links_desc = These external accounts are linked to your Gitea account. manage_account_links_desc = These external accounts are linked to your Gitea account.
@ -650,7 +650,7 @@ issues.new.open_milestone = Open Milestones
issues.new.closed_milestone = Closed Milestones issues.new.closed_milestone = Closed Milestones
issues.new.assignees = Assignees issues.new.assignees = Assignees
issues.new.clear_assignees = Clear assignees issues.new.clear_assignees = Clear assignees
issues.new.no_assignees = Nobody assigned issues.new.no_assignees = No Assignees
issues.no_ref = No Branch/Tag Specified issues.no_ref = No Branch/Tag Specified
issues.create = Create Issue issues.create = Create Issue
issues.new_label = New Label issues.new_label = New Label
@ -781,7 +781,33 @@ issues.due_date_added = "added the due date %s %s"
issues.due_date_modified = "modified the due date to %s from %s %s" issues.due_date_modified = "modified the due date to %s from %s %s"
issues.due_date_remove = "removed the due date %s %s" issues.due_date_remove = "removed the due date %s %s"
issues.due_date_overdue = "Overdue" issues.due_date_overdue = "Overdue"
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format yyyy-mm-dd." issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'."
issues.dependency.title = Dependencies
issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies.
issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies.
issues.dependency.add = Add dependency...
issues.dependency.cancel = Cancel
issues.dependency.remove = Remove
issues.dependency.added_dependency = `<a href="%[1]s">%[2]s</a> added a new dependency %[3]s`
issues.dependency.removed_dependency = `<a href="%[1]s">%[2]s</a> removed a dependency %[3]s`
issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues
issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues
issues.dependency.issue_close_blocks = This issue blocks closing of the following issues
issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues
issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it.
issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it.
issues.dependency.blocks_short = Blocks
issues.dependency.blocked_by_short = Depends on
issues.dependency.remove_header = Remove Dependency
issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue?
issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue?
issues.dependency.setting = Enable Dependencies For Issues and Pull Requests
issues.dependency.add_error_same_issue = You cannot make an issue depend on itself.
issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist.
issues.dependency.add_error_dep_not_exist = Dependency does not exist.
issues.dependency.add_error_dep_exists = Dependency already exists.
issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other.
issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository.
pulls.desc = Enable merge requests and code reviews. pulls.desc = Enable merge requests and code reviews.
pulls.new = New Pull Request pulls.new = New Pull Request
@ -1170,8 +1196,8 @@ branch.protected_deletion_failed = Branch '%s' is protected. It cannot be delete
topic.manage_topics = Manage Topics topic.manage_topics = Manage Topics
topic.done = Done topic.done = Done
topic.count_prompt = You can't select more than 25 topics topic.count_prompt = You can not 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 topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
[org] [org]
org_name_holder = Organization Name org_name_holder = Organization Name
@ -1276,8 +1302,8 @@ dashboard.operation_switch = Switch
dashboard.operation_run = Run dashboard.operation_run = Run
dashboard.clean_unbind_oauth = Clean unbound OAuth connections dashboard.clean_unbind_oauth = Clean unbound OAuth connections
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted. dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
dashboard.delete_inactivate_accounts = Delete all not activated accounts dashboard.delete_inactivate_accounts = Delete all unactivated accounts
dashboard.delete_inactivate_accounts_success = All not activated accounts have been deleted. dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted.
dashboard.delete_repo_archives = Delete all repository archives dashboard.delete_repo_archives = Delete all repository archives
dashboard.delete_repo_archives_success = All repository archives have been deleted. dashboard.delete_repo_archives_success = All repository archives have been deleted.
dashboard.delete_missing_repos = Delete all repositories missing their Git files dashboard.delete_missing_repos = Delete all repositories missing their Git files
@ -1485,7 +1511,7 @@ config.db_path = Path
config.service_config = Service Configuration config.service_config = Service Configuration
config.register_email_confirm = Require Email Confirmation to Register config.register_email_confirm = Require Email Confirmation to Register
config.disable_register = Disable Self-Registration config.disable_register = Disable Self-Registration
config.allow_only_external_registration = Enable the registration only through external services config.allow_only_external_registration = Allow Registration Only Through External Services
config.enable_openid_signup = Enable OpenID Self-Registration config.enable_openid_signup = Enable OpenID Self-Registration
config.enable_openid_signin = Enable OpenID Sign-In config.enable_openid_signin = Enable OpenID Sign-In
config.show_registration_button = Show Register Button config.show_registration_button = Show Register Button
@ -1501,6 +1527,7 @@ config.enable_timetracking = Enable Time Tracking
config.default_enable_timetracking = Enable Time Tracking by Default config.default_enable_timetracking = Enable Time Tracking by Default
config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
config.no_reply_address = Hidden Email Domain config.no_reply_address = Hidden Email Domain
config.default_enable_dependencies = Enable Issue Dependencies by Default
config.webhook_config = Webhook Configuration config.webhook_config = Webhook Configuration
config.queue_length = Queue Length config.queue_length = Queue Length

View File

@ -32,11 +32,6 @@ passcode=Contraseña
u2f_insert_key=Inserte su clave de seguridad u2f_insert_key=Inserte su clave de seguridad
u2f_use_twofa=Use un código de dos factores de su celular u2f_use_twofa=Use un código de dos factores de su celular
u2f_error=No podemos leer su llave de seguridad!
u2f_unsupported_browser=Su navegador no soporta llaves U2F. Por favor utilicé otro navegador.
u2f_error_1=Un error desconocido ha ocurrido. Por favor vuelva a intentarlo.
u2f_error_2=Por favor asegúrese de que está utilizando una conexión cifrada (https://) y que esta visitando la dirección URL correcta.
u2f_error_3=El servidor no pudo procesar su petición.
u2f_reload=Recargar u2f_reload=Recargar
repository=Repositorio repository=Repositorio

View File

@ -32,16 +32,8 @@ twofa_scratch=Code de secours pour l'authentification à deux facteurs
passcode=Code d'accès passcode=Code d'accès
u2f_insert_key=Insérez votre clef de sécurité u2f_insert_key=Insérez votre clef de sécurité
u2f_sign_in=Appuyez sur le bouton de votre clef de sécurité. Si vous ne voyez pas de bouton, ré-insérez là.
u2f_press_button=Veuillez appuyer sur le bouton de votre clef de sécurité… u2f_press_button=Veuillez appuyer sur le bouton de votre clef de sécurité…
u2f_use_twofa=Utilisez l'authentification à deux facteurs avec votre téléphone u2f_use_twofa=Utilisez l'authentification à deux facteurs avec votre téléphone
u2f_error=Nous ne pouvons pas lire votre clé de sécurité !
u2f_unsupported_browser=Votre navigateur n'est pas compatible avec les clefs U2F. Veuillez réessayer avec un autre navigateur.
u2f_error_1=Une erreur inconnue s'est produite. Veuillez réessayer.
u2f_error_2=Veuillez vous assurer que vous utilisez une connexion chiffrée (https) et que vous visitez la bonne adresse.
u2f_error_3=Le serveur n'a pas pu répondre à votre requête.
u2f_error_4=Cette clef n'est pas compatible avec votre requête. Si vous êtes en train de l'enregistrer, veuillez vous assurer que cette clef n'est pas déjà enregistrée.
u2f_error_5=Le délai d'attente imparti a été atteint avant que votre clef ne puisse être lue. Veuillez recharger la page pour réessayer.
u2f_reload=Recharger u2f_reload=Recharger
repository=Dépôt repository=Dépôt
@ -129,7 +121,6 @@ federated_avatar_lookup=Activer les avatars unifiés
federated_avatar_lookup_popup=Activer la recherche unifiée d'avatars en utilisant le service open source unifié basé sur libravatar. federated_avatar_lookup_popup=Activer la recherche unifiée d'avatars en utilisant le service open source unifié basé sur libravatar.
disable_registration=Désactiver le formulaire d'inscription disable_registration=Désactiver le formulaire d'inscription
disable_registration_popup=Désactiver les nouvelles inscriptions. Seuls les administrateurs pourront créer de nouveaux comptes utilisateurs. disable_registration_popup=Désactiver les nouvelles inscriptions. Seuls les administrateurs pourront créer de nouveaux comptes utilisateurs.
allow_only_external_registration_popup=N'autoriser l'inscription qu'à partir des services externes.
openid_signin=Activer l'inscription OpenID openid_signin=Activer l'inscription OpenID
openid_signin_popup=Activer l'authentification via OpenID. openid_signin_popup=Activer l'authentification via OpenID.
openid_signup=Activer l'inscription OpenID openid_signup=Activer l'inscription OpenID
@ -462,13 +453,10 @@ then_enter_passcode=Et entrez le mot de passe s'affichant dans l'application :
passcode_invalid=Le mot de passe est invalide. Réessayez. passcode_invalid=Le mot de passe est invalide. Réessayez.
twofa_enrolled=L'authentification à deux facteurs a été activée pour votre compte. Gardez votre jeton de secours (%s) en lieu sûr car il ne vous sera montré qu'une seule fois ! twofa_enrolled=L'authentification à deux facteurs a été activée pour votre compte. Gardez votre jeton de secours (%s) en lieu sûr car il ne vous sera montré qu'une seule fois !
u2f_desc=Les clefs de sécurité sont des dispositifs matériels contenant des clefs cryptographiques. Elles peuvent être utilisées pour l'authentification à deux facteurs. La clef de sécurité doit supporter le standard <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=L'authentification à deux facteurs doit être activée afin d'utiliser une clef de sécurité.
u2f_register_key=Ajouter une clef de sécurité u2f_register_key=Ajouter une clef de sécurité
u2f_nickname=Pseudonyme u2f_nickname=Pseudonyme
u2f_press_button=Appuyer sur le bouton de votre clef de sécurité pour l'enregistrer. u2f_press_button=Appuyer sur le bouton de votre clef de sécurité pour l'enregistrer.
u2f_delete_key=Supprimer une clef de sécurité u2f_delete_key=Supprimer une clef de sécurité
u2f_delete_key_desc=Si vous supprimez une clef de sécurité vous ne pourrez plus l'utiliser pour vous connecter. Continuer?
manage_account_links=Gérer les comptes liés manage_account_links=Gérer les comptes liés
manage_account_links_desc=Ces comptes externes sont liés à votre compte Gitea. manage_account_links_desc=Ces comptes externes sont liés à votre compte Gitea.
@ -643,7 +631,6 @@ issues.new.open_milestone=Ouvrir un jalon
issues.new.closed_milestone=Jalons fermés issues.new.closed_milestone=Jalons fermés
issues.new.assignees=Affecté à issues.new.assignees=Affecté à
issues.new.clear_assignees=Supprimer les affectations issues.new.clear_assignees=Supprimer les affectations
issues.new.no_assignees=Aucune affectation
issues.no_ref=Aucune branche/tag spécifiés issues.no_ref=Aucune branche/tag spécifiés
issues.create=Créer un ticket issues.create=Créer un ticket
issues.new_label=Nouvelle étiquette issues.new_label=Nouvelle étiquette
@ -1158,8 +1145,6 @@ branch.protected_deletion_failed=La branche '%s' est protégé. Il ne peut pas
topic.manage_topics=Gérer les sujets topic.manage_topics=Gérer les sujets
topic.done=Terminé topic.done=Terminé
topic.count_prompt=Vous ne pouvez pas sélectionner plus de 25 sujets
topic.format_prompt=Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets (-), sans dépasser 35 caractères
[org] [org]
org_name_holder=Nom de l'organisation org_name_holder=Nom de l'organisation
@ -1471,7 +1456,6 @@ config.db_path=Emplacement
config.service_config=Configuration du service config.service_config=Configuration du service
config.register_email_confirm=Exiger la confirmation de l'e-mail lors de l'inscription config.register_email_confirm=Exiger la confirmation de l'e-mail lors de l'inscription
config.disable_register=Désactiver le formulaire d'inscription config.disable_register=Désactiver le formulaire d'inscription
config.allow_only_external_registration=N'autoriser l'inscription qu'à partir des services externes
config.enable_openid_signup=Activer l'inscription avec OpenID config.enable_openid_signup=Activer l'inscription avec OpenID
config.enable_openid_signin=Activer la connexion avec OpenID config.enable_openid_signin=Activer la connexion avec OpenID
config.show_registration_button=Afficher le bouton d'enregistrement config.show_registration_button=Afficher le bouton d'enregistrement

View File

@ -32,16 +32,8 @@ twofa_scratch=Kode Awal Dua Faktor
passcode=Kode Akses passcode=Kode Akses
u2f_insert_key=Masukkan kunci keamanan anda u2f_insert_key=Masukkan kunci keamanan anda
u2f_sign_in=Tekan tombol pada kunci keamanan anda. Jika anda tidak menemukan tombol, masukkan kembali.
u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda… u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda…
u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda
u2f_error=Kami tidak bisa membaca kunci keamanan anda!
u2f_unsupported_browser=Browser anda tidak mendukung kunci U2F. Silakan mencoba browser lain.
u2f_error_1=Terdapat kesalahan yang tidak diketahui. Mohon coba lagi.
u2f_error_2=Pastikan bahwa anda menggunakan koneksi terenkripsi (https://) dan mengunjungi URL yang benar.
u2f_error_3=Server tidak bisa melanjutkan permintaan anda.
u2f_error_4=Kunci tidak layak untuk permintaan ini. Jika Anda mencoba untuk mendaftarkanya, pastikan bahwa kunci sudah tidak terdaftar.
u2f_error_5=Timeout tercapai sebelum kunci anda bisa terbaca. Silahkan muat ulang untuk mencoba kembali.
u2f_reload=Muat Ulang u2f_reload=Muat Ulang
repository=Repositori repository=Repositori

View File

@ -32,16 +32,8 @@ twofa_scratch=Codice di recupero per la verifica in due passaggi
passcode=Codice di sicurezza passcode=Codice di sicurezza
u2f_insert_key=Inserisci la chiave di sicurezza u2f_insert_key=Inserisci la chiave di sicurezza
u2f_sign_in=Premi il pulsante sulla tua chiave di sicurezza. Se non riesci a trovare alcun pulsante, reinseriscilo.
u2f_press_button=Si prega di premere il pulsante sulla tua chiave di sicurezza… u2f_press_button=Si prega di premere il pulsante sulla tua chiave di sicurezza…
u2f_use_twofa=Usa un codice di verifica in due passaggi dal tuo telefono u2f_use_twofa=Usa un codice di verifica in due passaggi dal tuo telefono
u2f_error=Non riusciamo a leggere la tua chiave di sicurezza!
u2f_unsupported_browser=Il tuo browser non supporta le chiavi U2F. Si prega di provare un altro browser.
u2f_error_1=Si è verificato un errore sconosciuto. Si prega di riprovare.
u2f_error_2=Si prega di assicurarsi che si sta utilizza una connessione crittografata (https://) e visitando l'URL corretto.
u2f_error_3=Il server non ha potuto eseguire la richiesta.
u2f_error_4=La chiave data non è idonea per questa richiesta. Se stai provando a registrarla, assicurati che la chiave non sia già stata registrata.
u2f_error_5=Timeout raggiunto prima che la tua chiave potesse essere letta. Si prega di ricaricare per riprovare.
u2f_reload=Ricarica u2f_reload=Ricarica
repository=Repository repository=Repository
@ -129,7 +121,6 @@ federated_avatar_lookup=Attiva i Federated Avatar
federated_avatar_lookup_popup=Enable federated avatars lookup to use federated open source service based on libravatar. federated_avatar_lookup_popup=Enable federated avatars lookup to use federated open source service based on libravatar.
disable_registration=Disattiva Self-Registration disable_registration=Disattiva Self-Registration
disable_registration_popup=Disattiva la user self-registration. Solo gli amministratori saranno in grado di creare account. disable_registration_popup=Disattiva la user self-registration. Solo gli amministratori saranno in grado di creare account.
allow_only_external_registration_popup=Attiva la registrazione solo tramite servizi esterni.
openid_signin=Attiva l'accesso OpenID openid_signin=Attiva l'accesso OpenID
openid_signin_popup=Attiva registrazione utente via OpenID. openid_signin_popup=Attiva registrazione utente via OpenID.
openid_signup=Attiva OpenID Self-Registration openid_signup=Attiva OpenID Self-Registration
@ -462,14 +453,10 @@ then_enter_passcode=E immetti il codice di accesso indicato nell'applicazione:
passcode_invalid=Il codice di accesso non è corretto. Riprova. passcode_invalid=Il codice di accesso non è corretto. Riprova.
twofa_enrolled=Il tuo account è stato registrato alla verifica in due passaggi. Conserva il token di sicurezza (%s) in un luogo sicuro in quanto viene visualizzato sono una volta! twofa_enrolled=Il tuo account è stato registrato alla verifica in due passaggi. Conserva il token di sicurezza (%s) in un luogo sicuro in quanto viene visualizzato sono una volta!
u2f_desc=Le chiavi di sicurezza sono dispositivi hardware contenenti chiavi crittografiche. Potrebbero essere usate per la verifica in due passaggi.
La chiave di sicurezza deve supportare lo standard <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=La verifica in due passaggi deve essere attiva per poter utilizzare le chiavi di protezione.
u2f_register_key=Aggiungi chiave di sicurezza u2f_register_key=Aggiungi chiave di sicurezza
u2f_nickname=Nickname u2f_nickname=Nickname
u2f_press_button=Premi il pulsante sulla tua chiave di sicurezza per registrarla. u2f_press_button=Premi il pulsante sulla tua chiave di sicurezza per registrarla.
u2f_delete_key=Rimuovi chiave di sicurezza u2f_delete_key=Rimuovi chiave di sicurezza
u2f_delete_key_desc=Se si rimuove una chiave di sicurezza non sarà più possibile effettuare l'accesso con essa. Sei sicuro?
manage_account_links=Gestisci gli account collegati manage_account_links=Gestisci gli account collegati
manage_account_links_desc=Questi account esterni sono collegati al tuo account Gitea. manage_account_links_desc=Questi account esterni sono collegati al tuo account Gitea.
@ -644,7 +631,6 @@ issues.new.open_milestone=Apri Milestone
issues.new.closed_milestone=Milestone chiuse issues.new.closed_milestone=Milestone chiuse
issues.new.assignees=Assegnatari issues.new.assignees=Assegnatari
issues.new.clear_assignees=Cancella assegnatari issues.new.clear_assignees=Cancella assegnatari
issues.new.no_assignees=Nessuno assegnato
issues.no_ref=Nessun Branch/Tag specificato issues.no_ref=Nessun Branch/Tag specificato
issues.create=Crea Problema issues.create=Crea Problema
issues.new_label=Nuova etichetta issues.new_label=Nuova etichetta
@ -1159,8 +1145,6 @@ branch.protected_deletion_failed=Il branch '%s' è protetto. Non può essere eli
topic.manage_topics=Gestisci argomenti topic.manage_topics=Gestisci argomenti
topic.done=Fatto topic.done=Fatto
topic.count_prompt=Non puoi selezione più di 25 argomenti
topic.format_prompt=Gli argomenti devono iniziare con una lettera o un numero, possono includere il carattere hiphens(-) e non possono essere più lunghi di 35 caratteri
[org] [org]
org_name_holder=Nome dell'Organizzazione org_name_holder=Nome dell'Organizzazione
@ -1472,7 +1456,6 @@ config.db_path=Percorso
config.service_config=Configurazione Servizio config.service_config=Configurazione Servizio
config.register_email_confirm=Richiedere la conferma Email per registrarsi config.register_email_confirm=Richiedere la conferma Email per registrarsi
config.disable_register=Disattiva Self-Registration config.disable_register=Disattiva Self-Registration
config.allow_only_external_registration=Attiva la registrazione solo tramite servizi esterni
config.enable_openid_signup=Attiva OpenID Self-Registration config.enable_openid_signup=Attiva OpenID Self-Registration
config.enable_openid_signin=Attiva l'accesso tramite OpenID config.enable_openid_signin=Attiva l'accesso tramite OpenID
config.show_registration_button=Mostra Pulsane Registrazione config.show_registration_button=Mostra Pulsane Registrazione

View File

@ -32,16 +32,8 @@ twofa_scratch=Divu faktoru vienreizējais kods
passcode=Kods passcode=Kods
u2f_insert_key=Ievietojiet Jūsu drošības atslēgu u2f_insert_key=Ievietojiet Jūsu drošības atslēgu
u2f_sign_in=Nospiediet drošības atslēgas pogu. Ja nevarat to atrast, ievietojiet atkārtoti.
u2f_press_button=Nospiediet drošības atslēgas pogu… u2f_press_button=Nospiediet drošības atslēgas pogu…
u2f_use_twofa=Izmantot divu faktoru kodu no tālruņa u2f_use_twofa=Izmantot divu faktoru kodu no tālruņa
u2f_error=Nav iespējams nolasīt drošības atslēgu!
u2f_unsupported_browser=Jūsu pārlūks neatbalsta U2F atslēgas. Izmantojiet citu pārlūku.
u2f_error_1=Notikusi nezināma kļūda. Atkārtojiet darbību vēlreiz.
u2f_error_2=Pārliecinieties, ka izmantojat šifrētu savienojumu (https://) un apmeklējat pareizo URL.
u2f_error_3=Serveris nevar apstrādāt Jūsu pieprasījumu.
u2f_error_4=Nav iespējams izmantot atslēgu šim pieprasījumam. Ja mēģināt to reģistrēt, pārliecinieties, ka atslēga jau nav reģistrēta.
u2f_error_5=Iestājusies noildze nolasot atslēgu. Pārlādējiet lapu, lai atkārtotu vēlreiz.
u2f_reload=Pārlādēt u2f_reload=Pārlādēt
repository=Repozitorijs repository=Repozitorijs
@ -130,7 +122,6 @@ federated_avatar_lookup=Iespējot apvienotās profila bildes
federated_avatar_lookup_popup=Iespējot apvienoto profila bilžu meklētāju, lai izmantotu atvērtā koda apvienoto servisu balstītu uz libravatar. federated_avatar_lookup_popup=Iespējot apvienoto profila bilžu meklētāju, lai izmantotu atvērtā koda apvienoto servisu balstītu uz libravatar.
disable_registration=Atspējot lietotāju reģistrāciju disable_registration=Atspējot lietotāju reģistrāciju
disable_registration_popup=Atspējot iespēju reģistrēties. Tikai administratori varēs izveidot jaunus kontus. disable_registration_popup=Atspējot iespēju reģistrēties. Tikai administratori varēs izveidot jaunus kontus.
allow_only_external_registration_popup=Ļaut reģistrēties tikai izmantojot ārējos pakalpojumus.
openid_signin=Iespējot OpenID autorizāciju openid_signin=Iespējot OpenID autorizāciju
openid_signin_popup=Iespējot lietotāju autorizāciju ar OpenID. openid_signin_popup=Iespējot lietotāju autorizāciju ar OpenID.
openid_signup=Iespējot reģistrāciju, izmantojot OpenID openid_signup=Iespējot reģistrāciju, izmantojot OpenID
@ -463,13 +454,10 @@ then_enter_passcode=Ievadiet piekļuves kodu no lietojumprogrammas:
passcode_invalid=Nederīgs piekļuves kods. Mēģiniet ievadīt atkārtoti. passcode_invalid=Nederīgs piekļuves kods. Mēģiniet ievadīt atkārtoti.
twofa_enrolled=Kontam tagad ir ieslēgta divu faktoru autentifikācija. Saglabājiet savu vienreizējo kodu (%s), jo tas vairāk netiks parādīts! twofa_enrolled=Kontam tagad ir ieslēgta divu faktoru autentifikācija. Saglabājiet savu vienreizējo kodu (%s), jo tas vairāk netiks parādīts!
u2f_desc=Drošības atslēgas ir aparatūras ierīces, kas satur kriptogrāfiskās atslēgas. Tās var tikt izmantotas, lai nodrošinātu divu faktoru autentifikāciju. Drošības atslēgām ir jāatbalsta <a href="https://fidoalliance.org/">FIDO U2F</a> standarts.
u2f_require_twofa=Jābūt iespējotai divu faktoru autentifikācija, lai varētu izmantot drošības atslēgas.
u2f_register_key=Pievienot drošības atslēgu u2f_register_key=Pievienot drošības atslēgu
u2f_nickname=Segvārds u2f_nickname=Segvārds
u2f_press_button=Nospiediet pogu uz Jūsu drošības atslēgas, lai to reģistrētu. u2f_press_button=Nospiediet pogu uz Jūsu drošības atslēgas, lai to reģistrētu.
u2f_delete_key=Noņemt drošības atslēgu u2f_delete_key=Noņemt drošības atslēgu
u2f_delete_key_desc=Noņemot drošības atslēgu ar to vairs nebūs iespējams autorizēties. Vai turpināt?
manage_account_links=Pārvaldīt saistītos kontus manage_account_links=Pārvaldīt saistītos kontus
manage_account_links_desc=Šādi ārējie konti ir piesaistīti Jūsu Gitea kontam. manage_account_links_desc=Šādi ārējie konti ir piesaistīti Jūsu Gitea kontam.
@ -646,7 +634,6 @@ issues.new.open_milestone=Atvērtie atskaites punktus
issues.new.closed_milestone=Aizvērtie atskaites punkti issues.new.closed_milestone=Aizvērtie atskaites punkti
issues.new.assignees=Atbildīgie issues.new.assignees=Atbildīgie
issues.new.clear_assignees=Noņemt atbildīgo issues.new.clear_assignees=Noņemt atbildīgo
issues.new.no_assignees=Nav atbildīgā
issues.no_ref=Nav norādīts atzars/tags issues.no_ref=Nav norādīts atzars/tags
issues.create=Pieteikt problēmu issues.create=Pieteikt problēmu
issues.new_label=Jauna etiķete issues.new_label=Jauna etiķete
@ -1163,8 +1150,6 @@ branch.protected_deletion_failed=Atzars '%s' ir aizsargāts. To nevar izdzēst.
topic.manage_topics=Pārvaldīt tēmas topic.manage_topics=Pārvaldīt tēmas
topic.done=Gatavs topic.done=Gatavs
topic.count_prompt=Nevar pievienot vairāk kā 25 tēmas
topic.format_prompt=Tēmas nosaukumam ir jāsākas ar burtu vai ciparu, tas var saturēt defisi(-), kā arī tas nevar būt garāks par 35 simboliem
[org] [org]
org_name_holder=Organizācijas nosaukums org_name_holder=Organizācijas nosaukums
@ -1269,8 +1254,6 @@ dashboard.operation_switch=Pārslēgt
dashboard.operation_run=Palaist dashboard.operation_run=Palaist
dashboard.clean_unbind_oauth=Notīrīt nepiesaistītos OAuth savienojumus dashboard.clean_unbind_oauth=Notīrīt nepiesaistītos OAuth savienojumus
dashboard.clean_unbind_oauth_success=Visi nepiesaistītie OAuth savienojumu tika izdzēsti. dashboard.clean_unbind_oauth_success=Visi nepiesaistītie OAuth savienojumu tika izdzēsti.
dashboard.delete_inactivate_accounts=Dzēst visus neaktivizētos kontus
dashboard.delete_inactivate_accounts_success=Visi neaktivizētie konti tika izdzēsti.
dashboard.delete_repo_archives=Dzēst visu repozitoriju arhīvus dashboard.delete_repo_archives=Dzēst visu repozitoriju arhīvus
dashboard.delete_repo_archives_success=Visu repozitoriju arhīvi tika izdzēsti. dashboard.delete_repo_archives_success=Visu repozitoriju arhīvi tika izdzēsti.
dashboard.delete_missing_repos=Dzēst visus repozitorijus, kam trūkst Git failu dashboard.delete_missing_repos=Dzēst visus repozitorijus, kam trūkst Git failu
@ -1478,7 +1461,6 @@ config.db_path=Ceļš
config.service_config=Pakalpojuma konfigurācija config.service_config=Pakalpojuma konfigurācija
config.register_email_confirm=Reģistrējoties pieprasīt apstiprināt e-pasta adresi config.register_email_confirm=Reģistrējoties pieprasīt apstiprināt e-pasta adresi
config.disable_register=Atspējot lietotāju reģistrāciju config.disable_register=Atspējot lietotāju reģistrāciju
config.allow_only_external_registration=Ļaut reģistrēties tikai izmantojot ārējos pakalpojumus
config.enable_openid_signup=Iespējot reģistrāciju, izmantojot OpenID config.enable_openid_signup=Iespējot reģistrāciju, izmantojot OpenID
config.enable_openid_signin=Iespējot OpenID autorizāciju config.enable_openid_signin=Iespējot OpenID autorizāciju
config.show_registration_button=Rādīt reģistrēšanās pogu config.show_registration_button=Rādīt reģistrēšanās pogu

View File

@ -32,14 +32,8 @@ twofa_scratch=Eenmalige twee factor authenticatie code
passcode=PIN passcode=PIN
u2f_insert_key=Uw beveiligingssleutel invoegen u2f_insert_key=Uw beveiligingssleutel invoegen
u2f_sign_in=Druk op de knop op uw beveiligingssleutel. Als u een knop niet kunt vinden, deze opnieuw invoegen.
u2f_press_button=Druk op de knop op uw beveiligingssleutel… u2f_press_button=Druk op de knop op uw beveiligingssleutel…
u2f_use_twofa=Gebruik een twee-factor code van uw telefoon u2f_use_twofa=Gebruik een twee-factor code van uw telefoon
u2f_error=Wij kunnen niet uw beveiligingssleutel lezen!
u2f_unsupported_browser=Uw browser geen ondersteund U2F keys. Probeer een andere browser.
u2f_error_1=Er is een onbekende fout opgetreden. Probeer het opnieuw.
u2f_error_2=Zorg voor een versleutelde verbinding (https://) en een bezoek aan de juiste URL.
u2f_error_3=De server kan uw aanvraag niet verhandelen.
u2f_reload=Herladen u2f_reload=Herladen
repository=Repository repository=Repository

View File

@ -32,16 +32,16 @@ twofa_scratch=Código de backup da autenticação de dois fatores
passcode=Senha passcode=Senha
u2f_insert_key=Insira sua chave de segurança u2f_insert_key=Insira sua chave de segurança
u2f_sign_in=Pressione o botão na sua chave de segurança. Se você não encontrar um botão, insira-o novamente. u2f_sign_in=Pressione o botão na sua chave de segurança. Se a sua chave de segurança não tiver um botão, insira-a novamente.
u2f_press_button=Por favor, pressione o botão na sua chave de segurança... u2f_press_button=Por favor, pressione o botão na sua chave de segurança...
u2f_use_twofa=Use um código de dois fatores no seu telefone u2f_use_twofa=Use um código de dois fatores no seu telefone
u2f_error=Não conseguimos ler sua chave de segurança! u2f_error=Não foi possível ler sua chave de segurança.
u2f_unsupported_browser=Seu navegador não suporta chaves U2F. Por favor, tente outro navegador. u2f_unsupported_browser=Seu navegador não suporta chaves de segurança U2F.
u2f_error_1=Ocorreu um erro desconhecido. Por favor, tente novamente. u2f_error_1=Ocorreu um erro desconhecido. Por favor, tente novamente.
u2f_error_2=Por favor, certifique-se de que você está usando uma conexão criptografada (https://) e visite a URL correta. u2f_error_2=Por favor, certifique-se de usar a URL correto, criptografado (https://).
u2f_error_3=O servidor não pôde prosseguir com sua solicitação. u2f_error_3=O servidor não pôde processar sua solicitação.
u2f_error_4=A chave apresentada não é elegível para esta solicitação. Se você tentar registrá-la, certifique-se de que a chave já não é registrada. u2f_error_4=A chave de segurança não é permitida para esta solicitação. Por favor, certifique-se que a chave já não está registrada.
u2f_error_5=Tempo limite atingido antes de sua chave poder ser lida. Por favor, recarregue para tentar novamente. u2f_error_5=Tempo limite atingido antes de sua chave poder ser lida. Por favor, recarregue esta página e tente novamente.
u2f_reload=Recarregar u2f_reload=Recarregar
repository=Repositório repository=Repositório
@ -53,7 +53,7 @@ new_mirror=Novo mirror
new_fork=Novo Fork de Repositório new_fork=Novo Fork de Repositório
new_org=Nova organização new_org=Nova organização
manage_org=Gerenciar organizações manage_org=Gerenciar organizações
admin_panel=Administração do site admin_panel=Administração geral
account_settings=Configurações da conta account_settings=Configurações da conta
settings=Configurações settings=Configurações
your_profile=Perfil your_profile=Perfil
@ -93,7 +93,7 @@ no_admin_and_disable_registration=Você não pode desabilitar o auto-cadastro do
err_empty_admin_password=A senha do administrador não pode ser em branco. err_empty_admin_password=A senha do administrador não pode ser em branco.
general_title=Configurações gerais general_title=Configurações gerais
app_name=Título do site app_name=Nome do servidor
app_name_helper=Você pode inserir o nome da empresa aqui. app_name_helper=Você pode inserir o nome da empresa aqui.
repo_path=Caminho raíz do repositório repo_path=Caminho raíz do repositório
repo_path_helper=Todos os repositórios remotos do Git serão salvos neste diretório. repo_path_helper=Todos os repositórios remotos do Git serão salvos neste diretório.
@ -130,7 +130,7 @@ federated_avatar_lookup=Habilitar avatares federativos
federated_avatar_lookup_popup=Habilitar a busca federativa de avatares a usar o serviço federativo de código aberto baseado no libravatar. federated_avatar_lookup_popup=Habilitar a busca federativa de avatares a usar o serviço federativo de código aberto baseado no libravatar.
disable_registration=Desabilitar auto-cadastro disable_registration=Desabilitar auto-cadastro
disable_registration_popup=Desabilitar auto-cadastro de usuário. Somente os administradores serão capazes de criar novas contas de usuário. disable_registration_popup=Desabilitar auto-cadastro de usuário. Somente os administradores serão capazes de criar novas contas de usuário.
allow_only_external_registration_popup=Habilitar o cadastro apenas por meio de serviços externos. allow_only_external_registration_popup=Permitir cadastro somente por meio de serviços externos
openid_signin=Habilitar acesso via OpenID openid_signin=Habilitar acesso via OpenID
openid_signin_popup=Habilitar o acesso de usuários via OpenID. openid_signin_popup=Habilitar o acesso de usuários via OpenID.
openid_signup=Habilitar o auto-cadastro via OpenID openid_signup=Habilitar o auto-cadastro via OpenID
@ -463,13 +463,13 @@ then_enter_passcode=E insira a senha mostrada no aplicativo:
passcode_invalid=Esse código de acesso é inválido. Tente novamente. passcode_invalid=Esse código de acesso é inválido. Tente novamente.
twofa_enrolled=Sua conta foi inscrita na autenticação de dois fatores. Armazene seu token de backup (%s) em um local seguro, pois ele é exibido apenas uma vez! twofa_enrolled=Sua conta foi inscrita na autenticação de dois fatores. Armazene seu token de backup (%s) em um local seguro, pois ele é exibido apenas uma vez!
u2f_desc=Chaves de segurança são dispositivos de hardware que contém chaves de criptografia. Elas podem ser usadas para autenticação de dois fatores. A chave de segurança deve suportar o padrão <a href="https://fidoalliance.org/">FIDO U2F</a>. u2f_desc=Chaves de segurança são dispositivos de hardware contendo chaves criptográficas. Eles podem ser usados para autenticação de dois fatores. As chaves de segurança devem suportar o padrão <a rel="noreferrer" href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=Autenticação de dois fatores deve estar inscrita para usar chaves de segurança. u2f_require_twofa=Sua conta deve estar inscrita na autenticação de dois fatores para usar as chaves de segurança.
u2f_register_key=Adicionar chave de segurança u2f_register_key=Adicionar chave de segurança
u2f_nickname=Apelido u2f_nickname=Apelido
u2f_press_button=Pressione o botão na sua chave de segurança para registrá-la. u2f_press_button=Pressione o botão na sua chave de segurança para registrá-la.
u2f_delete_key=Remover chave de segurança u2f_delete_key=Remover chave de segurança
u2f_delete_key_desc=Se você remover uma chave de segurança você não poderá mais acessar com ela. Tem certeza? u2f_delete_key_desc=Se você remover uma chave de segurança, não poderá mais entrar com ela. Continuar?
manage_account_links=Gerenciar contas vinculadas manage_account_links=Gerenciar contas vinculadas
manage_account_links_desc=Estas contas externas estão vinculadas a sua conta de Gitea. manage_account_links_desc=Estas contas externas estão vinculadas a sua conta de Gitea.
@ -597,7 +597,7 @@ editor.name_your_file=Nomeie o seu arquivo…
editor.filename_help=Adicione um diretório digitando seu nome seguido por uma barra ('/'). Remova um diretório digitando o backspace no início do campo de entrada. editor.filename_help=Adicione um diretório digitando seu nome seguido por uma barra ('/'). Remova um diretório digitando o backspace no início do campo de entrada.
editor.or=ou editor.or=ou
editor.cancel_lower=Cancelar editor.cancel_lower=Cancelar
editor.commit_changes=Confirmar alterações editor.commit_changes=Realizar commit das alterações
editor.add_tmpl=Adicionar '%s/<filename>' editor.add_tmpl=Adicionar '%s/<filename>'
editor.add=Adicionar '%s' editor.add=Adicionar '%s'
editor.update=Atualizar '%s' editor.update=Atualizar '%s'
@ -650,7 +650,7 @@ issues.new.open_milestone=Marcos abertos
issues.new.closed_milestone=Marcos fechados issues.new.closed_milestone=Marcos fechados
issues.new.assignees=Responsáveis issues.new.assignees=Responsáveis
issues.new.clear_assignees=Limpar responsáveis issues.new.clear_assignees=Limpar responsáveis
issues.new.no_assignees=Nenhum responsável issues.new.no_assignees=Sem responsável
issues.no_ref=Nenhum branch/tag especificado issues.no_ref=Nenhum branch/tag especificado
issues.create=Criar issue issues.create=Criar issue
issues.new_label=Nova etiqueta issues.new_label=Nova etiqueta
@ -682,7 +682,7 @@ issues.filter_milestone_no_select=Todos os marcos
issues.filter_assignee=Atribuído issues.filter_assignee=Atribuído
issues.filter_assginee_no_select=Todos os responsáveis issues.filter_assginee_no_select=Todos os responsáveis
issues.filter_type=Tipo issues.filter_type=Tipo
issues.filter_type.all_issues=Todos os issues issues.filter_type.all_issues=Todas as issues
issues.filter_type.assigned_to_you=Atribuídos a você issues.filter_type.assigned_to_you=Atribuídos a você
issues.filter_type.created_by_you=Criado por você issues.filter_type.created_by_you=Criado por você
issues.filter_type.mentioning_you=Mencionando você issues.filter_type.mentioning_you=Mencionando você
@ -781,6 +781,33 @@ issues.due_date_added=adicionou a data limite %s %s
issues.due_date_modified=modificou a data limite para %s ao invés de %s %s issues.due_date_modified=modificou a data limite para %s ao invés de %s %s
issues.due_date_remove=removeu a data limite %s %s issues.due_date_remove=removeu a data limite %s %s
issues.due_date_overdue=Em atraso issues.due_date_overdue=Em atraso
issues.due_date_invalid=A data limite é inválida ou está fora do intervalo. Por favor, use o formato 'dd/mm/aaaa'.
issues.dependency.title=Dependências
issues.dependency.issue_no_dependencies=Esta issue atualmente não tem dependências.
issues.dependency.pr_no_dependencies=Este pull request atualmente não tem dependências.
issues.dependency.add=Adicione...
issues.dependency.cancel=Cancelar
issues.dependency.remove=Remover
issues.dependency.added_dependency=`<a href="%[1]s">%[2]s</a> adicionou uma nova dependência %[3]s`
issues.dependency.removed_dependency=`<a href="%[1]s">%[2]s</a> removeu uma dependência %[3]s`
issues.dependency.issue_closing_blockedby=Fechamento deste pull request está bloqueado pelas seguintes issues
issues.dependency.pr_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues
issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues
issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues
issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la.
issues.dependency.pr_close_blocked=Você precisa fechar todas issues que bloqueiam este pull request antes de poder fazer o merge.
issues.dependency.blocks_short=Bloqueia
issues.dependency.blocked_by_short=Depende de
issues.dependency.remove_header=Remover dependência
issues.dependency.issue_remove_text=Isto removerá a dependência desta issue. Continuar?
issues.dependency.pr_remove_text=Isto removerá a dependência deste pull request. Continuar?
issues.dependency.setting=Habilitar dependências para issues e pull requests
issues.dependency.add_error_same_issue=Você não pode fazer uma issue depender de si mesma.
issues.dependency.add_error_dep_issue_not_exist=Issue dependente não existe.
issues.dependency.add_error_dep_not_exist=Dependência não existe.
issues.dependency.add_error_dep_exists=Dependência já existe.
issues.dependency.add_error_cannot_create_circular=Você não pode criar uma dependência com duas issues bloqueando uma a outra.
issues.dependency.add_error_dep_not_same_repo=Ambas as issues devem estar no mesmo repositório.
pulls.desc=Habilitar solicitações de merge e revisões de código. pulls.desc=Habilitar solicitações de merge e revisões de código.
pulls.new=Novo pull request pulls.new=Novo pull request
@ -793,7 +820,7 @@ pulls.no_results=Nada encontrado.
pulls.nothing_to_compare=Estes branches são iguais. Não há nenhuma necessidade para criar um pull request. pulls.nothing_to_compare=Estes branches são iguais. Não há nenhuma necessidade para criar um pull request.
pulls.has_pull_request=`Um pull request entre esses branches já existe: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>` pulls.has_pull_request=`Um pull request entre esses branches já existe: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
pulls.create=Criar pull request pulls.create=Criar pull request
pulls.title_desc=quer mesclar %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code> pulls.title_desc=quer realizar o merge de %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code>
pulls.merged_title_desc=fez merge dos %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code> %[4]s pulls.merged_title_desc=fez merge dos %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code> %[4]s
pulls.tab_conversation=Conversação pulls.tab_conversation=Conversação
pulls.tab_commits=Commits pulls.tab_commits=Commits
@ -818,14 +845,14 @@ milestones.new=Novo marco
milestones.open_tab=%d Aberto milestones.open_tab=%d Aberto
milestones.close_tab=%d Fechado milestones.close_tab=%d Fechado
milestones.closed=Fechado %s milestones.closed=Fechado %s
milestones.no_due_date=Sem prazo milestones.no_due_date=Sem data limite
milestones.open=Reabrir milestones.open=Reabrir
milestones.close=Fechar milestones.close=Fechar
milestones.new_subheader=Marcos organizam as issues e acompanham o progresso. milestones.new_subheader=Marcos organizam as issues e acompanham o progresso.
milestones.create=Criar marco milestones.create=Criar marco
milestones.title=Título milestones.title=Título
milestones.desc=Descrição milestones.desc=Descrição
milestones.due_date=Prazo (opcional) milestones.due_date=Data limite (opcional)
milestones.clear=Limpar milestones.clear=Limpar
milestones.invalid_due_date_format=Formato da data limite deve ser 'dd/mm/aaaa'. milestones.invalid_due_date_format=Formato da data limite deve ser 'dd/mm/aaaa'.
milestones.create_success=O marco '%s' foi criado. milestones.create_success=O marco '%s' foi criado.
@ -837,8 +864,8 @@ milestones.edit_success=O marco '%s' foi atualizado.
milestones.deletion=Excluir marco milestones.deletion=Excluir marco
milestones.deletion_desc=A exclusão deste marco irá removê-lo de todas as issues. Tem certeza que deseja continuar? milestones.deletion_desc=A exclusão deste marco irá removê-lo de todas as issues. Tem certeza que deseja continuar?
milestones.deletion_success=O marco foi excluído. milestones.deletion_success=O marco foi excluído.
milestones.filter_sort.closest_due_date=Prazo mais próximo milestones.filter_sort.closest_due_date=Data limite mais próxima
milestones.filter_sort.furthest_due_date=Prazo mais longe milestones.filter_sort.furthest_due_date=Data limite mais distante
milestones.filter_sort.least_complete=Menos completo milestones.filter_sort.least_complete=Menos completo
milestones.filter_sort.most_complete=Mais completo milestones.filter_sort.most_complete=Mais completo
milestones.filter_sort.most_issues=Com mais issues milestones.filter_sort.most_issues=Com mais issues
@ -894,15 +921,15 @@ activity.closed_issues_count_1=Issue fechada
activity.closed_issues_count_n=Issues fechadas activity.closed_issues_count_n=Issues fechadas
activity.title.issues_1=+%d Issue activity.title.issues_1=+%d Issue
activity.title.issues_n=+%d Issues activity.title.issues_n=+%d Issues
activity.title.issues_closed_by=%s fechado por %s activity.title.issues_closed_by=%s fechada por %s
activity.title.issues_created_by=%s criado por %s activity.title.issues_created_by=%s criada por %s
activity.closed_issue_label=Fechado activity.closed_issue_label=Fechado
activity.new_issues_count_1=Nova issue activity.new_issues_count_1=Nova issue
activity.new_issues_count_n=Novas issues activity.new_issues_count_n=Novas issues
activity.new_issue_label=Aberto activity.new_issue_label=Aberto
activity.title.unresolved_conv_1=%d conversa não resolvida activity.title.unresolved_conv_1=%d conversa não resolvida
activity.title.unresolved_conv_n=%d conversas não resolvidas activity.title.unresolved_conv_n=%d conversas não resolvidas
activity.unresolved_conv_desc=Estes problemas foram recentemente alterados e pull requests ainda não foram resolvidos. activity.unresolved_conv_desc=Estas issues foram recentemente alteradas e pull requests ainda não foram resolvidos.
activity.unresolved_conv_label=Abrir activity.unresolved_conv_label=Abrir
activity.title.releases_1=%d Versão activity.title.releases_1=%d Versão
activity.title.releases_n=%d Versões activity.title.releases_n=%d Versões
@ -975,7 +1002,7 @@ settings.confirm_wiki_delete=Excluir dados da wiki
settings.wiki_deletion_success=Os dados da wiki do repositório foi excluídos. settings.wiki_deletion_success=Os dados da wiki do repositório foi excluídos.
settings.delete=Excluir este repositório settings.delete=Excluir este repositório
settings.delete_desc=A exclusão de um repositório é permanente e não pode ser desfeita. settings.delete_desc=A exclusão de um repositório é permanente e não pode ser desfeita.
settings.delete_notices_1=-Esta operação <strong>NÃO PODERÁ</strong> ser desfeita. settings.delete_notices_1=- Esta operação <strong>NÃO PODERÁ</strong> ser desfeita.
settings.delete_notices_2=- Essa operação excluirá permanentemente o repositório <strong>%s</strong>, incluindo código, issues, comentários, dados da wiki e configurações do colaborador. settings.delete_notices_2=- Essa operação excluirá permanentemente o repositório <strong>%s</strong>, incluindo código, issues, comentários, dados da wiki e configurações do colaborador.
settings.delete_notices_fork_1=- Forks deste repositório se tornarão independentes após a exclusão. settings.delete_notices_fork_1=- Forks deste repositório se tornarão independentes após a exclusão.
settings.deletion_success=O repositório foi excluído. settings.deletion_success=O repositório foi excluído.
@ -1169,7 +1196,7 @@ branch.protected_deletion_failed=A branch '%s' está protegida. Ela não pode se
topic.manage_topics=Gerenciar Tópicos topic.manage_topics=Gerenciar Tópicos
topic.done=Feito topic.done=Feito
topic.count_prompt=Você não pode selecionar mais de 25 tópicos 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 topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
[org] [org]
org_name_holder=Nome da organização org_name_holder=Nome da organização
@ -1443,7 +1470,7 @@ auths.deletion_success=A fonte de autenticação foi excluída.
auths.login_source_exist=A fonte de autenticação '%s' já existe. auths.login_source_exist=A fonte de autenticação '%s' já existe.
config.server_config=Configuração do servidor config.server_config=Configuração do servidor
config.app_name=Título do site config.app_name=Nome do servidor
config.app_ver=Versão do Gitea config.app_ver=Versão do Gitea
config.app_url=URL base do Gitea config.app_url=URL base do Gitea
config.custom_conf=Caminho do Arquivo de Configuração config.custom_conf=Caminho do Arquivo de Configuração
@ -1483,7 +1510,7 @@ config.db_path=Caminho
config.service_config=Configuração do serviço config.service_config=Configuração do serviço
config.register_email_confirm=Exigir confirmação de e-mail para se cadastrar config.register_email_confirm=Exigir confirmação de e-mail para se cadastrar
config.disable_register=Desabilitar auto-cadastro config.disable_register=Desabilitar auto-cadastro
config.allow_only_external_registration=Habilitar o cadastro apenas por meio de serviços externos config.allow_only_external_registration=Permitir cadastro somente por meio de serviços externos
config.enable_openid_signup=Habilitar o auto-cadastro via OpenID config.enable_openid_signup=Habilitar o auto-cadastro via OpenID
config.enable_openid_signin=Habilitar acesso via OpenID config.enable_openid_signin=Habilitar acesso via OpenID
config.show_registration_button=Mostrar botão de cadastro config.show_registration_button=Mostrar botão de cadastro
@ -1499,6 +1526,7 @@ config.enable_timetracking=Habilitar contador de tempo
config.default_enable_timetracking=Habilitar o contador de tempo por padrão config.default_enable_timetracking=Habilitar o contador de tempo por padrão
config.default_allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo config.default_allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo
config.no_reply_address=Ocultar domínio de e-mail config.no_reply_address=Ocultar domínio de e-mail
config.default_enable_dependencies=Habilitar dependências de issue por padrão
config.webhook_config=Configuração de Hook da Web config.webhook_config=Configuração de Hook da Web
config.queue_length=Tamanho da fila config.queue_length=Tamanho da fila
@ -1586,7 +1614,7 @@ create_repo=criou o repositório <a href="%s">%s</a>
rename_repo=renomeou o repositório <code>%[1]s</code> para <a href="%[2]s">%[3]s</a> rename_repo=renomeou o repositório <code>%[1]s</code> para <a href="%[2]s">%[3]s</a>
commit_repo=fez push para <a href="%[1]s/src/%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a> commit_repo=fez push para <a href="%[1]s/src/%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a>
create_issue=`abriu a issue <a href="%s/issues/%s">%s#%[2]s</a>` create_issue=`abriu a issue <a href="%s/issues/%s">%s#%[2]s</a>`
close_issue=`fechou issue <a href="%s/issues/%s">%s#%[2]s</a>` close_issue=`fechou a issue <a href="%s/issues/%s">%s#%[2]s</a>`
reopen_issue=`reabriu a issue <a href="%s/issues/%s">%s#%[2]s</a>` reopen_issue=`reabriu a issue <a href="%s/issues/%s">%s#%[2]s</a>`
create_pull_request=`criou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>` create_pull_request=`criou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
close_pull_request=`fechou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>` close_pull_request=`fechou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>`

View File

@ -32,16 +32,8 @@ twofa_scratch=Двухфакторный scratch-код
passcode=Пароль passcode=Пароль
u2f_insert_key=Вставьте ключ безопасности u2f_insert_key=Вставьте ключ безопасности
u2f_sign_in=Нажмите кнопку на ключе безопасности. Если вы не можете найти кнопку, вставьте его снова.
u2f_press_button=Пожалуйста нажмите кнопку на вашем ключе безопасности… u2f_press_button=Пожалуйста нажмите кнопку на вашем ключе безопасности…
u2f_use_twofa=Используйте двухфакторный код с телефона u2f_use_twofa=Используйте двухфакторный код с телефона
u2f_error=Мы не можем прочитать ваш ключ безопасности!
u2f_unsupported_browser=Ваш браузер не поддерживает U2F ключи. Попробуйте другой браузер.
u2f_error_1=Произошла неизвестная ошибка. Повторите попытку.
u2f_error_2=Пожалуйста, убедитесь, что вы используете зашифрованное соединение (https://) и используете правильный URL.
u2f_error_3=Серверу не удалось обработать ваш запрос.
u2f_error_4=Представленный ключ не подходит для этого запроса. Если вы пытаетесь зарегистрировать его, убедитесь, что ключ еще не зарегистрирован.
u2f_error_5=Таймаут достигнут до того, как ваш ключ был прочитан. Перезагрузите, чтобы повторить попытку.
u2f_reload=Обновить u2f_reload=Обновить
repository=Репозиторий repository=Репозиторий
@ -130,7 +122,6 @@ federated_avatar_lookup=Включить федеративные аватары
federated_avatar_lookup_popup=Включите поиск федеративного аватара для использования службы с открытым исходным кодом на основе libravatar. federated_avatar_lookup_popup=Включите поиск федеративного аватара для использования службы с открытым исходным кодом на основе libravatar.
disable_registration=Отключить самостоятельную регистрацию disable_registration=Отключить самостоятельную регистрацию
disable_registration_popup=Запретить самостоятельную регистрацию. Только администраторы смогут создавать новые учетные записи пользователей. disable_registration_popup=Запретить самостоятельную регистрацию. Только администраторы смогут создавать новые учетные записи пользователей.
allow_only_external_registration_popup=Включить регистрацию только через сторонние сервисы.
openid_signin=Включение входа через OpenID openid_signin=Включение входа через OpenID
openid_signin_popup=Включение входа через OpenID. openid_signin_popup=Включение входа через OpenID.
openid_signup=Включить саморегистрацию OpenID openid_signup=Включить саморегистрацию OpenID
@ -463,13 +454,10 @@ then_enter_passcode=И введите пароль, показанный в пр
passcode_invalid=Неверный пароль. попробуйте снова. passcode_invalid=Неверный пароль. попробуйте снова.
twofa_enrolled=Для вашего аккаунта была включена двухфакторная аутентификация. Сохраните ваш scratch-токен (%s), он не сохраняется на сервере! twofa_enrolled=Для вашего аккаунта была включена двухфакторная аутентификация. Сохраните ваш scratch-токен (%s), он не сохраняется на сервере!
u2f_desc=Ключами безопасности являются аппаратные устройства, содержащие криптографические ключи. Они могут использоваться для двухфакторной аутентификации. Ключ безопасности должен поддерживать стандарт <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=Для использования ключей безопасности необходимо включить двухфакторную аутентификацию.
u2f_register_key=Добавить ключ безопасности u2f_register_key=Добавить ключ безопасности
u2f_nickname=Имя пользователя u2f_nickname=Имя пользователя
u2f_press_button=Нажмите кнопку на ключе безопасности, чтобы зарегистрировать его. u2f_press_button=Нажмите кнопку на ключе безопасности, чтобы зарегистрировать его.
u2f_delete_key=Удалить ключ безопасности u2f_delete_key=Удалить ключ безопасности
u2f_delete_key_desc=Если вы удалите ключ безопасности, вы не сможете использовать его для входа. Вы уверены?
manage_account_links=Управление привязанными аккаунтами manage_account_links=Управление привязанными аккаунтами
manage_account_links_desc=Эти внешние аккаунты привязаны к вашему аккаунту Gitea. manage_account_links_desc=Эти внешние аккаунты привязаны к вашему аккаунту Gitea.
@ -650,7 +638,6 @@ issues.new.open_milestone=Открыть этап
issues.new.closed_milestone=Завершенные этапы issues.new.closed_milestone=Завершенные этапы
issues.new.assignees=Назначенные issues.new.assignees=Назначенные
issues.new.clear_assignees=Убрать ответственных issues.new.clear_assignees=Убрать ответственных
issues.new.no_assignees=Никто не назначен
issues.no_ref=Не указана ветка или тэг issues.no_ref=Не указана ветка или тэг
issues.create=Добавить задачу issues.create=Добавить задачу
issues.new_label=Новая метка issues.new_label=Новая метка
@ -1168,8 +1155,6 @@ branch.protected_deletion_failed=Ветка '%s' защищена. Её нель
topic.manage_topics=Редактировать тематические метки topic.manage_topics=Редактировать тематические метки
topic.done=Сохранить topic.done=Сохранить
topic.count_prompt=Вы не можете выбрать более 25 тем
topic.format_prompt=Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов
[org] [org]
org_name_holder=Название организации org_name_holder=Название организации
@ -1274,8 +1259,6 @@ dashboard.operation_switch=Переключить
dashboard.operation_run=Запуск dashboard.operation_run=Запуск
dashboard.clean_unbind_oauth=Очистить список незавершённых авторизаций OAuth dashboard.clean_unbind_oauth=Очистить список незавершённых авторизаций OAuth
dashboard.clean_unbind_oauth_success=Все незавершённые связи OAuth были удалены. dashboard.clean_unbind_oauth_success=Все незавершённые связи OAuth были удалены.
dashboard.delete_inactivate_accounts=Удалить все неактивированные учетные записи
dashboard.delete_inactivate_accounts_success=Все не активированные учетные записи были удалены.
dashboard.delete_repo_archives=Удаление всех архивов репозиториев dashboard.delete_repo_archives=Удаление всех архивов репозиториев
dashboard.delete_repo_archives_success=Все архивы репозиториев удалены. dashboard.delete_repo_archives_success=Все архивы репозиториев удалены.
dashboard.delete_missing_repos=Удалить все записи о репозиториях с отсутствующими файлами Git dashboard.delete_missing_repos=Удалить все записи о репозиториях с отсутствующими файлами Git
@ -1483,7 +1466,6 @@ config.db_path=Путь
config.service_config=Сервисная конфигурация config.service_config=Сервисная конфигурация
config.register_email_confirm=Требуется подтверждение по электронной почте config.register_email_confirm=Требуется подтверждение по электронной почте
config.disable_register=Отключить самостоятельную регистрацию config.disable_register=Отключить самостоятельную регистрацию
config.allow_only_external_registration=Включить регистрацию только через сторонние сервисы
config.enable_openid_signup=Включить cамостоятельную регистрацию OpenID config.enable_openid_signup=Включить cамостоятельную регистрацию OpenID
config.enable_openid_signin=Включение входа через OpenID config.enable_openid_signin=Включение входа через OpenID
config.show_registration_button=Показать кнопку регистрации config.show_registration_button=Показать кнопку регистрации

View File

@ -32,16 +32,8 @@ twofa_scratch=Tvåfaktorsskrapkod
passcode=Kod passcode=Kod
u2f_insert_key=Sätt i din säkerhetsnyckel u2f_insert_key=Sätt i din säkerhetsnyckel
u2f_sign_in=Tryck på knappen på din säkerhetsnyckel. Om du inte kan hitta en knapp, dra ur och sätt i nyckeln igen.
u2f_press_button=Vänligen tryck på knappen på din säkerhetsnyckel… u2f_press_button=Vänligen tryck på knappen på din säkerhetsnyckel…
u2f_use_twofa=Använd en tvåfaktorskod från din telefon u2f_use_twofa=Använd en tvåfaktorskod från din telefon
u2f_error=Vi kan inte läsa din säkerhetsnyckel!
u2f_unsupported_browser=Din webbläsare stödjer inte U2F-nycklar. Vänligen prova en annan webbläsare.
u2f_error_1=Ett okänt fel uppstod. Vänligen försök igen.
u2f_error_2=Kontrollera att du använder en krypterad anslutning (https://) och besöker korrekt länk.
u2f_error_3=Servern kunde inte hantera din förfrågan.
u2f_error_4=Den givna nyckeln är inte behörig för denna förfrågan. Om du försöker registrera den, se till att den inte redan är registrerad.
u2f_error_5=Timeout uppnådd innan nyckeln kunde bli läst. Vänligen ladda om sidan och för att försök igen.
u2f_reload=Ladda om u2f_reload=Ladda om
repository=Utvecklingskatalog repository=Utvecklingskatalog
@ -129,7 +121,6 @@ disable_gravatar_popup=Inaktivera Gravatar- och avatarskällor från tredjepart.
federated_avatar_lookup_popup=Använd libravatar vid förenad uppslagning av avatarer. federated_avatar_lookup_popup=Använd libravatar vid förenad uppslagning av avatarer.
disable_registration=Inaktivera Självregistrering disable_registration=Inaktivera Självregistrering
disable_registration_popup=Inaktivera självregistrering av användare. Endast administratörer kommer kunna skapa nya konton. disable_registration_popup=Inaktivera självregistrering av användare. Endast administratörer kommer kunna skapa nya konton.
allow_only_external_registration_popup=Aktivera registrering enbart via externa tjänster.
openid_signin=Aktivera OpenID-inloggning openid_signin=Aktivera OpenID-inloggning
openid_signin_popup=Aktivera användarinloggning via OpenID. openid_signin_popup=Aktivera användarinloggning via OpenID.
openid_signup=Aktivera självregistrering genom OpenID openid_signup=Aktivera självregistrering genom OpenID
@ -462,13 +453,10 @@ then_enter_passcode=Och ange den lösenkod som visas i programmet:
passcode_invalid=Koden är ogiltig. Försök igen. passcode_invalid=Koden är ogiltig. Försök igen.
twofa_enrolled=Tvåfaktorsautentisering har aktiverats för ditt konto. Förvara din skrapkod (%s) på en säker plats eftersom den bara visas en gång! twofa_enrolled=Tvåfaktorsautentisering har aktiverats för ditt konto. Förvara din skrapkod (%s) på en säker plats eftersom den bara visas en gång!
u2f_desc=Säkerhetsnycklar är maskinvaruenheter som innehåller kryptografiska nycklar. De kan användas för tvåfaktorautentisering. Säkerhetsnyckeln måste dock stödja <a href="https://fidoalliance.org/">FIDO U2F</a> standarden.
u2f_require_twofa=Tvåfaktorautentisering måste aktiveras för att kunna använda säkerhetsnycklar.
u2f_register_key=Lägg till säkerhetsnyckel u2f_register_key=Lägg till säkerhetsnyckel
u2f_nickname=Smeknamn u2f_nickname=Smeknamn
u2f_press_button=Tryck på knappen på din säkerhetsnyckel för att registrera den. u2f_press_button=Tryck på knappen på din säkerhetsnyckel för att registrera den.
u2f_delete_key=Ta Bort Säkerhetsnyckel u2f_delete_key=Ta Bort Säkerhetsnyckel
u2f_delete_key_desc=Om du tar bort en säkerhetsnyckel kommer du inte kunna logga in med den längre. Är du säker?
manage_account_links=Hantera Länkade Konton manage_account_links=Hantera Länkade Konton
manage_account_links_desc=Dessa externa konton är länkade till ditt Gitea-konto. manage_account_links_desc=Dessa externa konton är länkade till ditt Gitea-konto.
@ -646,7 +634,6 @@ issues.new.open_milestone=Öppna Milstenar
issues.new.closed_milestone=Stängda Milstenar issues.new.closed_milestone=Stängda Milstenar
issues.new.assignees=Tilldelade issues.new.assignees=Tilldelade
issues.new.clear_assignees=Rensa tilldelade issues.new.clear_assignees=Rensa tilldelade
issues.new.no_assignees=Ingen tilldelad
issues.no_ref=Ingen branch/Tag specificerad issues.no_ref=Ingen branch/Tag specificerad
issues.create=Skapa Ärende issues.create=Skapa Ärende
issues.new_label=Ny etikett issues.new_label=Ny etikett
@ -1127,8 +1114,6 @@ branch.protected_deletion_failed=Branch '%s' är skyddad. Den kan inte bli bortt
topic.manage_topics=Hantera ämnen topic.manage_topics=Hantera ämnen
topic.done=Klar topic.done=Klar
topic.count_prompt=Du kan inte markera mer än 25 ämnen
topic.format_prompt=Ämnen måste starta med en bokstav eller nummer, kan inkludera bindestreck(-) och får inte vara längre än 35 tecken långt
[org] [org]
org_name_holder=Organisationsnamn org_name_holder=Organisationsnamn
@ -1229,8 +1214,6 @@ dashboard.operation_switch=Byt till
dashboard.operation_run=Kör dashboard.operation_run=Kör
dashboard.clean_unbind_oauth=Rena obundna OAuth anslutningar dashboard.clean_unbind_oauth=Rena obundna OAuth anslutningar
dashboard.clean_unbind_oauth_success=Alla obundna OAuth anslutningar har raderats. dashboard.clean_unbind_oauth_success=Alla obundna OAuth anslutningar har raderats.
dashboard.delete_inactivate_accounts=Ta bort alla inaktiva konton
dashboard.delete_inactivate_accounts_success=Alla inaktiva konton har tagits bort.
dashboard.delete_repo_archives=Ta bort alla utvecklingskatalogers arkiv dashboard.delete_repo_archives=Ta bort alla utvecklingskatalogers arkiv
dashboard.delete_missing_repos=Ta bort alla utvecklingskataloger som saknar filer specifika för Git dashboard.delete_missing_repos=Ta bort alla utvecklingskataloger som saknar filer specifika för Git
dashboard.delete_missing_repos_success=Alla utvecklingskataloger som saknade sina Git-filer har tagits bort. dashboard.delete_missing_repos_success=Alla utvecklingskataloger som saknade sina Git-filer har tagits bort.
@ -1417,7 +1400,6 @@ config.db_path=Sökväg
config.service_config=Tjänstkonfiguration config.service_config=Tjänstkonfiguration
config.register_email_confirm=Kräv mejlbekräftelse för att registrera config.register_email_confirm=Kräv mejlbekräftelse för att registrera
config.disable_register=Inaktivera självregistrering config.disable_register=Inaktivera självregistrering
config.allow_only_external_registration=Aktivera registrering enbart genom externa tjänster
config.enable_openid_signup=Aktivera självregistrering genom OpenID config.enable_openid_signup=Aktivera självregistrering genom OpenID
config.enable_openid_signin=Aktivera OpenID-inloggning config.enable_openid_signin=Aktivera OpenID-inloggning
config.show_registration_button=Visa registreringsknapp config.show_registration_button=Visa registreringsknapp

View File

@ -32,16 +32,8 @@ twofa_scratch=Двофакторний одноразовий пароль
passcode=Код доступу passcode=Код доступу
u2f_insert_key=Вставте ключ безпеки u2f_insert_key=Вставте ключ безпеки
u2f_sign_in=Натисніть кнопку на ключі безпеки. Якщо не вдається знайти кнопки, повторно вставте ключ.
u2f_press_button=Будь ласка, натисніть кнопку на ключі захисту... u2f_press_button=Будь ласка, натисніть кнопку на ключі захисту...
u2f_use_twofa=Використовуйте дво-факторний код з вашого телефону u2f_use_twofa=Використовуйте дво-факторний код з вашого телефону
u2f_error=Неможливо прочитати ваш ключ безпеки!
u2f_unsupported_browser=Ваш браузер не підтримує U2F ключі. Будь ласка, спробуйте інший браузер.
u2f_error_1=Сталася невідома помилка. Спробуйте ще раз.
u2f_error_2=Переконайтеся, що ви використовуєте зашифроване з'єднання (https://) та відвідуєте правильну URL-адресу.
u2f_error_3=Сервер не може обробити, ваш запит.
u2f_error_4=Представлений ключ не дає право на цей запит. Якщо спробуєте зареєструвати його, переконайтеся, що ключ ще не зареєстровано.
u2f_error_5=Таймаут досягнуто до того, як ваш ключ можна буде прочитати. Перезавантажте, щоб повторити спробу.
u2f_reload=Оновити u2f_reload=Оновити
repository=Репозиторій repository=Репозиторій
@ -130,7 +122,6 @@ federated_avatar_lookup=Увімкнути федеративні аватари
federated_avatar_lookup_popup=Увімкнути зовнішний Аватар за допомогою Libravatar. federated_avatar_lookup_popup=Увімкнути зовнішний Аватар за допомогою Libravatar.
disable_registration=Вимкнути самостійну реєстрацію disable_registration=Вимкнути самостійну реєстрацію
disable_registration_popup=Вимкнути самостійну реєстрацію користувачів, тільки адміністратор може створювати нові облікові записи. disable_registration_popup=Вимкнути самостійну реєстрацію користувачів, тільки адміністратор може створювати нові облікові записи.
allow_only_external_registration_popup=Включити реєстрацію тільки через зовнішні сервіси.
openid_signin=Увімкнути реєстрацію за допомогою OpenID openid_signin=Увімкнути реєстрацію за допомогою OpenID
openid_signin_popup=Увімкнути вхід за допомогою OpenID. openid_signin_popup=Увімкнути вхід за допомогою OpenID.
openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID
@ -463,13 +454,10 @@ then_enter_passcode=І введіть пароль, який відобража
passcode_invalid=Некоректний пароль. Спробуй ще раз. passcode_invalid=Некоректний пароль. Спробуй ще раз.
twofa_enrolled=Для вашого облікового запису було включена двофакторна автентифікація. Зберігайте свій scratch-токен (%s) у безпечному місці, оскільки він показується лише один раз! twofa_enrolled=Для вашого облікового запису було включена двофакторна автентифікація. Зберігайте свій scratch-токен (%s) у безпечному місці, оскільки він показується лише один раз!
u2f_desc=Ключами безпеки є апаратні пристрої, що містять криптографічні ключі. Вони можуть використовуватися для двофакторної автентифікації. Ключ безпеки повинен підтримувати стандарт <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=Для використання ключів безпеки необхідно зареєструвати двофакторну аутентифікацію.
u2f_register_key=Додати ключ безпеки u2f_register_key=Додати ключ безпеки
u2f_nickname=Псевдонім u2f_nickname=Псевдонім
u2f_press_button=Натисніть кнопку на ключі безпеки, щоб зареєструвати його. u2f_press_button=Натисніть кнопку на ключі безпеки, щоб зареєструвати його.
u2f_delete_key=Видалити ключ безпеки u2f_delete_key=Видалити ключ безпеки
u2f_delete_key_desc=Якщо ви видалите ключ безпеки, ви не зможете використати його для входу. Ти впевнені?
manage_account_links=Керування обліковими записами manage_account_links=Керування обліковими записами
manage_account_links_desc=Ці зовнішні акаунти прив'язані до вашого аккаунту Gitea. manage_account_links_desc=Ці зовнішні акаунти прив'язані до вашого аккаунту Gitea.
@ -650,7 +638,6 @@ issues.new.open_milestone=Активні етапи
issues.new.closed_milestone=Закриті етапи issues.new.closed_milestone=Закриті етапи
issues.new.assignees=Виконавеці issues.new.assignees=Виконавеці
issues.new.clear_assignees=Прибрати виконавеців issues.new.clear_assignees=Прибрати виконавеців
issues.new.no_assignees=Ніхто не призначений
issues.no_ref=Не вказана гілка або тег issues.no_ref=Не вказана гілка або тег
issues.create=Створити проблему issues.create=Створити проблему
issues.new_label=Нова мітка issues.new_label=Нова мітка
@ -781,6 +768,14 @@ issues.due_date_added=додав(ла) дату завершення %s %s
issues.due_date_modified=термін змінено з %s %s на %s issues.due_date_modified=термін змінено з %s %s на %s
issues.due_date_remove=видалив(ла) дату завершення %s %s issues.due_date_remove=видалив(ла) дату завершення %s %s
issues.due_date_overdue=Прострочено issues.due_date_overdue=Прострочено
issues.dependency.title=Залежності
issues.dependency.issue_no_dependencies=Ця проблема в даний час не має залежностей.
issues.dependency.pr_no_dependencies=Цей запит на злиття в даний час не має залежностей.
issues.dependency.cancel=Відмінити
issues.dependency.remove=Видалити
issues.dependency.added_dependency=<div class="notranslate"> 0%</div>[2]s</a> додано нову залежність %[3]s'
issues.dependency.removed_dependency=<div class="notranslate"> 0%</div>[2]s</a> видалено залежність %[3]s'
issues.dependency.blocks_short=Блоки
pulls.desc=Увімкнути запити на злиття та інтерфейс узгодження правок. pulls.desc=Увімкнути запити на злиття та інтерфейс узгодження правок.
pulls.new=Новий запит на злиття pulls.new=Новий запит на злиття
@ -1168,8 +1163,6 @@ branch.protected_deletion_failed=Гілка '%s' захищена. Її не м
topic.manage_topics=Керувати тематичними мітками topic.manage_topics=Керувати тематичними мітками
topic.done=Готово topic.done=Готово
topic.count_prompt=Ви не можете вибрати більше 25 тем
topic.format_prompt=Теми мають починатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів
[org] [org]
org_name_holder=Назва організації org_name_holder=Назва організації
@ -1274,8 +1267,6 @@ dashboard.operation_switch=Перемкнути
dashboard.operation_run=Запустити dashboard.operation_run=Запустити
dashboard.clean_unbind_oauth=Очистити список незавершених авторизацій OAuth dashboard.clean_unbind_oauth=Очистити список незавершених авторизацій OAuth
dashboard.clean_unbind_oauth_success=Всі незавершені зв'язки OAuth були видалені. dashboard.clean_unbind_oauth_success=Всі незавершені зв'язки OAuth були видалені.
dashboard.delete_inactivate_accounts=Видалити всі неактивовані облікові записи
dashboard.delete_inactivate_accounts_success=Усі неактивовані облікові записи успішно видалено.
dashboard.delete_repo_archives=Видалити всі архіви репозиторіїв dashboard.delete_repo_archives=Видалити всі архіви репозиторіїв
dashboard.delete_repo_archives_success=Всі архіви репозиторіїв були видалені. dashboard.delete_repo_archives_success=Всі архіви репозиторіїв були видалені.
dashboard.delete_missing_repos=Видалити всі записи про репозиторії з відсутніми файлами Git dashboard.delete_missing_repos=Видалити всі записи про репозиторії з відсутніми файлами Git
@ -1483,7 +1474,6 @@ config.db_path=Шлях
config.service_config=Конфігурація сервісу config.service_config=Конфігурація сервісу
config.register_email_confirm=Потрібно підтвердити електронну пошту для реєстрації config.register_email_confirm=Потрібно підтвердити електронну пошту для реєстрації
config.disable_register=Вимкнути самостійну реєстрацію config.disable_register=Вимкнути самостійну реєстрацію
config.allow_only_external_registration=Включити реєстрацію тільки через сторонні сервіси
config.enable_openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID config.enable_openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID
config.enable_openid_signin=Увімкнути реєстрацію за допомогою OpenID config.enable_openid_signin=Увімкнути реєстрацію за допомогою OpenID
config.show_registration_button=Показувати кнопку "Реєстрація config.show_registration_button=Показувати кнопку "Реєстрація

View File

@ -32,16 +32,8 @@ twofa_scratch=两步验证口令
passcode=验证码 passcode=验证码
u2f_insert_key=插入安全密钥 u2f_insert_key=插入安全密钥
u2f_sign_in=按下安全密钥上的按钮。如果找不到按钮, 请重新插入。
u2f_press_button=请按下安全密钥上的按钮。 u2f_press_button=请按下安全密钥上的按钮。
u2f_use_twofa=使用来自你手机中的两步验证码 u2f_use_twofa=使用来自你手机中的两步验证码
u2f_error=没有找到你的安全密钥!
u2f_unsupported_browser=您的浏览器不支持 U2F 密钥。请尝试其他浏览器。
u2f_error_1=发生未知错误。请重试。
u2f_error_2=请确保您使用的是加密连接 (https://) 并访问正确的 URL。
u2f_error_3=服务器无法执行您的请求。
u2f_error_4=所提交的密钥不符合此请求。如果您尝试注册它, 请确保该密钥尚未注册。
u2f_error_5=在读取到密钥之前超时。请重新加载以重试。
u2f_reload=重新加载 u2f_reload=重新加载
repository=仓库 repository=仓库
@ -130,7 +122,6 @@ federated_avatar_lookup=启用 Federated 头像
federated_avatar_lookup_popup=启用 Federated Avatars 查找以使用开源的 Libravatar 服务。 federated_avatar_lookup_popup=启用 Federated Avatars 查找以使用开源的 Libravatar 服务。
disable_registration=禁止用户自助注册 disable_registration=禁止用户自助注册
disable_registration_popup=禁用用户自助注册。只有管理员才能创建新的用户帐户。 disable_registration_popup=禁用用户自助注册。只有管理员才能创建新的用户帐户。
allow_only_external_registration_popup=仅允许通过外部服务注册。
openid_signin=启用 OpenID 登录 openid_signin=启用 OpenID 登录
openid_signin_popup=启用通过 OpenID 登录 openid_signin_popup=启用通过 OpenID 登录
openid_signup=启用 OpenID 自助注册 openid_signup=启用 OpenID 自助注册
@ -463,13 +454,10 @@ then_enter_passcode=并输入应用程序中显示的密码:
passcode_invalid=密码不正确。再试一次。 passcode_invalid=密码不正确。再试一次。
twofa_enrolled=你的账号已经启用了两步验证。请保存初始令牌(%s到一个安全的地方此令牌仅当前显示一次。 twofa_enrolled=你的账号已经启用了两步验证。请保存初始令牌(%s到一个安全的地方此令牌仅当前显示一次。
u2f_desc=安全密钥是包含加密算法的硬件设备。它们可以用于两步验证。安全密钥必须支持 <a href="https://fidoalliance.org/">FIDO U2F</a> 标准。
u2f_require_twofa=必须开启两步验证才能使用安全密钥。
u2f_register_key=添加安全密钥 u2f_register_key=添加安全密钥
u2f_nickname=昵称 u2f_nickname=昵称
u2f_press_button=按安全密钥上的按钮进行注册。 u2f_press_button=按安全密钥上的按钮进行注册。
u2f_delete_key=移除安全密钥 u2f_delete_key=移除安全密钥
u2f_delete_key_desc=如果移除安全密钥, 则无法再使用它登录。是否确定?
manage_account_links=管理绑定过的账号 manage_account_links=管理绑定过的账号
manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。 manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。
@ -650,7 +638,6 @@ issues.new.open_milestone=开启中的里程碑
issues.new.closed_milestone=已关闭的里程碑 issues.new.closed_milestone=已关闭的里程碑
issues.new.assignees=指派成员 issues.new.assignees=指派成员
issues.new.clear_assignees=取消指派成员 issues.new.clear_assignees=取消指派成员
issues.new.no_assignees=未指派成员
issues.no_ref=分支/标记未指定 issues.no_ref=分支/标记未指定
issues.create=创建工单 issues.create=创建工单
issues.new_label=创建标签 issues.new_label=创建标签
@ -1167,8 +1154,6 @@ branch.protected_deletion_failed=分支 '%s' 已被保护,不可删除。
topic.manage_topics=管理主题 topic.manage_topics=管理主题
topic.done=保存 topic.done=保存
topic.count_prompt=您最多选择25个主题
topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 (-)并且长度不得超过35个字符
[org] [org]
org_name_holder=组织名称 org_name_holder=组织名称
@ -1273,8 +1258,6 @@ dashboard.operation_switch=开关
dashboard.operation_run=执行 dashboard.operation_run=执行
dashboard.clean_unbind_oauth=清理未绑定的 OAuth 连接 dashboard.clean_unbind_oauth=清理未绑定的 OAuth 连接
dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth 连接已被删除。 dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth 连接已被删除。
dashboard.delete_inactivate_accounts=删除所有未激活的帐户
dashboard.delete_inactivate_accounts_success=所有未激活的帐户都已删除。
dashboard.delete_repo_archives=删除所有仓库存档 dashboard.delete_repo_archives=删除所有仓库存档
dashboard.delete_repo_archives_success=所有仓库存档清除成功! dashboard.delete_repo_archives_success=所有仓库存档清除成功!
dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库 dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库
@ -1482,7 +1465,6 @@ config.db_path=数据库路径
config.service_config=服务配置 config.service_config=服务配置
config.register_email_confirm=需要电子邮件确认注册 config.register_email_confirm=需要电子邮件确认注册
config.disable_register=禁止用户注册 config.disable_register=禁止用户注册
config.allow_only_external_registration=仅允许通过外部服务注册
config.enable_openid_signup=启用 OpenID 自注册 config.enable_openid_signup=启用 OpenID 自注册
config.enable_openid_signin=启用 OpenID 登录 config.enable_openid_signin=启用 OpenID 登录
config.show_registration_button=显示注册按钮 config.show_registration_button=显示注册按钮

View File

@ -32,16 +32,8 @@ twofa_scratch=兩步驟驗證備用碼
passcode=驗證碼 passcode=驗證碼
u2f_insert_key=插入安全金鑰 u2f_insert_key=插入安全金鑰
u2f_sign_in=按下安全金鑰上的按鈕。如果找不到按鈕, 請重新插入。
u2f_press_button=請按下安全金鑰上的按鈕… u2f_press_button=請按下安全金鑰上的按鈕…
u2f_use_twofa=使用來自手機的兩步驟驗證碼 u2f_use_twofa=使用來自手機的兩步驟驗證碼
u2f_error=找不到您的安全金鑰!
u2f_unsupported_browser=您的瀏覽器不支援 U2F 二步驟驗證技術。請嘗試其他瀏覽器。
u2f_error_1=發生未知的錯誤,請重試。
u2f_error_2=請確保您使用的是加密連接 (https://) 並且 URL 是正確的。
u2f_error_3=伺服器無法執行您的請求。
u2f_error_4=所提交的金鑰不符合此請求。如果您嘗試註冊它, 請確保該金鑰尚未註冊。
u2f_error_5=在讀取金鑰之前已逾時,請重新載入以重試。
u2f_reload=重新載入 u2f_reload=重新載入
repository=儲存庫 repository=儲存庫
@ -129,7 +121,6 @@ federated_avatar_lookup=開啟聯合大頭貼
federated_avatar_lookup_popup=開啟聯合頭像查詢並使用基於開放源碼的 libravatar 服務 federated_avatar_lookup_popup=開啟聯合頭像查詢並使用基於開放源碼的 libravatar 服務
disable_registration=關閉註冊功能 disable_registration=關閉註冊功能
disable_registration_popup=關閉註冊功能,只有管理員可以新增帳號。 disable_registration_popup=關閉註冊功能,只有管理員可以新增帳號。
allow_only_external_registration_popup=僅允許通過外部服務註冊。
openid_signin=啟用 OpenID 登入 openid_signin=啟用 OpenID 登入
openid_signin_popup=啟用 OpenID 登入 openid_signin_popup=啟用 OpenID 登入
openid_signup=啟用 OpenID 註冊 openid_signup=啟用 OpenID 註冊
@ -432,13 +423,10 @@ or_enter_secret=或者輸入密碼: %s
then_enter_passcode=然後輸入應用程序中顯示的驗證碼: then_enter_passcode=然後輸入應用程序中顯示的驗證碼:
passcode_invalid=無效的驗證碼,請重試。 passcode_invalid=無效的驗證碼,請重試。
u2f_desc=安全密鑰是包含加密密鑰的硬體設備。 它們可以用於兩步驟認證。 安全密鑰必須符合 <a href="https://fidoalliance.org/">FIDO U2F</a> 標準。
u2f_require_twofa=必須先開啟兩步驟驗證才能使用安全密鑰。
u2f_register_key=新增安全密鑰 u2f_register_key=新增安全密鑰
u2f_nickname=暱稱 u2f_nickname=暱稱
u2f_press_button=按下安全密鑰上的密碼進行註冊。 u2f_press_button=按下安全密鑰上的密碼進行註冊。
u2f_delete_key=移除安全密鑰 u2f_delete_key=移除安全密鑰
u2f_delete_key_desc=如果您移除安全密鑰,則無法再使用它登錄。 確定嗎?
manage_account_links=管理已連結的帳號 manage_account_links=管理已連結的帳號
manage_account_links_desc=這些外部帳號與您的 Gitea 帳號相關聯。 manage_account_links_desc=這些外部帳號與您的 Gitea 帳號相關聯。
@ -591,7 +579,6 @@ issues.new.open_milestone=開啟中的里程碑
issues.new.closed_milestone=已關閉的里程碑 issues.new.closed_milestone=已關閉的里程碑
issues.new.assignees=指派成員 issues.new.assignees=指派成員
issues.new.clear_assignees=取消指派成員 issues.new.clear_assignees=取消指派成員
issues.new.no_assignees=未指派成員
issues.no_ref=未指定分支或標籤 issues.no_ref=未指定分支或標籤
issues.create=建立問題 issues.create=建立問題
issues.new_label=建立標籤 issues.new_label=建立標籤
@ -1260,7 +1247,6 @@ config.db_path=資料庫路徑
config.service_config=服務設定 config.service_config=服務設定
config.register_email_confirm=要求註冊時確認電子郵件 config.register_email_confirm=要求註冊時確認電子郵件
config.disable_register=關閉註冊功能 config.disable_register=關閉註冊功能
config.allow_only_external_registration=僅允許通過外部服務註冊
config.enable_openid_signup=啟用 OpenID 註冊 config.enable_openid_signup=啟用 OpenID 註冊
config.enable_openid_signin=啟用 OpenID 登入 config.enable_openid_signin=啟用 OpenID 登入
config.show_registration_button=顯示註冊按鈕 config.show_registration_button=顯示註冊按鈕

View File

@ -1769,6 +1769,7 @@ $(document).ready(function () {
initTopicbar(); initTopicbar();
initU2FAuth(); initU2FAuth();
initU2FRegister(); initU2FRegister();
initIssueList();
// Repo clone url. // Repo clone url.
if ($('#repo-clone-url').length > 0) { if ($('#repo-clone-url').length > 0) {
@ -2488,3 +2489,41 @@ function updateDeadline(deadlineString) {
} }
}); });
} }
function deleteDependencyModal(id, type) {
$('.remove-dependency')
.modal({
closable: false,
duration: 200,
onApprove: function () {
$('#removeDependencyID').val(id);
$('#dependencyType').val(type);
$('#removeDependencyForm').submit();
}
}).modal('show')
;
}
function initIssueList() {
var repolink = $('#repolink').val();
$('.new-dependency-drop-list')
.dropdown({
apiSettings: {
url: '/api/v1/repos' + repolink + '/issues?q={query}',
onResponse: function(response) {
var filteredResponse = {'success': true, 'results': []};
// Parse the response from the api to work with our dropdown
$.each(response, function(index, issue) {
filteredResponse.results.push({
'name' : '#' + issue.number + '&nbsp;' + issue.title,
'value' : issue.id
});
});
return filteredResponse;
},
},
fullTextSearch: true
})
;
}

View File

@ -11,122 +11,122 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><a href="/vendor/plugins/jquery.areyousure/jquery.are-you-sure.js">jquery.are-you-sure.js</a></td> <td><a href="./plugins/jquery.areyousure/jquery.are-you-sure.js">jquery.are-you-sure.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> <td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/codedance/jquery.AreYouSure/archive/1.9.0.tar.gz">jquery.areyousure-1.9.0.tar.gz</a></td> <td><a href="https://github.com/codedance/jquery.AreYouSure/archive/1.9.0.tar.gz">jquery.areyousure-1.9.0.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/jquery/jquery.min.js">jquery.min.js</a></td> <td><a href="./plugins/jquery/jquery.min.js">jquery.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> <td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://code.jquery.com/jquery-1.11.3.min.js">jquery-1.11.3.min.js</a></td> <td><a href="https://code.jquery.com/jquery-1.11.3.min.js">jquery-1.11.3.min.js</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/semantic/semantic.min.js">semantic.min.js</a></td> <td><a href="./plugins/semantic/semantic.min.js">semantic.min.js</a></td>
<td><a href="https://semantic-ui.mit-license.org/">Expat</a></td> <td><a href="https://semantic-ui.mit-license.org/">Expat</a></td>
<td><a href="https://github.com/Semantic-Org/Semantic-UI/archive/2.3.1.tar.gz">semantic-UI-2.3.1.tar.gz</a></td> <td><a href="https://github.com/Semantic-Org/Semantic-UI/archive/2.3.1.tar.gz">semantic-UI-2.3.1.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/js/index.js">index.js</a></td> <td><a href="../js/index.js">index.js</a></td>
<td><a href="https://github.com/go-gitea/gitea/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/go-gitea/gitea/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/go-gitea/gitea/tree/master/public/js">index.js</a></td> <td><a href="https://github.com/go-gitea/gitea/tree/master/public/js">index.js</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/js/draw.js">draw.js</a></td> <td><a href="../js/draw.js">draw.js</a></td>
<td><a href="https://github.com/go-gitea/gitea/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/go-gitea/gitea/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/go-gitea/gitea/tree/master/public/js">draw.js</a></td> <td><a href="https://github.com/go-gitea/gitea/tree/master/public/js">draw.js</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/clipboard/clipboard.min.js">clipboard.min.js</a></td> <td><a href="./plugins/clipboard/clipboard.min.js">clipboard.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> <td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/zenorocha/clipboard.js/archive/v1.5.9.tar.gz">clipboard-1.5.9.tar.gz</a></td> <td><a href="https://github.com/zenorocha/clipboard.js/archive/v1.5.9.tar.gz">clipboard-1.5.9.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/gitgraph/gitgraph.js">gitgraph.js</a></td> <td><a href="./plugins/gitgraph/gitgraph.js">gitgraph.js</a></td>
<td><a href="https://github.com/bluef/gitgraph.js/blob/master/LICENSE">BSD 3-Clause</a></td> <td><a href="https://github.com/bluef/gitgraph.js/blob/master/LICENSE">BSD 3-Clause</a></td>
<td><a href="https://github.com/bluef/gitgraph.js">gitgraph.js-latest</a></td> <td><a href="https://github.com/bluef/gitgraph.js">gitgraph.js-latest</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/vue/vue.min.js">vue.min.js</a></td> <td><a href="./plugins/vue/vue.min.js">vue.min.js</a></td>
<td><a href="https://github.com/vuejs/vue/blob/dev/LICENSE">Expat</a></td> <td><a href="https://github.com/vuejs/vue/blob/dev/LICENSE">Expat</a></td>
<td><a href="https://github.com/vuejs/vue/archive/v2.1.10.tar.gz">vue.js-v2.1.10.tar.gz</a></td> <td><a href="https://github.com/vuejs/vue/archive/v2.1.10.tar.gz">vue.js-v2.1.10.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/emojify/emojify.min.js">emojify.min.js</a></td> <td><a href="./plugins/emojify/emojify.min.js">emojify.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> <td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/Ranks/emojify.js/archive/1.1.0.tar.gz">emojify-1.1.0.tar.gz</a></td> <td><a href="https://github.com/Ranks/emojify.js/archive/1.1.0.tar.gz">emojify-1.1.0.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/cssrelpreload/loadCSS.min.js">loadCSS.min.js</a></td> <td><a href="./plugins/cssrelpreload/loadCSS.min.js">loadCSS.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> <td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/filamentgroup/loadCSS/archive/v1.3.1.tar.gz">loadCSS-1.3.1.tar.gz</a></td> <td><a href="https://github.com/filamentgroup/loadCSS/archive/v1.3.1.tar.gz">loadCSS-1.3.1.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/cssrelpreload/cssrelpreload.min.js">cssrelpreload.min.js</a></td> <td><a href="./plugins/cssrelpreload/cssrelpreload.min.js">cssrelpreload.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> <td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/filamentgroup/loadCSS/archive/v1.3.1.tar.gz">loadCSS-1.3.1.tar.gz</a></td> <td><a href="https://github.com/filamentgroup/loadCSS/archive/v1.3.1.tar.gz">loadCSS-1.3.1.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/dropzone/dropzone.js">dropzone.js</a></td> <td><a href="./plugins/dropzone/dropzone.js">dropzone.js</a></td>
<td><a href="https://github.com/enyo/dropzone/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/enyo/dropzone/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/enyo/dropzone/archive/v4.2.0.tar.gz">dropzone.js-4.2.0.tar.gz</a></td> <td><a href="https://github.com/enyo/dropzone/archive/v4.2.0.tar.gz">dropzone.js-4.2.0.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/highlight/highlight.pack.js">highlight.pack.js</a></td> <td><a href="./plugins/highlight/highlight.pack.js">highlight.pack.js</a></td>
<td><a href="https://github.com/isagalaev/highlight.js/blob/master/LICENSE">BSD 3-Clause</a></td> <td><a href="https://github.com/isagalaev/highlight.js/blob/master/LICENSE">BSD 3-Clause</a></td>
<td><a href="https://github.com/isagalaev/highlight.js/archive/9.6.0.tar.gz">highlight.js-9.6.0.tar.gz</a></td> <td><a href="https://github.com/isagalaev/highlight.js/archive/9.6.0.tar.gz">highlight.js-9.6.0.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/jquery.datetimepicker/jquery.datetimepicker.js">jquery.datetimepicker.js</a></td> <td><a href="./plugins/jquery.datetimepicker/jquery.datetimepicker.js">jquery.datetimepicker.js</a></td>
<td><a href="https://github.com/xdan/datetimepicker/blob/master/MIT-LICENSE.txt">Expat</a></td> <td><a href="https://github.com/xdan/datetimepicker/blob/master/MIT-LICENSE.txt">Expat</a></td>
<td><a href="https://github.com/xdan/datetimepicker/archive/2.4.5.tar.gz">jquery.datetimepicker-2.4.5.tar.gz</a></td> <td><a href="https://github.com/xdan/datetimepicker/archive/2.4.5.tar.gz">jquery.datetimepicker-2.4.5.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/jquery.minicolors/jquery.minicolors.min.js">jquery.minicolors.min.js</a></td> <td><a href="./plugins/jquery.minicolors/jquery.minicolors.min.js">jquery.minicolors.min.js</a></td>
<td><a href="https://github.com/claviska/jquery-minicolors/blob/master/LICENSE.md">Expat</a></td> <td><a href="https://github.com/claviska/jquery-minicolors/blob/master/LICENSE.md">Expat</a></td>
<td><a href="https://github.com/claviska/jquery-minicolors/archive/2.2.3.tar.gz">jquery.minicolors-2.2.3.tar.gz</a></td> <td><a href="https://github.com/claviska/jquery-minicolors/archive/2.2.3.tar.gz">jquery.minicolors-2.2.3.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/codemirror/addon/mode/loadmode.js">loadmode.js</a></td> <td><a href="./plugins/codemirror/addon/mode/loadmode.js">loadmode.js</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/codemirror/CodeMirror/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/archive/5.17.0.tar.gz">codemirror-5.17.0.tar.gz</a></td> <td><a href="https://github.com/codemirror/CodeMirror/archive/5.17.0.tar.gz">codemirror-5.17.0.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/codemirror/mode/meta.js">meta.js</a></td> <td><a href="./plugins/codemirror/mode/meta.js">meta.js</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/codemirror/CodeMirror/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/archive/5.17.0.tar.gz">codemirror-5.17.0.tar.gz</a></td> <td><a href="https://github.com/codemirror/CodeMirror/archive/5.17.0.tar.gz">codemirror-5.17.0.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/simplemde/simplemde.min.js">simplemde.min.js</a></td> <td><a href="./plugins/simplemde/simplemde.min.js">simplemde.min.js</a></td>
<td><a href="https://github.com/sparksuite/simplemde-markdown-editor/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/sparksuite/simplemde-markdown-editor/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/NextStepWebs/simplemde-markdown-editor/archive/1.10.1.tar.gz">simplemde-markdown-editor-1.10.1.tar.gz</a></td> <td><a href="https://github.com/NextStepWebs/simplemde-markdown-editor/archive/1.10.1.tar.gz">simplemde-markdown-editor-1.10.1.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/pdfjs/">pdf.js</a></td> <td><a href="./plugins/pdfjs/">pdf.js</a></td>
<td><a href="https://github.com/mozilla/pdf.js/blob/master/LICENSE">Apache-2.0-only</a></td> <td><a href="https://github.com/mozilla/pdf.js/blob/master/LICENSE">Apache-2.0-only</a></td>
<td><a href="https://github.com/mozilla/pdf.js/archive/v1.4.20.tar.gz">pdf.js-v1.4.20.tar.gz</a></td> <td><a href="https://github.com/mozilla/pdf.js/archive/v1.4.20.tar.gz">pdf.js-v1.4.20.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/plugins/u2f/">u2f-api</a></td> <td><a href="./plugins/u2f/">u2f-api</a></td>
<td><a href="https://github.com/go-gitea/u2f-api/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/go-gitea/u2f-api/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/go-gitea/u2f-api/archive/v1.0.8.zip">u2f-api-1.0.8.zip</a></td> <td><a href="https://github.com/go-gitea/u2f-api/archive/v1.0.8.zip">u2f-api-1.0.8.zip</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/assets/font-awesome/fonts/">font-awesome - fonts</a></td> <td><a href="./assets/font-awesome/fonts/">font-awesome - fonts</a></td>
<td><a href="http://fontawesome.io/license/">OFL</a></td> <td><a href="http://fontawesome.io/license/">OFL</a></td>
<td><a href="http://fontawesome.io/assets/font-awesome-4.6.0.zip">font-awesome-4.6.0.zip</a></td> <td><a href="http://fontawesome.io/assets/font-awesome-4.6.0.zip">font-awesome-4.6.0.zip</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/assets/font-awesome/css/">font-awesome - code</a></td> <td><a href="./assets/font-awesome/css/">font-awesome - code</a></td>
<td><a href="http://fontawesome.io/license/">Expat</a></td> <td><a href="http://fontawesome.io/license/">Expat</a></td>
<td><a href="http://fontawesome.io/assets/font-awesome-4.6.0.zip">font-awesome-4.6.0.zip</a></td> <td><a href="http://fontawesome.io/assets/font-awesome-4.6.0.zip">font-awesome-4.6.0.zip</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/assets/octicons/">octicons</a></td> <td><a href="./assets/octicons/">octicons</a></td>
<td><a href="https://github.com/primer/octicons/blob/master/LICENSE">Expat</a></td> <td><a href="https://github.com/primer/octicons/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/primer/octicons/archive/v4.3.0.tar.gz">octicons-v4.3.0.tar.gz</a></td> <td><a href="https://github.com/primer/octicons/archive/v4.3.0.tar.gz">octicons-v4.3.0.tar.gz</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="/vendor/assets/swagger-ui/">swagger-ui</a></td> <td><a href="./assets/swagger-ui/">swagger-ui</a></td>
<td><a href="https://github.com/swagger-api/swagger-ui/blob/master/LICENSE">Apache-2.0</a></td> <td><a href="https://github.com/swagger-api/swagger-ui/blob/master/LICENSE">Apache-2.0</a></td>
<td><a href="https://github.com/swagger-api/swagger-ui/archive/v3.0.4.tar.gz">swagger-ui-v3.0.4.tar.gz</a></td> <td><a href="https://github.com/swagger-api/swagger-ui/archive/v3.0.4.tar.gz">swagger-ui-v3.0.4.tar.gz</a></td>
</tr> </tr>

View File

@ -7,6 +7,7 @@ package repo
import ( import (
"fmt" "fmt"
"net/http"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -208,6 +209,10 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
if form.Closed { if form.Closed {
if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, true); err != nil { if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, true); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
}
ctx.Error(500, "ChangeStatus", err) ctx.Error(500, "ChangeStatus", err)
return return
} }
@ -325,6 +330,10 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
} }
if form.State != nil { if form.State != nil {
if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil { if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
}
ctx.Error(500, "ChangeStatus", err) ctx.Error(500, "ChangeStatus", err)
return return
} }

View File

@ -6,6 +6,7 @@ package repo
import ( import (
"fmt" "fmt"
"net/http"
"strings" "strings"
"code.gitea.io/git" "code.gitea.io/git"
@ -378,6 +379,10 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
} }
if form.State != nil { if form.State != nil {
if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil { if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
return
}
ctx.Error(500, "ChangeStatus", err) ctx.Error(500, "ChangeStatus", err)
return return
} }

View File

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -302,6 +303,9 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.
} }
ctx.Data["Branches"] = brs ctx.Data["Branches"] = brs
// Contains true if the user can create issue dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
return labels return labels
} }
@ -665,6 +669,9 @@ func ViewIssue(ctx *context.Context) {
} }
} }
// Check if the user can use the dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
// Render comments and and fetch participants. // Render comments and and fetch participants.
participants[0] = issue.Poster participants[0] = issue.Poster
for _, comment = range issue.Comments { for _, comment = range issue.Comments {
@ -721,6 +728,11 @@ func ViewIssue(ctx *context.Context) {
ctx.ServerError("LoadAssigneeUser", err) ctx.ServerError("LoadAssigneeUser", err)
return return
} }
} else if comment.Type == models.CommentTypeRemoveDependency || comment.Type == models.CommentTypeAddDependency {
if err = comment.LoadDepIssueDetails(); err != nil {
ctx.ServerError("LoadDepIssueDetails", err)
return
}
} }
} }
@ -774,6 +786,10 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["IsPullBranchDeletable"] = canDelete && pull.HeadRepo != nil && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) ctx.Data["IsPullBranchDeletable"] = canDelete && pull.HeadRepo != nil && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch)
} }
// Get Dependencies
ctx.Data["BlockedByDependencies"], err = issue.BlockedByDependencies()
ctx.Data["BlockingDependencies"], err = issue.BlockingDependencies()
ctx.Data["Participants"] = participants ctx.Data["Participants"] = participants
ctx.Data["NumParticipants"] = len(participants) ctx.Data["NumParticipants"] = len(participants)
ctx.Data["Issue"] = issue ctx.Data["Issue"] = issue
@ -971,6 +987,12 @@ func UpdateIssueStatus(ctx *context.Context) {
} }
for _, issue := range issues { for _, issue := range issues {
if err := issue.ChangeStatus(ctx.User, issue.Repo, isClosed); err != nil { if err := issue.ChangeStatus(ctx.User, issue.Repo, isClosed); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.JSON(http.StatusPreconditionFailed, map[string]interface{}{
"error": "cannot close this issue because it still has open dependencies",
})
return
}
ctx.ServerError("ChangeStatus", err) ctx.ServerError("ChangeStatus", err)
return return
} }
@ -1034,6 +1056,17 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
} else { } else {
if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil { if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil {
log.Error(4, "ChangeStatus: %v", err) log.Error(4, "ChangeStatus: %v", err)
if models.IsErrDependenciesLeft(err) {
if issue.IsPull {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
} else {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
}
return
}
} else { } else {
log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed) log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)

View File

@ -0,0 +1,119 @@
// 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 repo
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
)
// AddDependency adds new dependencies
func AddDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("newDependency")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
ctx.ServerError("GetIssueByIndex", err)
return
}
// Redirect
defer ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
// Dependency
dep, err := models.GetIssueByID(depID)
if err != nil {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_issue_not_exist"))
return
}
// Check if both issues are in the same repo
if issue.RepoID != dep.RepoID {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_not_same_repo"))
return
}
// Check if issue and dependency is the same
if dep.Index == issueIndex {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_same_issue"))
return
}
err = models.CreateIssueDependency(ctx.User, issue, dep)
if err != nil {
if models.IsErrDependencyExists(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_exists"))
return
} else if models.IsErrCircularDependency(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_cannot_create_circular"))
return
} else {
ctx.ServerError("CreateOrUpdateIssueDependency", err)
return
}
}
}
// RemoveDependency removes the dependency
func RemoveDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("removeDependencyID")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
ctx.ServerError("GetIssueByIndex", err)
return
}
// Redirect
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
// Dependency Type
depTypeStr := ctx.Req.PostForm.Get("dependencyType")
var depType models.DependencyType
switch depTypeStr {
case "blockedBy":
depType = models.DependencyTypeBlockedBy
case "blocking":
depType = models.DependencyTypeBlocking
default:
ctx.Error(http.StatusBadRequest, "GetDependecyType")
return
}
// Dependency
dep, err := models.GetIssueByID(depID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
if err = models.RemoveIssueDependency(ctx.User, issue, dep, depType); err != nil {
if models.IsErrDependencyNotExists(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_not_exist"))
return
}
ctx.ServerError("RemoveIssueDependency", err)
return
}
}

View File

@ -524,6 +524,18 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
pr.Issue = issue pr.Issue = issue
pr.Issue.Repo = ctx.Repo.Repository pr.Issue.Repo = ctx.Repo.Repository
noDeps, err := models.IssueNoDependenciesLeft(issue)
if err != nil {
return
}
if !noDeps {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
return
}
if err = pr.Merge(ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil { if err = pr.Merge(ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if models.IsErrInvalidMergeStyle(err) { if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))

View File

@ -202,6 +202,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
Config: &models.IssuesConfig{ Config: &models.IssuesConfig{
EnableTimetracker: form.EnableTimetracker, EnableTimetracker: form.EnableTimetracker,
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
EnableDependencies: form.EnableIssueDependencies,
}, },
}) })
} }

View File

@ -527,6 +527,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/title", repo.UpdateIssueTitle) m.Post("/title", repo.UpdateIssueTitle)
m.Post("/content", repo.UpdateIssueContent) m.Post("/content", repo.UpdateIssueContent)
m.Post("/watch", repo.IssueWatch) m.Post("/watch", repo.IssueWatch)
m.Group("/dependency", func() {
m.Post("/add", repo.AddDependency)
m.Post("/delete", repo.RemoveDependency)
})
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
m.Group("/times", func() { m.Group("/times", func() {
m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually) m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -320,7 +321,7 @@ func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthFo
handleSignInFull(ctx, u, remember, false) handleSignInFull(ctx, u, remember, false)
ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used")) ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return return
} }

View File

@ -150,6 +150,8 @@
{{end}} {{end}}
<dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt> <dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt>
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd> <dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_enable_dependencies"}}</dt>
<dd><i class="fa fa{{if .Service.DefaultEnableDependencies}}-check{{end}}-square-o"></i></dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.active_code_lives"}}</dt> <dt>{{.i18n.Tr "admin.config.active_code_lives"}}</dt>
<dd>{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd> <dd>{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd>

View File

@ -72,7 +72,7 @@
{{.i18n.Tr "repo.branch.delete_html"}} <span class="branch-name"></span> {{.i18n.Tr "repo.branch.delete_html"}} <span class="branch-name"></span>
</div> </div>
<div class="content"> <div class="content">
<p>{{.i18n.Tr "repo.branch.delete_desc"}}</p> <p>{{.i18n.Tr "repo.branch.delete_desc" | Str2html}}</p>
</div> </div>
{{template "base/delete_modal_actions" .}} {{template "base/delete_modal_actions" .}}
</div> </div>

View File

@ -137,7 +137,7 @@
{{.i18n.Tr "repo.branch.delete" .HeadTarget }} {{.i18n.Tr "repo.branch.delete" .HeadTarget }}
</div> </div>
<div class="content"> <div class="content">
<p>{{.i18n.Tr "repo.branch.delete_desc"}}</p> <p>{{.i18n.Tr "repo.branch.delete_desc" | Str2html}}</p>
</div> </div>
{{template "base/delete_modal_actions" .}} {{template "base/delete_modal_actions" .}}
</div> </div>

View File

@ -1,7 +1,7 @@
{{range .Issue.Comments}} {{range .Issue.Comments}}
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} {{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }}
<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE --> <!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY -->
{{if eq .Type 0}} {{if eq .Type 0}}
<div class="comment" id="{{.HashTag}}"> <div class="comment" id="{{.HashTag}}">
<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}> <a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
@ -65,7 +65,6 @@
{{end}} {{end}}
</div> </div>
</div> </div>
{{else if eq .Type 1}} {{else if eq .Type 1}}
<div class="event"> <div class="event">
<span class="octicon octicon-primitive-dot"></span> <span class="octicon octicon-primitive-dot"></span>
@ -233,5 +232,33 @@
{{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}} {{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}}
</span> </span>
</div> </div>
{{end}} {{else if eq .Type 19}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey">
{{$.i18n.Tr "repo.issues.dependency.added_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}}
</span>
<div class="detail">
<span class="octicon octicon-plus"></span>
<span class="text grey"><a href="{{$.RepoLink}}/issues/{{.DependentIssue.Index}}">#{{.DependentIssue.Index}} {{.DependentIssue.Title}}</a></span>
</div>
</div>
{{else if eq .Type 20}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey">
{{$.i18n.Tr "repo.issues.dependency.removed_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}}
</span>
<div class="detail">
<span class="text grey octicon octicon-trashcan"></span>
<span class="text grey"><a href="{{$.RepoLink}}/issues/{{.DependentIssue.Index}}">#{{.DependentIssue.Index}} {{.DependentIssue.Title}}</a></span>
</div>
</div>
{{end}}
{{end}} {{end}}

View File

@ -249,5 +249,142 @@
</div> </div>
{{end}} {{end}}
</div> </div>
{{if .Repository.IsDependenciesEnabled}}
<div class="ui divider"></div>
<div class="ui depending">
<span class="text"><strong>{{.i18n.Tr "repo.issues.dependency.title"}}</strong></span>
<br>
{{if .BlockedByDependencies}}
<span class="text" data-tooltip="{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.issue_closing_blockedby"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.pr_closing_blockedby"}}
{{end}}" data-inverted="">
{{.i18n.Tr "repo.issues.dependency.blocked_by_short"}}:
</span>
<div class="ui relaxed divided list">
{{range .BlockedByDependencies}}
<div class="item">
<div class="right floated content">
{{if $.CanCreateIssueDependencies}}
<a class="delete-dependency-button" onclick="deleteDependencyModal({{.ID}}, 'blockedBy');">
<i class="delete icon text red"></i>
</a>
{{end}}
{{if .IsClosed}}
<div class="ui red mini label">
<i class="octicon octicon-issue-closed"></i>
</div>
{{else}}
<div class="ui green mini label">
<i class="octicon octicon-issue-opened"></i>
</div>
{{end}}
</div>
<div class="ui black label">#{{.Index}}</div>
<a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a>
</div>
{{end}}
</div>
{{end}}
{{if .BlockingDependencies}}
<span class="text" data-tooltip="{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.pr_close_blocks"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.issue_close_blocks"}}
{{end}}" data-inverted="">
{{.i18n.Tr "repo.issues.dependency.blocks_short"}}:
</span>
<div class="ui relaxed divided list">
{{range .BlockingDependencies}}
<div class="item">
<div class="right floated content">
{{if $.CanCreateIssueDependencies}}
<a class="delete-dependency-button" onclick="deleteDependencyModal({{.ID}}, 'blocking');">
<i class="delete icon text red"></i>
</a>
{{end}}
{{if .IsClosed}}
<div class="ui red tiny label">
<i class="octicon octicon-issue-closed"></i>
</div>
{{else}}
<div class="ui green mini label">
<i class="octicon octicon-issue-opened"></i>
</div>
{{end}}
</div>
<div class="ui black label">#{{.Index}}</div>
<a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a>
</div>
{{end}}
</div>
{{end}}
{{if (and (not .BlockedByDependencies) (not .BlockingDependencies))}}
<p>{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.pr_no_dependencies"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.issue_no_dependencies"}}
{{end}}</p>
{{end}}
{{if .CanCreateIssueDependencies}}
<div>
<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/dependency/add" id="addDependencyForm">
{{$.CsrfTokenHtml}}
<div class="ui fluid action input">
<div class="ui search selection dropdown new-dependency-drop-list" style="min-width: 13.9rem;border-radius: 4px 0 0 4px;border-right: 0;white-space: nowrap;">
<input name="newDependency" type="hidden">
<i class="dropdown icon"></i>
<input type="text" class="search">
<div class="default text">{{.i18n.Tr "repo.issues.dependency.add"}}</div>
</div>
<button class="ui green icon button">
<i class="plus icon"></i>
</button>
</div>
</form>
</div>
{{end}}
</div>
</div> </div>
</div> </div>
{{if .CanCreateIssueDependencies}}
<input type="hidden" id="repolink" value="{{$.RepoLink}}">
<!-- I know, there is probably a better way to do this -->
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/>
<div class="ui basic modal remove-dependency">
<div class="ui icon header">
<i class="trash icon"></i>
{{.i18n.Tr "repo.issues.dependency.remove_header"}}
</div>
<div class="content">
<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/dependency/delete" id="removeDependencyForm">
{{$.CsrfTokenHtml}}
<input type="hidden" value="" name="removeDependencyID" id="removeDependencyID"/>
<input type="hidden" value="" name="dependencyType" id="dependencyType"/>
</form>
<p>{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.pr_remove_text"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.issue_remove_text"}}
{{end}}</p>
</div>
<div class="actions">
<div class="ui basic red cancel inverted button">
<i class="remove icon"></i>
{{.i18n.Tr "repo.issues.dependency.cancel"}}
</div>
<div class="ui basic green ok inverted button">
<i class="checkmark icon"></i>
{{.i18n.Tr "repo.issues.dependency.remove"}}
</div>
</div>
</div>
{{end}}
{{end}}

View File

@ -153,6 +153,12 @@
</div> </div>
</div> </div>
{{end}} {{end}}
<div class="field">
<div class="ui checkbox">
<input name="enable_issue_dependencies" type="checkbox" {{if (.Repository.IsDependenciesEnabled)}}checked{{end}}>
<label>{{.i18n.Tr "repo.issues.dependency.setting"}}</label>
</div>
</div>
</div> </div>
<div class="field"> <div class="field">
<div class="ui radio checkbox"> <div class="ui radio checkbox">

View File

@ -4,6 +4,10 @@
package builder package builder
import (
"fmt"
)
type optype byte type optype byte
const ( const (
@ -29,6 +33,9 @@ type Builder struct {
joins []join joins []join
inserts Eq inserts Eq
updates []Eq updates []Eq
orderBy string
groupBy string
having string
} }
// Select creates a select Builder // Select creates a select Builder
@ -67,6 +74,11 @@ func (b *Builder) From(tableName string) *Builder {
return b return b
} }
// TableName returns the table name
func (b *Builder) TableName() string {
return b.tableName
}
// Into sets insert table name // Into sets insert table name
func (b *Builder) Into(tableName string) *Builder { func (b *Builder) Into(tableName string) *Builder {
b.tableName = tableName b.tableName = tableName
@ -178,6 +190,33 @@ func (b *Builder) ToSQL() (string, []interface{}, error) {
return w.writer.String(), w.args, nil return w.writer.String(), w.args, nil
} }
// ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix
func ConvertPlaceholder(sql, prefix string) (string, error) {
buf := StringBuilder{}
var j, start = 0, 0
for i := 0; i < len(sql); i++ {
if sql[i] == '?' {
_, err := buf.WriteString(sql[start:i])
if err != nil {
return "", err
}
start = i + 1
_, err = buf.WriteString(prefix)
if err != nil {
return "", err
}
j = j + 1
_, err = buf.WriteString(fmt.Sprintf("%d", j))
if err != nil {
return "", err
}
}
}
return buf.String(), nil
}
// ToSQL convert a builder or condtions to SQL and args // ToSQL convert a builder or condtions to SQL and args
func ToSQL(cond interface{}) (string, []interface{}, error) { func ToSQL(cond interface{}) (string, []interface{}, error) {
switch cond.(type) { switch cond.(type) {

View File

@ -15,7 +15,7 @@ func (b *Builder) insertWriteTo(w Writer) error {
return errors.New("no table indicated") return errors.New("no table indicated")
} }
if len(b.inserts) <= 0 { if len(b.inserts) <= 0 {
return errors.New("no column to be update") return errors.New("no column to be insert")
} }
if _, err := fmt.Fprintf(w, "INSERT INTO %s (", b.tableName); err != nil { if _, err := fmt.Fprintf(w, "INSERT INTO %s (", b.tableName); err != nil {
@ -26,7 +26,9 @@ func (b *Builder) insertWriteTo(w Writer) error {
var bs []byte var bs []byte
var valBuffer = bytes.NewBuffer(bs) var valBuffer = bytes.NewBuffer(bs)
var i = 0 var i = 0
for col, value := range b.inserts {
for _, col := range b.inserts.sortedKeys() {
value := b.inserts[col]
fmt.Fprint(w, col) fmt.Fprint(w, col)
if e, ok := value.(expr); ok { if e, ok := value.(expr); ok {
fmt.Fprint(valBuffer, e.sql) fmt.Fprint(valBuffer, e.sql)

View File

@ -34,24 +34,65 @@ func (b *Builder) selectWriteTo(w Writer) error {
} }
} }
if _, err := fmt.Fprintf(w, " FROM %s", b.tableName); err != nil { if _, err := fmt.Fprint(w, " FROM ", b.tableName); err != nil {
return err return err
} }
for _, v := range b.joins { for _, v := range b.joins {
fmt.Fprintf(w, " %s JOIN %s ON ", v.joinType, v.joinTable) if _, err := fmt.Fprintf(w, " %s JOIN %s ON ", v.joinType, v.joinTable); err != nil {
return err
}
if err := v.joinCond.WriteTo(w); err != nil { if err := v.joinCond.WriteTo(w); err != nil {
return err return err
} }
} }
if !b.cond.IsValid() { if b.cond.IsValid() {
return nil if _, err := fmt.Fprint(w, " WHERE "); err != nil {
return err
}
if err := b.cond.WriteTo(w); err != nil {
return err
}
} }
if _, err := fmt.Fprint(w, " WHERE "); err != nil { if len(b.groupBy) > 0 {
return err if _, err := fmt.Fprint(w, " GROUP BY ", b.groupBy); err != nil {
return err
}
} }
return b.cond.WriteTo(w) if len(b.having) > 0 {
if _, err := fmt.Fprint(w, " HAVING ", b.having); err != nil {
return err
}
}
if len(b.orderBy) > 0 {
if _, err := fmt.Fprint(w, " ORDER BY ", b.orderBy); err != nil {
return err
}
}
return nil
}
// OrderBy orderBy SQL
func (b *Builder) OrderBy(orderBy string) *Builder {
b.orderBy = orderBy
return b
}
// GroupBy groupby SQL
func (b *Builder) GroupBy(groupby string) *Builder {
b.groupBy = groupby
return b
}
// Having having SQL
func (b *Builder) Having(having string) *Builder {
b.having = having
return b
} }

View File

@ -5,7 +5,6 @@
package builder package builder
import ( import (
"bytes"
"io" "io"
) )
@ -19,15 +18,15 @@ var _ Writer = NewWriter()
// BytesWriter implments Writer and save SQL in bytes.Buffer // BytesWriter implments Writer and save SQL in bytes.Buffer
type BytesWriter struct { type BytesWriter struct {
writer *bytes.Buffer writer *StringBuilder
buffer []byte
args []interface{} args []interface{}
} }
// NewWriter creates a new string writer // NewWriter creates a new string writer
func NewWriter() *BytesWriter { func NewWriter() *BytesWriter {
w := &BytesWriter{} w := &BytesWriter{
w.writer = bytes.NewBuffer(w.buffer) writer: &StringBuilder{},
}
return w return w
} }

View File

@ -10,7 +10,13 @@ import "fmt"
func WriteMap(w Writer, data map[string]interface{}, op string) error { func WriteMap(w Writer, data map[string]interface{}, op string) error {
var args = make([]interface{}, 0, len(data)) var args = make([]interface{}, 0, len(data))
var i = 0 var i = 0
for k, v := range data { keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
for _, k := range keys {
v := data[k]
switch v.(type) { switch v.(type) {
case expr: case expr:
if _, err := fmt.Fprintf(w, "%s%s(", k, op); err != nil { if _, err := fmt.Fprintf(w, "%s%s(", k, op); err != nil {

View File

@ -4,7 +4,10 @@
package builder package builder
import "fmt" import (
"fmt"
"sort"
)
// Incr implements a type used by Eq // Incr implements a type used by Eq
type Incr int type Incr int
@ -19,7 +22,8 @@ var _ Cond = Eq{}
func (eq Eq) opWriteTo(op string, w Writer) error { func (eq Eq) opWriteTo(op string, w Writer) error {
var i = 0 var i = 0
for k, v := range eq { for _, k := range eq.sortedKeys() {
v := eq[k]
switch v.(type) { switch v.(type) {
case []int, []int64, []string, []int32, []int16, []int8, []uint, []uint64, []uint32, []uint16, []interface{}: case []int, []int64, []string, []int32, []int16, []int8, []uint, []uint64, []uint32, []uint16, []interface{}:
if err := In(k, v).WriteTo(w); err != nil { if err := In(k, v).WriteTo(w); err != nil {
@ -94,3 +98,15 @@ func (eq Eq) Or(conds ...Cond) Cond {
func (eq Eq) IsValid() bool { func (eq Eq) IsValid() bool {
return len(eq) > 0 return len(eq) > 0
} }
// sortedKeys returns all keys of this Eq sorted with sort.Strings.
// It is used internally for consistent ordering when generating
// SQL, see https://github.com/go-xorm/builder/issues/10
func (eq Eq) sortedKeys() []string {
keys := make([]string, 0, len(eq))
for key := range eq {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

View File

@ -16,7 +16,7 @@ func (like Like) WriteTo(w Writer) error {
if _, err := fmt.Fprintf(w, "%s LIKE ?", like[0]); err != nil { if _, err := fmt.Fprintf(w, "%s LIKE ?", like[0]); err != nil {
return err return err
} }
// FIXME: if use other regular express, this will be failed. but for compitable, keep this // FIXME: if use other regular express, this will be failed. but for compatible, keep this
if like[1][0] == '%' || like[1][len(like[1])-1] == '%' { if like[1][0] == '%' || like[1][len(like[1])-1] == '%' {
w.Append(like[1]) w.Append(like[1])
} else { } else {

View File

@ -4,7 +4,10 @@
package builder package builder
import "fmt" import (
"fmt"
"sort"
)
// Neq defines not equal conditions // Neq defines not equal conditions
type Neq map[string]interface{} type Neq map[string]interface{}
@ -15,7 +18,8 @@ var _ Cond = Neq{}
func (neq Neq) WriteTo(w Writer) error { func (neq Neq) WriteTo(w Writer) error {
var args = make([]interface{}, 0, len(neq)) var args = make([]interface{}, 0, len(neq))
var i = 0 var i = 0
for k, v := range neq { for _, k := range neq.sortedKeys() {
v := neq[k]
switch v.(type) { switch v.(type) {
case []int, []int64, []string, []int32, []int16, []int8: case []int, []int64, []string, []int32, []int16, []int8:
if err := NotIn(k, v).WriteTo(w); err != nil { if err := NotIn(k, v).WriteTo(w); err != nil {
@ -76,3 +80,15 @@ func (neq Neq) Or(conds ...Cond) Cond {
func (neq Neq) IsValid() bool { func (neq Neq) IsValid() bool {
return len(neq) > 0 return len(neq) > 0
} }
// sortedKeys returns all keys of this Neq sorted with sort.Strings.
// It is used internally for consistent ordering when generating
// SQL, see https://github.com/go-xorm/builder/issues/10
func (neq Neq) sortedKeys() []string {
keys := make([]string, 0, len(neq))
for key := range neq {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

View File

@ -21,6 +21,18 @@ func (not Not) WriteTo(w Writer) error {
if _, err := fmt.Fprint(w, "("); err != nil { if _, err := fmt.Fprint(w, "("); err != nil {
return err return err
} }
case Eq:
if len(not[0].(Eq)) > 1 {
if _, err := fmt.Fprint(w, "("); err != nil {
return err
}
}
case Neq:
if len(not[0].(Neq)) > 1 {
if _, err := fmt.Fprint(w, "("); err != nil {
return err
}
}
} }
if err := not[0].WriteTo(w); err != nil { if err := not[0].WriteTo(w); err != nil {
@ -32,6 +44,18 @@ func (not Not) WriteTo(w Writer) error {
if _, err := fmt.Fprint(w, ")"); err != nil { if _, err := fmt.Fprint(w, ")"); err != nil {
return err return err
} }
case Eq:
if len(not[0].(Eq)) > 1 {
if _, err := fmt.Fprint(w, ")"); err != nil {
return err
}
}
case Neq:
if len(not[0].(Neq)) > 1 {
if _, err := fmt.Fprint(w, ")"); err != nil {
return err
}
}
} }
return nil return nil

119
vendor/github.com/go-xorm/builder/strings_builder.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package builder
import (
"unicode/utf8"
"unsafe"
)
// A StringBuilder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type StringBuilder struct {
addr *StringBuilder // of receiver, to detect copies by value
buf []byte
}
// noescape hides a pointer from escape analysis. noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input. noescape is inlined and currently
// compiles down to zero instructions.
// USE CAREFULLY!
// This was copied from the runtime; see issues 23382 and 7921.
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
func (b *StringBuilder) copyCheck() {
if b.addr == nil {
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*StringBuilder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
// String returns the accumulated string.
func (b *StringBuilder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
// Len returns the number of accumulated bytes; b.Len() == len(b.String()).
func (b *StringBuilder) Len() int { return len(b.buf) }
// Reset resets the Builder to be empty.
func (b *StringBuilder) Reset() {
b.addr = nil
b.buf = nil
}
// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *StringBuilder) grow(n int) {
buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
copy(buf, b.buf)
b.buf = buf
}
// Grow grows b's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to b
// without another allocation. If n is negative, Grow panics.
func (b *StringBuilder) Grow(n int) {
b.copyCheck()
if n < 0 {
panic("strings.Builder.Grow: negative count")
}
if cap(b.buf)-len(b.buf) < n {
b.grow(n)
}
}
// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *StringBuilder) Write(p []byte) (int, error) {
b.copyCheck()
b.buf = append(b.buf, p...)
return len(p), nil
}
// WriteByte appends the byte c to b's buffer.
// The returned error is always nil.
func (b *StringBuilder) WriteByte(c byte) error {
b.copyCheck()
b.buf = append(b.buf, c)
return nil
}
// WriteRune appends the UTF-8 encoding of Unicode code point r to b's buffer.
// It returns the length of r and a nil error.
func (b *StringBuilder) WriteRune(r rune) (int, error) {
b.copyCheck()
if r < utf8.RuneSelf {
b.buf = append(b.buf, byte(r))
return 1, nil
}
l := len(b.buf)
if cap(b.buf)-l < utf8.UTFMax {
b.grow(utf8.UTFMax)
}
n := utf8.EncodeRune(b.buf[l:l+utf8.UTFMax], r)
b.buf = b.buf[:l+n]
return n, nil
}
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *StringBuilder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}

View File

@ -147,12 +147,12 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) {
} }
fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1]) fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1])
} else { } else {
return nil, fmt.Errorf("field %v is not valid", col.FieldName) return nil, fmt.Errorf("field %v is not valid", col.FieldName)
} }
} }
if !fieldValue.IsValid() { if !fieldValue.IsValid() {
return nil, fmt.Errorf("field %v is not valid", col.FieldName) return nil, fmt.Errorf("field %v is not valid", col.FieldName)
} }
return &fieldValue, nil return &fieldValue, nil

57
vendor/github.com/go-xorm/core/db.go generated vendored
View File

@ -7,6 +7,11 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"regexp" "regexp"
"sync"
)
var (
DefaultCacheSize = 200
) )
func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { func MapToSlice(query string, mp interface{}) (string, []interface{}, error) {
@ -58,9 +63,16 @@ func StructToSlice(query string, st interface{}) (string, []interface{}, error)
return query, args, nil return query, args, nil
} }
type cacheStruct struct {
value reflect.Value
idx int
}
type DB struct { type DB struct {
*sql.DB *sql.DB
Mapper IMapper Mapper IMapper
reflectCache map[reflect.Type]*cacheStruct
reflectCacheMutex sync.RWMutex
} }
func Open(driverName, dataSourceName string) (*DB, error) { func Open(driverName, dataSourceName string) (*DB, error) {
@ -68,11 +80,32 @@ func Open(driverName, dataSourceName string) (*DB, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &DB{db, NewCacheMapper(&SnakeMapper{})}, nil return &DB{
DB: db,
Mapper: NewCacheMapper(&SnakeMapper{}),
reflectCache: make(map[reflect.Type]*cacheStruct),
}, nil
} }
func FromDB(db *sql.DB) *DB { func FromDB(db *sql.DB) *DB {
return &DB{db, NewCacheMapper(&SnakeMapper{})} return &DB{
DB: db,
Mapper: NewCacheMapper(&SnakeMapper{}),
reflectCache: make(map[reflect.Type]*cacheStruct),
}
}
func (db *DB) reflectNew(typ reflect.Type) reflect.Value {
db.reflectCacheMutex.Lock()
defer db.reflectCacheMutex.Unlock()
cs, ok := db.reflectCache[typ]
if !ok || cs.idx+1 > DefaultCacheSize-1 {
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0}
db.reflectCache[typ] = cs
} else {
cs.idx = cs.idx + 1
}
return cs.value.Index(cs.idx).Addr()
} }
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
@ -83,7 +116,7 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
} }
return nil, err return nil, err
} }
return &Rows{rows, db.Mapper}, nil return &Rows{rows, db}, nil
} }
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) {
@ -128,8 +161,8 @@ func (db *DB) QueryRowStruct(query string, st interface{}) *Row {
type Stmt struct { type Stmt struct {
*sql.Stmt *sql.Stmt
Mapper IMapper db *DB
names map[string]int names map[string]int
} }
func (db *DB) Prepare(query string) (*Stmt, error) { func (db *DB) Prepare(query string) (*Stmt, error) {
@ -145,7 +178,7 @@ func (db *DB) Prepare(query string) (*Stmt, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Stmt{stmt, db.Mapper, names}, nil return &Stmt{stmt, db, names}, nil
} }
func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) {
@ -179,7 +212,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Rows{rows, s.Mapper}, nil return &Rows{rows, s.db}, nil
} }
func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) {
@ -274,7 +307,7 @@ func (EmptyScanner) Scan(src interface{}) error {
type Tx struct { type Tx struct {
*sql.Tx *sql.Tx
Mapper IMapper db *DB
} }
func (db *DB) Begin() (*Tx, error) { func (db *DB) Begin() (*Tx, error) {
@ -282,7 +315,7 @@ func (db *DB) Begin() (*Tx, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Tx{tx, db.Mapper}, nil return &Tx{tx, db}, nil
} }
func (tx *Tx) Prepare(query string) (*Stmt, error) { func (tx *Tx) Prepare(query string) (*Stmt, error) {
@ -298,7 +331,7 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Stmt{stmt, tx.Mapper, names}, nil return &Stmt{stmt, tx.db, names}, nil
} }
func (tx *Tx) Stmt(stmt *Stmt) *Stmt { func (tx *Tx) Stmt(stmt *Stmt) *Stmt {
@ -327,7 +360,7 @@ func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Rows{rows, tx.Mapper}, nil return &Rows{rows, tx.db}, nil
} }
func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) {

View File

@ -74,6 +74,7 @@ type Dialect interface {
GetIndexes(tableName string) (map[string]*Index, error) GetIndexes(tableName string) (map[string]*Index, error)
Filters() []Filter Filters() []Filter
SetParams(params map[string]string)
} }
func OpenDialect(dialect Dialect) (*DB, error) { func OpenDialect(dialect Dialect) (*DB, error) {
@ -148,7 +149,8 @@ func (db *Base) SupportDropIfExists() bool {
} }
func (db *Base) DropTableSql(tableName string) string { func (db *Base) DropTableSql(tableName string) string {
return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) quote := db.dialect.Quote
return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName))
} }
func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) {
@ -289,6 +291,9 @@ func (b *Base) LogSQL(sql string, args []interface{}) {
} }
} }
func (b *Base) SetParams(params map[string]string) {
}
var ( var (
dialects = map[string]func() Dialect{} dialects = map[string]func() Dialect{}
) )

View File

@ -37,9 +37,9 @@ func (q *Quoter) Quote(content string) string {
func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string { func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string {
quoter := NewQuoter(dialect) quoter := NewQuoter(dialect)
if table != nil && len(table.PrimaryKeys) == 1 { if table != nil && len(table.PrimaryKeys) == 1 {
sql = strings.Replace(sql, "`(id)`", quoter.Quote(table.PrimaryKeys[0]), -1) sql = strings.Replace(sql, " `(id)` ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
sql = strings.Replace(sql, quoter.Quote("(id)"), quoter.Quote(table.PrimaryKeys[0]), -1) sql = strings.Replace(sql, " "+quoter.Quote("(id)")+" ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
return strings.Replace(sql, "(id)", quoter.Quote(table.PrimaryKeys[0]), -1) return strings.Replace(sql, " (id) ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
} }
return sql return sql
} }

View File

@ -22,6 +22,8 @@ type Index struct {
func (index *Index) XName(tableName string) string { func (index *Index) XName(tableName string) string {
if !strings.HasPrefix(index.Name, "UQE_") && if !strings.HasPrefix(index.Name, "UQE_") &&
!strings.HasPrefix(index.Name, "IDX_") { !strings.HasPrefix(index.Name, "IDX_") {
tableName = strings.Replace(tableName, `"`, "", -1)
tableName = strings.Replace(tableName, `.`, "_", -1)
if index.Type == UniqueType { if index.Type == UniqueType {
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
} }

View File

@ -9,7 +9,7 @@ import (
type Rows struct { type Rows struct {
*sql.Rows *sql.Rows
Mapper IMapper db *DB
} }
func (rs *Rows) ToMapString() ([]map[string]string, error) { func (rs *Rows) ToMapString() ([]map[string]string, error) {
@ -105,7 +105,7 @@ func (rs *Rows) ScanStructByName(dest interface{}) error {
newDest := make([]interface{}, len(cols)) newDest := make([]interface{}, len(cols))
var v EmptyScanner var v EmptyScanner
for j, name := range cols { for j, name := range cols {
f := fieldByName(vv.Elem(), rs.Mapper.Table2Obj(name)) f := fieldByName(vv.Elem(), rs.db.Mapper.Table2Obj(name))
if f.IsValid() { if f.IsValid() {
newDest[j] = f.Addr().Interface() newDest[j] = f.Addr().Interface()
} else { } else {
@ -116,36 +116,6 @@ func (rs *Rows) ScanStructByName(dest interface{}) error {
return rs.Rows.Scan(newDest...) return rs.Rows.Scan(newDest...)
} }
type cacheStruct struct {
value reflect.Value
idx int
}
var (
reflectCache = make(map[reflect.Type]*cacheStruct)
reflectCacheMutex sync.RWMutex
)
func ReflectNew(typ reflect.Type) reflect.Value {
reflectCacheMutex.RLock()
cs, ok := reflectCache[typ]
reflectCacheMutex.RUnlock()
const newSize = 200
if !ok || cs.idx+1 > newSize-1 {
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), newSize, newSize), 0}
reflectCacheMutex.Lock()
reflectCache[typ] = cs
reflectCacheMutex.Unlock()
} else {
reflectCacheMutex.Lock()
cs.idx = cs.idx + 1
reflectCacheMutex.Unlock()
}
return cs.value.Index(cs.idx).Addr()
}
// scan data to a slice's pointer, slice's length should equal to columns' number // scan data to a slice's pointer, slice's length should equal to columns' number
func (rs *Rows) ScanSlice(dest interface{}) error { func (rs *Rows) ScanSlice(dest interface{}) error {
vv := reflect.ValueOf(dest) vv := reflect.ValueOf(dest)
@ -197,9 +167,7 @@ func (rs *Rows) ScanMap(dest interface{}) error {
vvv := vv.Elem() vvv := vv.Elem()
for i, _ := range cols { for i, _ := range cols {
newDest[i] = ReflectNew(vvv.Type().Elem()).Interface() newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface()
//v := reflect.New(vvv.Type().Elem())
//newDest[i] = v.Interface()
} }
err = rs.Rows.Scan(newDest...) err = rs.Rows.Scan(newDest...)
@ -215,32 +183,6 @@ func (rs *Rows) ScanMap(dest interface{}) error {
return nil return nil
} }
/*func (rs *Rows) ScanMap(dest interface{}) error {
vv := reflect.ValueOf(dest)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
return errors.New("dest should be a map's pointer")
}
cols, err := rs.Columns()
if err != nil {
return err
}
newDest := make([]interface{}, len(cols))
err = rs.ScanSlice(newDest)
if err != nil {
return err
}
vvv := vv.Elem()
for i, name := range cols {
vname := reflect.ValueOf(name)
vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem())
}
return nil
}*/
type Row struct { type Row struct {
rows *Rows rows *Rows
// One of these two will be non-nil: // One of these two will be non-nil:

View File

@ -49,7 +49,6 @@ func NewTable(name string, t reflect.Type) *Table {
} }
func (table *Table) columnsByName(name string) []*Column { func (table *Table) columnsByName(name string) []*Column {
n := len(name) n := len(name)
for k := range table.columnsMap { for k := range table.columnsMap {
@ -75,7 +74,6 @@ func (table *Table) GetColumn(name string) *Column {
} }
func (table *Table) GetColumnIdx(name string, idx int) *Column { func (table *Table) GetColumnIdx(name string, idx int) *Column {
cols := table.columnsByName(name) cols := table.columnsByName(name)
if cols != nil && idx < len(cols) { if cols != nil && idx < len(cols) {

View File

@ -69,15 +69,18 @@ var (
Enum = "ENUM" Enum = "ENUM"
Set = "SET" Set = "SET"
Char = "CHAR" Char = "CHAR"
Varchar = "VARCHAR" Varchar = "VARCHAR"
NVarchar = "NVARCHAR" NVarchar = "NVARCHAR"
TinyText = "TINYTEXT" TinyText = "TINYTEXT"
Text = "TEXT" Text = "TEXT"
Clob = "CLOB" NText = "NTEXT"
MediumText = "MEDIUMTEXT" Clob = "CLOB"
LongText = "LONGTEXT" MediumText = "MEDIUMTEXT"
Uuid = "UUID" LongText = "LONGTEXT"
Uuid = "UUID"
UniqueIdentifier = "UNIQUEIDENTIFIER"
SysName = "SYSNAME"
Date = "DATE" Date = "DATE"
DateTime = "DATETIME" DateTime = "DATETIME"
@ -128,10 +131,12 @@ var (
NVarchar: TEXT_TYPE, NVarchar: TEXT_TYPE,
TinyText: TEXT_TYPE, TinyText: TEXT_TYPE,
Text: TEXT_TYPE, Text: TEXT_TYPE,
NText: TEXT_TYPE,
MediumText: TEXT_TYPE, MediumText: TEXT_TYPE,
LongText: TEXT_TYPE, LongText: TEXT_TYPE,
Uuid: TEXT_TYPE, Uuid: TEXT_TYPE,
Clob: TEXT_TYPE, Clob: TEXT_TYPE,
SysName: TEXT_TYPE,
Date: TIME_TYPE, Date: TIME_TYPE,
DateTime: TIME_TYPE, DateTime: TIME_TYPE,
@ -148,11 +153,12 @@ var (
Binary: BLOB_TYPE, Binary: BLOB_TYPE,
VarBinary: BLOB_TYPE, VarBinary: BLOB_TYPE,
TinyBlob: BLOB_TYPE, TinyBlob: BLOB_TYPE,
Blob: BLOB_TYPE, Blob: BLOB_TYPE,
MediumBlob: BLOB_TYPE, MediumBlob: BLOB_TYPE,
LongBlob: BLOB_TYPE, LongBlob: BLOB_TYPE,
Bytea: BLOB_TYPE, Bytea: BLOB_TYPE,
UniqueIdentifier: BLOB_TYPE,
Bool: NUMERIC_TYPE, Bool: NUMERIC_TYPE,
@ -289,9 +295,9 @@ func SQLType2Type(st SQLType) reflect.Type {
return reflect.TypeOf(float32(1)) return reflect.TypeOf(float32(1))
case Double: case Double:
return reflect.TypeOf(float64(1)) return reflect.TypeOf(float64(1))
case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob: case Char, Varchar, NVarchar, TinyText, Text, NText, MediumText, LongText, Enum, Set, Uuid, Clob, SysName:
return reflect.TypeOf("") return reflect.TypeOf("")
case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier:
return reflect.TypeOf([]byte{}) return reflect.TypeOf([]byte{})
case Bool: case Bool:
return reflect.TypeOf(true) return reflect.TypeOf(true)

View File

@ -172,12 +172,33 @@ type mysql struct {
allowAllFiles bool allowAllFiles bool
allowOldPasswords bool allowOldPasswords bool
clientFoundRows bool clientFoundRows bool
rowFormat string
} }
func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
return db.Base.Init(d, db, uri, drivername, dataSourceName) return db.Base.Init(d, db, uri, drivername, dataSourceName)
} }
func (db *mysql) SetParams(params map[string]string) {
rowFormat, ok := params["rowFormat"]
if ok {
var t = strings.ToUpper(rowFormat)
switch t {
case "COMPACT":
fallthrough
case "REDUNDANT":
fallthrough
case "DYNAMIC":
fallthrough
case "COMPRESSED":
db.rowFormat = t
break
default:
break
}
}
}
func (db *mysql) SqlType(c *core.Column) string { func (db *mysql) SqlType(c *core.Column) string {
var res string var res string
switch t := c.SQLType.Name; t { switch t := c.SQLType.Name; t {
@ -487,6 +508,59 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) {
return indexes, nil return indexes, nil
} }
func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string {
var sql string
sql = "CREATE TABLE IF NOT EXISTS "
if tableName == "" {
tableName = table.Name
}
sql += db.Quote(tableName)
sql += " ("
if len(table.ColumnsSeq()) > 0 {
pkList := table.PrimaryKeys
for _, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
if col.IsPrimaryKey && len(pkList) == 1 {
sql += col.String(db)
} else {
sql += col.StringNoPk(db)
}
sql = strings.TrimSpace(sql)
if len(col.Comment) > 0 {
sql += " COMMENT '" + col.Comment + "'"
}
sql += ", "
}
if len(pkList) > 1 {
sql += "PRIMARY KEY ( "
sql += db.Quote(strings.Join(pkList, db.Quote(",")))
sql += " ), "
}
sql = sql[:len(sql)-2]
}
sql += ")"
if storeEngine != "" {
sql += " ENGINE=" + storeEngine
}
if len(charset) == 0 {
charset = db.URI().Charset
} else if len(charset) > 0 {
sql += " DEFAULT CHARSET " + charset
}
if db.rowFormat != "" {
sql += " ROW_FORMAT=" + db.rowFormat
}
return sql
}
func (db *mysql) Filters() []core.Filter { func (db *mysql) Filters() []core.Filter {
return []core.Filter{&core.IdFilter{}} return []core.Filter{&core.IdFilter{}}
} }

View File

@ -769,14 +769,21 @@ var (
DefaultPostgresSchema = "public" DefaultPostgresSchema = "public"
) )
const postgresPublicSchema = "public"
type postgres struct { type postgres struct {
core.Base core.Base
schema string
} }
func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
db.schema = DefaultPostgresSchema err := db.Base.Init(d, db, uri, drivername, dataSourceName)
return db.Base.Init(d, db, uri, drivername, dataSourceName) if err != nil {
return err
}
if db.Schema == "" {
db.Schema = DefaultPostgresSchema
}
return nil
} }
func (db *postgres) SqlType(c *core.Column) string { func (db *postgres) SqlType(c *core.Column) string {
@ -873,32 +880,42 @@ func (db *postgres) IndexOnTable() bool {
} }
func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) { func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
args := []interface{}{tableName, idxName} if len(db.Schema) == 0 {
args := []interface{}{tableName, idxName}
return `SELECT indexname FROM pg_indexes WHERE tablename = ? AND indexname = ?`, args
}
args := []interface{}{db.Schema, tableName, idxName}
return `SELECT indexname FROM pg_indexes ` + return `SELECT indexname FROM pg_indexes ` +
`WHERE tablename = ? AND indexname = ?`, args `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args
} }
func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) {
args := []interface{}{tableName} if len(db.Schema) == 0 {
return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args args := []interface{}{tableName}
return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args
}
args := []interface{}{db.Schema, tableName}
return `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, args
} }
/*func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
args := []interface{}{tableName, colName}
return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" +
" AND column_name = ?", args
}*/
func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string { func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string {
return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", if len(db.Schema) == 0 {
tableName, col.Name, db.SqlType(col)) return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s",
tableName, col.Name, db.SqlType(col))
}
return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s",
db.Schema, tableName, col.Name, db.SqlType(col))
} }
func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { func (db *postgres) DropIndexSql(tableName string, index *core.Index) string {
//var unique string
quote := db.Quote quote := db.Quote
idxName := index.Name idxName := index.Name
tableName = strings.Replace(tableName, `"`, "", -1)
tableName = strings.Replace(tableName, `.`, "_", -1)
if !strings.HasPrefix(idxName, "UQE_") && if !strings.HasPrefix(idxName, "UQE_") &&
!strings.HasPrefix(idxName, "IDX_") { !strings.HasPrefix(idxName, "IDX_") {
if index.Type == core.UniqueType { if index.Type == core.UniqueType {
@ -907,13 +924,21 @@ func (db *postgres) DropIndexSql(tableName string, index *core.Index) string {
idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
} }
} }
if db.Uri.Schema != "" {
idxName = db.Uri.Schema + "." + idxName
}
return fmt.Sprintf("DROP INDEX %v", quote(idxName)) return fmt.Sprintf("DROP INDEX %v", quote(idxName))
} }
func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) {
args := []interface{}{tableName, colName} args := []interface{}{db.Schema, tableName, colName}
query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_name = $2" +
" AND column_name = $2" " AND column_name = $3"
if len(db.Schema) == 0 {
args = []interface{}{tableName, colName}
query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" +
" AND column_name = $2"
}
db.LogSQL(query, args) db.LogSQL(query, args)
rows, err := db.DB().Query(query, args...) rows, err := db.DB().Query(query, args...)
@ -926,8 +951,7 @@ func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) {
} }
func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
// FIXME: the schema should be replaced by user custom's args := []interface{}{tableName}
args := []interface{}{tableName, db.schema}
s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix , s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix ,
CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey,
CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey
@ -938,7 +962,15 @@ FROM pg_attribute f
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey) LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid LEFT JOIN pg_class AS g ON p.confrelid = g.oid
LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name
WHERE c.relkind = 'r'::char AND c.relname = $1 AND s.table_schema = $2 AND f.attnum > 0 ORDER BY f.attnum;` WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;`
var f string
if len(db.Schema) != 0 {
args = append(args, db.Schema)
f = " AND s.table_schema = $2"
}
s = fmt.Sprintf(s, f)
db.LogSQL(s, args) db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...) rows, err := db.DB().Query(s, args...)
@ -1028,8 +1060,13 @@ WHERE c.relkind = 'r'::char AND c.relname = $1 AND s.table_schema = $2 AND f.att
} }
func (db *postgres) GetTables() ([]*core.Table, error) { func (db *postgres) GetTables() ([]*core.Table, error) {
args := []interface{}{db.schema} args := []interface{}{}
s := fmt.Sprintf("SELECT tablename FROM pg_tables WHERE schemaname = $1") s := "SELECT tablename FROM pg_tables"
if len(db.Schema) != 0 {
args = append(args, db.Schema)
s = s + " WHERE schemaname = $1"
}
db.LogSQL(s, args) db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...) rows, err := db.DB().Query(s, args...)
@ -1053,8 +1090,12 @@ func (db *postgres) GetTables() ([]*core.Table, error) {
} }
func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) { func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) {
args := []interface{}{db.schema, tableName} args := []interface{}{tableName}
s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE schemaname=$1 AND tablename=$2") s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1")
if len(db.Schema) != 0 {
args = append(args, db.Schema)
s = s + " AND schemaname=$2"
}
db.LogSQL(s, args) db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...) rows, err := db.DB().Query(s, args...)
@ -1182,3 +1223,15 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
return db, nil return db, nil
} }
type pqDriverPgx struct {
pqDriver
}
func (pgx *pqDriverPgx) Parse(driverName, dataSourceName string) (*core.Uri, error) {
// Remove the leading characters for driver to work
if len(dataSourceName) >= 9 && dataSourceName[0] == 0 {
dataSourceName = dataSourceName[9:]
}
return pgx.pqDriver.Parse(driverName, dataSourceName)
}

View File

@ -49,6 +49,35 @@ type Engine struct {
tagHandlers map[string]tagHandler tagHandlers map[string]tagHandler
engineGroup *EngineGroup engineGroup *EngineGroup
cachers map[string]core.Cacher
cacherLock sync.RWMutex
}
func (engine *Engine) setCacher(tableName string, cacher core.Cacher) {
engine.cacherLock.Lock()
engine.cachers[tableName] = cacher
engine.cacherLock.Unlock()
}
func (engine *Engine) SetCacher(tableName string, cacher core.Cacher) {
engine.setCacher(tableName, cacher)
}
func (engine *Engine) getCacher(tableName string) core.Cacher {
var cacher core.Cacher
var ok bool
engine.cacherLock.RLock()
cacher, ok = engine.cachers[tableName]
engine.cacherLock.RUnlock()
if !ok && !engine.disableGlobalCache {
cacher = engine.Cacher
}
return cacher
}
func (engine *Engine) GetCacher(tableName string) core.Cacher {
return engine.getCacher(tableName)
} }
// BufferSize sets buffer size for iterate // BufferSize sets buffer size for iterate
@ -165,7 +194,7 @@ func (engine *Engine) Quote(value string) string {
} }
// QuoteTo quotes string and writes into the buffer // QuoteTo quotes string and writes into the buffer
func (engine *Engine) QuoteTo(buf *bytes.Buffer, value string) { func (engine *Engine) QuoteTo(buf *builder.StringBuilder, value string) {
if buf == nil { if buf == nil {
return return
} }
@ -245,13 +274,7 @@ func (engine *Engine) NoCascade() *Session {
// MapCacher Set a table use a special cacher // MapCacher Set a table use a special cacher
func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error { func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error {
v := rValue(bean) engine.setCacher(engine.TableName(bean, true), cacher)
tb, err := engine.autoMapType(v)
if err != nil {
return err
}
tb.Cacher = cacher
return nil return nil
} }
@ -536,33 +559,6 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D
return nil return nil
} }
func (engine *Engine) tableName(beanOrTableName interface{}) (string, error) {
v := rValue(beanOrTableName)
if v.Type().Kind() == reflect.String {
return beanOrTableName.(string), nil
} else if v.Type().Kind() == reflect.Struct {
return engine.tbName(v), nil
}
return "", errors.New("bean should be a struct or struct's point")
}
func (engine *Engine) tbName(v reflect.Value) string {
if tb, ok := v.Interface().(TableName); ok {
return tb.TableName()
}
if v.Type().Kind() == reflect.Ptr {
if tb, ok := reflect.Indirect(v).Interface().(TableName); ok {
return tb.TableName()
}
} else if v.CanAddr() {
if tb, ok := v.Addr().Interface().(TableName); ok {
return tb.TableName()
}
}
return engine.TableMapper.Obj2Table(reflect.Indirect(v).Type().Name())
}
// Cascade use cascade or not // Cascade use cascade or not
func (engine *Engine) Cascade(trueOrFalse ...bool) *Session { func (engine *Engine) Cascade(trueOrFalse ...bool) *Session {
session := engine.NewSession() session := engine.NewSession()
@ -846,7 +842,7 @@ func (engine *Engine) TableInfo(bean interface{}) *Table {
if err != nil { if err != nil {
engine.logger.Error(err) engine.logger.Error(err)
} }
return &Table{tb, engine.tbName(v)} return &Table{tb, engine.TableName(bean)}
} }
func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) { func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) {
@ -861,15 +857,6 @@ func addIndex(indexName string, table *core.Table, col *core.Column, indexType i
} }
} }
func (engine *Engine) newTable() *core.Table {
table := core.NewEmptyTable()
if !engine.disableGlobalCache {
table.Cacher = engine.Cacher
}
return table
}
// TableName table name interface to define customerize table name // TableName table name interface to define customerize table name
type TableName interface { type TableName interface {
TableName() string TableName() string
@ -881,21 +868,9 @@ var (
func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
t := v.Type() t := v.Type()
table := engine.newTable() table := core.NewEmptyTable()
if tb, ok := v.Interface().(TableName); ok {
table.Name = tb.TableName()
} else {
if v.CanAddr() {
if tb, ok = v.Addr().Interface().(TableName); ok {
table.Name = tb.TableName()
}
}
if table.Name == "" {
table.Name = engine.TableMapper.Obj2Table(t.Name())
}
}
table.Type = t table.Type = t
table.Name = engine.tbNameForMap(v)
var idFieldColName string var idFieldColName string
var hasCacheTag, hasNoCacheTag bool var hasCacheTag, hasNoCacheTag bool
@ -1049,15 +1024,15 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
if hasCacheTag { if hasCacheTag {
if engine.Cacher != nil { // !nash! use engine's cacher if provided if engine.Cacher != nil { // !nash! use engine's cacher if provided
engine.logger.Info("enable cache on table:", table.Name) engine.logger.Info("enable cache on table:", table.Name)
table.Cacher = engine.Cacher engine.setCacher(table.Name, engine.Cacher)
} else { } else {
engine.logger.Info("enable LRU cache on table:", table.Name) engine.logger.Info("enable LRU cache on table:", table.Name)
table.Cacher = NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) // !nashtsai! HACK use LRU cacher for now engine.setCacher(table.Name, NewLRUCacher2(NewMemoryStore(), time.Hour, 10000))
} }
} }
if hasNoCacheTag { if hasNoCacheTag {
engine.logger.Info("no cache on table:", table.Name) engine.logger.Info("disable cache on table:", table.Name)
table.Cacher = nil engine.setCacher(table.Name, nil)
} }
return table, nil return table, nil
@ -1116,7 +1091,25 @@ func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) {
pk := make([]interface{}, len(table.PrimaryKeys)) pk := make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() { for i, col := range table.PKColumns() {
var err error var err error
pkField := v.FieldByName(col.FieldName)
fieldName := col.FieldName
for {
parts := strings.SplitN(fieldName, ".", 2)
if len(parts) == 1 {
break
}
v = v.FieldByName(parts[0])
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, ErrUnSupportedType
}
fieldName = parts[1]
}
pkField := v.FieldByName(fieldName)
switch pkField.Kind() { switch pkField.Kind() {
case reflect.String: case reflect.String:
pk[i], err = engine.idTypeAssertion(col, pkField.String()) pk[i], err = engine.idTypeAssertion(col, pkField.String())
@ -1162,26 +1155,10 @@ func (engine *Engine) CreateUniques(bean interface{}) error {
return session.CreateUniques(bean) return session.CreateUniques(bean)
} }
func (engine *Engine) getCacher2(table *core.Table) core.Cacher {
return table.Cacher
}
// ClearCacheBean if enabled cache, clear the cache bean // ClearCacheBean if enabled cache, clear the cache bean
func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
v := rValue(bean) tableName := engine.TableName(bean)
t := v.Type() cacher := engine.getCacher(tableName)
if t.Kind() != reflect.Struct {
return errors.New("error params")
}
tableName := engine.tbName(v)
table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher
if cacher == nil {
cacher = engine.Cacher
}
if cacher != nil { if cacher != nil {
cacher.ClearIds(tableName) cacher.ClearIds(tableName)
cacher.DelBean(tableName, id) cacher.DelBean(tableName, id)
@ -1192,21 +1169,8 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
// ClearCache if enabled cache, clear some tables' cache // ClearCache if enabled cache, clear some tables' cache
func (engine *Engine) ClearCache(beans ...interface{}) error { func (engine *Engine) ClearCache(beans ...interface{}) error {
for _, bean := range beans { for _, bean := range beans {
v := rValue(bean) tableName := engine.TableName(bean)
t := v.Type() cacher := engine.getCacher(tableName)
if t.Kind() != reflect.Struct {
return errors.New("error params")
}
tableName := engine.tbName(v)
table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher
if cacher == nil {
cacher = engine.Cacher
}
if cacher != nil { if cacher != nil {
cacher.ClearIds(tableName) cacher.ClearIds(tableName)
cacher.ClearBeans(tableName) cacher.ClearBeans(tableName)
@ -1224,13 +1188,13 @@ func (engine *Engine) Sync(beans ...interface{}) error {
for _, bean := range beans { for _, bean := range beans {
v := rValue(bean) v := rValue(bean)
tableName := engine.tbName(v) tableNameNoSchema := engine.TableName(bean)
table, err := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil { if err != nil {
return err return err
} }
isExist, err := session.Table(bean).isTableExist(tableName) isExist, err := session.Table(bean).isTableExist(tableNameNoSchema)
if err != nil { if err != nil {
return err return err
} }
@ -1256,12 +1220,12 @@ func (engine *Engine) Sync(beans ...interface{}) error {
} }
} else { } else {
for _, col := range table.Columns() { for _, col := range table.Columns() {
isExist, err := engine.dialect.IsColumnExist(tableName, col.Name) isExist, err := engine.dialect.IsColumnExist(tableNameNoSchema, col.Name)
if err != nil { if err != nil {
return err return err
} }
if !isExist { if !isExist {
if err := session.statement.setRefValue(v); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return err return err
} }
err = session.addColumn(col.Name) err = session.addColumn(col.Name)
@ -1272,35 +1236,35 @@ func (engine *Engine) Sync(beans ...interface{}) error {
} }
for name, index := range table.Indexes { for name, index := range table.Indexes {
if err := session.statement.setRefValue(v); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return err return err
} }
if index.Type == core.UniqueType { if index.Type == core.UniqueType {
isExist, err := session.isIndexExist2(tableName, index.Cols, true) isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true)
if err != nil { if err != nil {
return err return err
} }
if !isExist { if !isExist {
if err := session.statement.setRefValue(v); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return err return err
} }
err = session.addUnique(tableName, name) err = session.addUnique(tableNameNoSchema, name)
if err != nil { if err != nil {
return err return err
} }
} }
} else if index.Type == core.IndexType { } else if index.Type == core.IndexType {
isExist, err := session.isIndexExist2(tableName, index.Cols, false) isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, false)
if err != nil { if err != nil {
return err return err
} }
if !isExist { if !isExist {
if err := session.statement.setRefValue(v); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return err return err
} }
err = session.addIndex(tableName, name) err = session.addIndex(tableNameNoSchema, name)
if err != nil { if err != nil {
return err return err
} }
@ -1453,6 +1417,13 @@ func (engine *Engine) Find(beans interface{}, condiBeans ...interface{}) error {
return session.Find(beans, condiBeans...) return session.Find(beans, condiBeans...)
} }
// FindAndCount find the results and also return the counts
func (engine *Engine) FindAndCount(rowsSlicePtr interface{}, condiBean ...interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
return session.FindAndCount(rowsSlicePtr, condiBean...)
}
// Iterate record by record handle records from table, bean's non-empty fields // Iterate record by record handle records from table, bean's non-empty fields
// are conditions. // are conditions.
func (engine *Engine) Iterate(bean interface{}, fun IterFunc) error { func (engine *Engine) Iterate(bean interface{}, fun IterFunc) error {
@ -1629,6 +1600,11 @@ func (engine *Engine) SetTZDatabase(tz *time.Location) {
engine.DatabaseTZ = tz engine.DatabaseTZ = tz
} }
// SetSchema sets the schema of database
func (engine *Engine) SetSchema(schema string) {
engine.dialect.URI().Schema = schema
}
// Unscoped always disable struct tag "deleted" // Unscoped always disable struct tag "deleted"
func (engine *Engine) Unscoped() *Session { func (engine *Engine) Unscoped() *Session {
session := engine.NewSession() session := engine.NewSession()

View File

@ -9,6 +9,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"time" "time"
"github.com/go-xorm/builder" "github.com/go-xorm/builder"
@ -51,7 +52,9 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{},
fieldValuePtr, err := col.ValueOf(bean) fieldValuePtr, err := col.ValueOf(bean)
if err != nil { if err != nil {
engine.logger.Error(err) if !strings.Contains(err.Error(), "is not valid") {
engine.logger.Warn(err)
}
continue continue
} }

113
vendor/github.com/go-xorm/xorm/engine_table.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
// Copyright 2018 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"fmt"
"reflect"
"strings"
"github.com/go-xorm/core"
)
// TableNameWithSchema will automatically add schema prefix on table name
func (engine *Engine) tbNameWithSchema(v string) string {
// Add schema name as prefix of table name.
// Only for postgres database.
if engine.dialect.DBType() == core.POSTGRES &&
engine.dialect.URI().Schema != "" &&
engine.dialect.URI().Schema != postgresPublicSchema &&
strings.Index(v, ".") == -1 {
return engine.dialect.URI().Schema + "." + v
}
return v
}
// TableName returns table name with schema prefix if has
func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string {
tbName := engine.tbNameNoSchema(bean)
if len(includeSchema) > 0 && includeSchema[0] {
tbName = engine.tbNameWithSchema(tbName)
}
return tbName
}
// tbName get some table's table name
func (session *Session) tbNameNoSchema(table *core.Table) string {
if len(session.statement.AltTableName) > 0 {
return session.statement.AltTableName
}
return table.Name
}
func (engine *Engine) tbNameForMap(v reflect.Value) string {
if v.Type().Implements(tpTableName) {
return v.Interface().(TableName).TableName()
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
if v.Type().Implements(tpTableName) {
return v.Interface().(TableName).TableName()
}
}
return engine.TableMapper.Obj2Table(v.Type().Name())
}
func (engine *Engine) tbNameNoSchema(tablename interface{}) string {
switch tablename.(type) {
case []string:
t := tablename.([]string)
if len(t) > 1 {
return fmt.Sprintf("%v AS %v", engine.Quote(t[0]), engine.Quote(t[1]))
} else if len(t) == 1 {
return engine.Quote(t[0])
}
case []interface{}:
t := tablename.([]interface{})
l := len(t)
var table string
if l > 0 {
f := t[0]
switch f.(type) {
case string:
table = f.(string)
case TableName:
table = f.(TableName).TableName()
default:
v := rValue(f)
t := v.Type()
if t.Kind() == reflect.Struct {
table = engine.tbNameForMap(v)
} else {
table = engine.Quote(fmt.Sprintf("%v", f))
}
}
}
if l > 1 {
return fmt.Sprintf("%v AS %v", engine.Quote(table),
engine.Quote(fmt.Sprintf("%v", t[1])))
} else if l == 1 {
return engine.Quote(table)
}
case TableName:
return tablename.(TableName).TableName()
case string:
return tablename.(string)
case reflect.Value:
v := tablename.(reflect.Value)
return engine.tbNameForMap(v)
default:
v := rValue(tablename)
t := v.Type()
if t.Kind() == reflect.Struct {
return engine.tbNameForMap(v)
}
return engine.Quote(fmt.Sprintf("%v", tablename))
}
return ""
}

View File

@ -6,23 +6,44 @@ package xorm
import ( import (
"errors" "errors"
"fmt"
) )
var ( var (
// ErrParamsType params error // ErrParamsType params error
ErrParamsType = errors.New("Params type error") ErrParamsType = errors.New("Params type error")
// ErrTableNotFound table not found error // ErrTableNotFound table not found error
ErrTableNotFound = errors.New("Not found table") ErrTableNotFound = errors.New("Table not found")
// ErrUnSupportedType unsupported error // ErrUnSupportedType unsupported error
ErrUnSupportedType = errors.New("Unsupported type error") ErrUnSupportedType = errors.New("Unsupported type error")
// ErrNotExist record is not exist error // ErrNotExist record does not exist error
ErrNotExist = errors.New("Not exist error") ErrNotExist = errors.New("Record does not exist")
// ErrCacheFailed cache failed error // ErrCacheFailed cache failed error
ErrCacheFailed = errors.New("Cache failed") ErrCacheFailed = errors.New("Cache failed")
// ErrNeedDeletedCond delete needs less one condition error // ErrNeedDeletedCond delete needs less one condition error
ErrNeedDeletedCond = errors.New("Delete need at least one condition") ErrNeedDeletedCond = errors.New("Delete action needs at least one condition")
// ErrNotImplemented not implemented // ErrNotImplemented not implemented
ErrNotImplemented = errors.New("Not implemented") ErrNotImplemented = errors.New("Not implemented")
// ErrConditionType condition type unsupported // ErrConditionType condition type unsupported
ErrConditionType = errors.New("Unsupported conditon type") ErrConditionType = errors.New("Unsupported condition type")
) )
// ErrFieldIsNotExist columns does not exist
type ErrFieldIsNotExist struct {
FieldName string
TableName string
}
func (e ErrFieldIsNotExist) Error() string {
return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName)
}
// ErrFieldIsNotValid is not valid
type ErrFieldIsNotValid struct {
FieldName string
TableName string
}
func (e ErrFieldIsNotValid) Error() string {
return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName)
}

View File

@ -11,7 +11,6 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/go-xorm/core" "github.com/go-xorm/core"
) )
@ -293,19 +292,6 @@ func structName(v reflect.Type) string {
return v.Name() return v.Name()
} }
func col2NewCols(columns ...string) []string {
newColumns := make([]string, 0, len(columns))
for _, col := range columns {
col = strings.Replace(col, "`", "", -1)
col = strings.Replace(col, `"`, "", -1)
ccols := strings.Split(col, ",")
for _, c := range ccols {
newColumns = append(newColumns, strings.TrimSpace(c))
}
}
return newColumns
}
func sliceEq(left, right []string) bool { func sliceEq(left, right []string) bool {
if len(left) != len(right) { if len(left) != len(right) {
return false return false
@ -320,154 +306,6 @@ func sliceEq(left, right []string) bool {
return true return true
} }
func setColumnInt(bean interface{}, col *core.Column, t int64) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t)
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t))
}
}
}
func setColumnTime(bean interface{}, col *core.Column, t time.Time) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Struct:
v.Set(reflect.ValueOf(t).Convert(v.Type()))
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t.Unix())
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t.Unix()))
}
}
}
func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) {
colNames := make([]string, 0, len(table.ColumnsSeq()))
args := make([]interface{}, 0, len(table.ColumnsSeq()))
for _, col := range table.Columns() {
if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
}
}
if col.MapType == core.ONLYFROMDB {
continue
}
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return nil, nil, err
}
fieldValue := *fieldValuePtr
if col.IsAutoIncrement {
switch fieldValue.Type().Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
if fieldValue.Int() == 0 {
continue
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
if fieldValue.Uint() == 0 {
continue
}
case reflect.String:
if len(fieldValue.String()) == 0 {
continue
}
case reflect.Ptr:
if fieldValue.Pointer() == 0 {
continue
}
}
}
if col.IsDeleted {
continue
}
if session.statement.ColumnStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
} else if _, ok := session.statement.incrColumns[col.Name]; ok {
continue
} else if _, ok := session.statement.decrColumns[col.Name]; ok {
continue
}
}
if session.statement.OmitStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue
}
}
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int
fieldValue = reflect.ValueOf(nilValue)
}
}
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
} else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
} else {
arg, err := session.value2Interface(col, fieldValue)
if err != nil {
return colNames, args, err
}
args = append(args, arg)
}
if includeQuote {
colNames = append(colNames, session.engine.Quote(col.Name)+" = ?")
} else {
colNames = append(colNames, col.Name)
}
}
return colNames, args, nil
}
func indexName(tableName, idxName string) string { func indexName(tableName, idxName string) string {
return fmt.Sprintf("IDX_%v_%v", tableName, idxName) return fmt.Sprintf("IDX_%v_%v", tableName, idxName)
} }
func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) {
if len(m) == 0 {
return false, false
}
n := len(col.Name)
for mk := range m {
if len(mk) != n {
continue
}
if strings.EqualFold(mk, col.Name) {
return m[mk], true
}
}
return false, false
}

View File

@ -30,6 +30,7 @@ type Interface interface {
Exec(string, ...interface{}) (sql.Result, error) Exec(string, ...interface{}) (sql.Result, error)
Exist(bean ...interface{}) (bool, error) Exist(bean ...interface{}) (bool, error)
Find(interface{}, ...interface{}) error Find(interface{}, ...interface{}) error
FindAndCount(interface{}, ...interface{}) (int64, error)
Get(interface{}) (bool, error) Get(interface{}) (bool, error)
GroupBy(keys string) *Session GroupBy(keys string) *Session
ID(interface{}) *Session ID(interface{}) *Session
@ -41,6 +42,7 @@ type Interface interface {
IsTableExist(beanOrTableName interface{}) (bool, error) IsTableExist(beanOrTableName interface{}) (bool, error)
Iterate(interface{}, IterFunc) error Iterate(interface{}, IterFunc) error
Limit(int, ...int) *Session Limit(int, ...int) *Session
MustCols(columns ...string) *Session
NoAutoCondition(...bool) *Session NoAutoCondition(...bool) *Session
NotIn(string, ...interface{}) *Session NotIn(string, ...interface{}) *Session
Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session
@ -75,6 +77,7 @@ type EngineInterface interface {
Dialect() core.Dialect Dialect() core.Dialect
DropTables(...interface{}) error DropTables(...interface{}) error
DumpAllToFile(fp string, tp ...core.DbType) error DumpAllToFile(fp string, tp ...core.DbType) error
GetCacher(string) core.Cacher
GetColumnMapper() core.IMapper GetColumnMapper() core.IMapper
GetDefaultCacher() core.Cacher GetDefaultCacher() core.Cacher
GetTableMapper() core.IMapper GetTableMapper() core.IMapper
@ -83,9 +86,11 @@ type EngineInterface interface {
NewSession() *Session NewSession() *Session
NoAutoTime() *Session NoAutoTime() *Session
Quote(string) string Quote(string) string
SetCacher(string, core.Cacher)
SetDefaultCacher(core.Cacher) SetDefaultCacher(core.Cacher)
SetLogLevel(core.LogLevel) SetLogLevel(core.LogLevel)
SetMapper(core.IMapper) SetMapper(core.IMapper)
SetSchema(string)
SetTZDatabase(tz *time.Location) SetTZDatabase(tz *time.Location)
SetTZLocation(tz *time.Location) SetTZLocation(tz *time.Location)
ShowSQL(show ...bool) ShowSQL(show ...bool)
@ -93,6 +98,7 @@ type EngineInterface interface {
Sync2(...interface{}) error Sync2(...interface{}) error
StoreEngine(storeEngine string) *Session StoreEngine(storeEngine string) *Session
TableInfo(bean interface{}) *Table TableInfo(bean interface{}) *Table
TableName(interface{}, ...bool) string
UnMapType(reflect.Type) UnMapType(reflect.Type)
} }

View File

@ -32,7 +32,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
var args []interface{} var args []interface{}
var err error var err error
if err = rows.session.statement.setRefValue(rValue(bean)); err != nil { if err = rows.session.statement.setRefBean(bean); err != nil {
return nil, err return nil, err
} }
@ -94,8 +94,7 @@ func (rows *Rows) Scan(bean interface{}) error {
return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType)
} }
dataStruct := rValue(bean) if err := rows.session.statement.setRefBean(bean); err != nil {
if err := rows.session.statement.setRefValue(dataStruct); err != nil {
return err return err
} }
@ -104,6 +103,7 @@ func (rows *Rows) Scan(bean interface{}) error {
return err return err
} }
dataStruct := rValue(bean)
_, err = rows.session.slice2Bean(scanResults, rows.fields, bean, &dataStruct, rows.session.statement.RefTable) _, err = rows.session.slice2Bean(scanResults, rows.fields, bean, &dataStruct, rows.session.statement.RefTable)
if err != nil { if err != nil {
return err return err

View File

@ -278,24 +278,22 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt,
return return
} }
func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) *reflect.Value { func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) (*reflect.Value, error) {
var col *core.Column var col *core.Column
if col = table.GetColumnIdx(key, idx); col == nil { if col = table.GetColumnIdx(key, idx); col == nil {
//session.engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq()) return nil, ErrFieldIsNotExist{key, table.Name}
return nil
} }
fieldValue, err := col.ValueOfV(dataStruct) fieldValue, err := col.ValueOfV(dataStruct)
if err != nil { if err != nil {
session.engine.logger.Error(err) return nil, err
return nil
} }
if !fieldValue.IsValid() || !fieldValue.CanSet() { if !fieldValue.IsValid() || !fieldValue.CanSet() {
session.engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key) return nil, ErrFieldIsNotValid{key, table.Name}
return nil
} }
return fieldValue
return fieldValue, nil
} }
// Cell cell is a result of one column field // Cell cell is a result of one column field
@ -407,409 +405,417 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
} }
tempMap[lKey] = idx tempMap[lKey] = idx
if fieldValue := session.getField(dataStruct, key, table, idx); fieldValue != nil { fieldValue, err := session.getField(dataStruct, key, table, idx)
rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) if err != nil {
if !strings.Contains(err.Error(), "is not valid") {
// if row is null then ignore session.engine.logger.Warn(err)
if rawValue.Interface() == nil {
continue
} }
continue
}
if fieldValue == nil {
continue
}
rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii]))
if fieldValue.CanAddr() { // if row is null then ignore
if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { if rawValue.Interface() == nil {
if data, err := value2Bytes(&rawValue); err == nil { continue
if err := structConvert.FromDB(data); err != nil { }
return nil, err
} if fieldValue.CanAddr() {
} else { if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil {
if err := structConvert.FromDB(data); err != nil {
return nil, err return nil, err
} }
continue
}
}
if _, ok := fieldValue.Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil {
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
}
fieldValue.Interface().(core.Conversion).FromDB(data)
} else { } else {
return nil, err return nil, err
} }
continue continue
} }
}
rawValueType := reflect.TypeOf(rawValue.Interface()) if _, ok := fieldValue.Interface().(core.Conversion); ok {
vv := reflect.ValueOf(rawValue.Interface()) if data, err := value2Bytes(&rawValue); err == nil {
col := table.GetColumnIdx(key, idx) if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
if col.IsPrimaryKey { fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
pk = append(pk, rawValue.Interface()) }
fieldValue.Interface().(core.Conversion).FromDB(data)
} else {
return nil, err
} }
fieldType := fieldValue.Type() continue
hasAssigned := false }
if col.SQLType.IsJson() { rawValueType := reflect.TypeOf(rawValue.Interface())
var bs []byte vv := reflect.ValueOf(rawValue.Interface())
if rawValueType.Kind() == reflect.String { col := table.GetColumnIdx(key, idx)
bs = []byte(vv.String()) if col.IsPrimaryKey {
} else if rawValueType.ConvertibleTo(core.BytesType) { pk = append(pk, rawValue.Interface())
bs = vv.Bytes() }
fieldType := fieldValue.Type()
hasAssigned := false
if col.SQLType.IsJson() {
var bs []byte
if rawValueType.Kind() == reflect.String {
bs = []byte(vv.String())
} else if rawValueType.ConvertibleTo(core.BytesType) {
bs = vv.Bytes()
} else {
return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind())
}
hasAssigned = true
if len(bs) > 0 {
if fieldType.Kind() == reflect.String {
fieldValue.SetString(string(bs))
continue
}
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
return nil, err
}
} else { } else {
return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind()) x := reflect.New(fieldType)
} err := json.Unmarshal(bs, x.Interface())
if err != nil {
hasAssigned = true return nil, err
if len(bs) > 0 {
if fieldType.Kind() == reflect.String {
fieldValue.SetString(string(bs))
continue
}
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
return nil, err
}
} else {
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
} }
fieldValue.Set(x.Elem())
} }
continue
} }
switch fieldType.Kind() { continue
case reflect.Complex64, reflect.Complex128: }
// TODO: reimplement this
var bs []byte
if rawValueType.Kind() == reflect.String {
bs = []byte(vv.String())
} else if rawValueType.ConvertibleTo(core.BytesType) {
bs = vv.Bytes()
}
hasAssigned = true switch fieldType.Kind() {
if len(bs) > 0 { case reflect.Complex64, reflect.Complex128:
if fieldValue.CanAddr() { // TODO: reimplement this
err := json.Unmarshal(bs, fieldValue.Addr().Interface()) var bs []byte
if err != nil { if rawValueType.Kind() == reflect.String {
return nil, err bs = []byte(vv.String())
} } else if rawValueType.ConvertibleTo(core.BytesType) {
} else { bs = vv.Bytes()
x := reflect.New(fieldType) }
err := json.Unmarshal(bs, x.Interface())
if err != nil { hasAssigned = true
return nil, err if len(bs) > 0 {
} if fieldValue.CanAddr() {
fieldValue.Set(x.Elem()) err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
return nil, err
} }
} else {
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
} }
}
case reflect.Slice, reflect.Array:
switch rawValueType.Kind() {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
switch rawValueType.Kind() { switch rawValueType.Elem().Kind() {
case reflect.Slice, reflect.Array: case reflect.Uint8:
switch rawValueType.Elem().Kind() { if fieldType.Elem().Kind() == reflect.Uint8 {
case reflect.Uint8:
if fieldType.Elem().Kind() == reflect.Uint8 {
hasAssigned = true
if col.SQLType.IsText() {
x := reflect.New(fieldType)
err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
} else {
if fieldValue.Len() > 0 {
for i := 0; i < fieldValue.Len(); i++ {
if i < vv.Len() {
fieldValue.Index(i).Set(vv.Index(i))
}
}
} else {
for i := 0; i < vv.Len(); i++ {
fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i)))
}
}
}
}
}
}
case reflect.String:
if rawValueType.Kind() == reflect.String {
hasAssigned = true
fieldValue.SetString(vv.String())
}
case reflect.Bool:
if rawValueType.Kind() == reflect.Bool {
hasAssigned = true
fieldValue.SetBool(vv.Bool())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch rawValueType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
hasAssigned = true
fieldValue.SetInt(vv.Int())
}
case reflect.Float32, reflect.Float64:
switch rawValueType.Kind() {
case reflect.Float32, reflect.Float64:
hasAssigned = true
fieldValue.SetFloat(vv.Float())
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
switch rawValueType.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
hasAssigned = true
fieldValue.SetUint(vv.Uint())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
hasAssigned = true
fieldValue.SetUint(uint64(vv.Int()))
}
case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) {
dbTZ := session.engine.DatabaseTZ
if col.TimeZone != nil {
dbTZ = col.TimeZone
}
if rawValueType == core.TimeType {
hasAssigned = true hasAssigned = true
if col.SQLType.IsText() {
t := vv.Convert(core.TimeType).Interface().(time.Time) x := reflect.New(fieldType)
z, _ := t.Zone()
// set new location if database don't save timezone or give an incorrect timezone
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location
session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
t.Minute(), t.Second(), t.Nanosecond(), dbTZ)
}
t = t.In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else if rawValueType == core.IntType || rawValueType == core.Int64Type ||
rawValueType == core.Int32Type {
hasAssigned = true
t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else {
if d, ok := vv.Interface().([]uint8); ok {
hasAssigned = true
t, err := session.byte2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else if d, ok := vv.Interface().(string); ok {
hasAssigned = true
t, err := session.str2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else {
return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface())
}
}
} else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
// !<winxxp>! 增加支持sql.Scanner接口的结构如sql.NullString
hasAssigned = true
if err := nulVal.Scan(vv.Interface()); err != nil {
session.engine.logger.Error("sql.Sanner error:", err.Error())
hasAssigned = false
}
} else if col.SQLType.IsJson() {
if rawValueType.Kind() == reflect.String {
hasAssigned = true
x := reflect.New(fieldType)
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
} else if rawValueType.Kind() == reflect.Slice {
hasAssigned = true
x := reflect.New(fieldType)
if len(vv.Bytes()) > 0 {
err := json.Unmarshal(vv.Bytes(), x.Interface()) err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil { if err != nil {
return nil, err return nil, err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
}
}
} else if session.statement.UseCascade {
table, err := session.engine.autoMapType(*fieldValue)
if err != nil {
return nil, err
}
hasAssigned = true
if len(table.PrimaryKeys) != 1 {
return nil, errors.New("unsupported non or composited primary key cascade")
}
var pk = make(core.PK, len(table.PrimaryKeys))
pk[0], err = asKind(vv, rawValueType)
if err != nil {
return nil, err
}
if !isPKZero(pk) {
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
// property to be fetched lazily
structInter := reflect.New(fieldValue.Type())
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
if err != nil {
return nil, err
}
if has {
fieldValue.Set(structInter.Elem())
} else { } else {
return nil, errors.New("cascade obj is not exist") if fieldValue.Len() > 0 {
for i := 0; i < fieldValue.Len(); i++ {
if i < vv.Len() {
fieldValue.Index(i).Set(vv.Index(i))
}
}
} else {
for i := 0; i < vv.Len(); i++ {
fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i)))
}
}
} }
} }
} }
case reflect.Ptr: }
// !nashtsai! TODO merge duplicated codes above case reflect.String:
switch fieldType { if rawValueType.Kind() == reflect.String {
// following types case matching ptr's native type, therefore assign ptr directly hasAssigned = true
case core.PtrStringType: fieldValue.SetString(vv.String())
if rawValueType.Kind() == reflect.String { }
x := vv.String() case reflect.Bool:
hasAssigned = true if rawValueType.Kind() == reflect.Bool {
fieldValue.Set(reflect.ValueOf(&x)) hasAssigned = true
} fieldValue.SetBool(vv.Bool())
case core.PtrBoolType: }
if rawValueType.Kind() == reflect.Bool { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x := vv.Bool() switch rawValueType.Kind() {
hasAssigned = true case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fieldValue.Set(reflect.ValueOf(&x)) hasAssigned = true
} fieldValue.SetInt(vv.Int())
case core.PtrTimeType: }
if rawValueType == core.PtrTimeType { case reflect.Float32, reflect.Float64:
hasAssigned = true switch rawValueType.Kind() {
var x = rawValue.Interface().(time.Time) case reflect.Float32, reflect.Float64:
fieldValue.Set(reflect.ValueOf(&x)) hasAssigned = true
} fieldValue.SetFloat(vv.Float())
case core.PtrFloat64Type: }
if rawValueType.Kind() == reflect.Float64 { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
x := vv.Float() switch rawValueType.Kind() {
hasAssigned = true case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
fieldValue.Set(reflect.ValueOf(&x)) hasAssigned = true
} fieldValue.SetUint(vv.Uint())
case core.PtrUint64Type: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if rawValueType.Kind() == reflect.Int64 { hasAssigned = true
var x = uint64(vv.Int()) fieldValue.SetUint(uint64(vv.Int()))
hasAssigned = true }
fieldValue.Set(reflect.ValueOf(&x)) case reflect.Struct:
} if fieldType.ConvertibleTo(core.TimeType) {
case core.PtrInt64Type: dbTZ := session.engine.DatabaseTZ
if rawValueType.Kind() == reflect.Int64 { if col.TimeZone != nil {
x := vv.Int() dbTZ = col.TimeZone
hasAssigned = true }
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrFloat32Type:
if rawValueType.Kind() == reflect.Float64 {
var x = float32(vv.Float())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrIntType:
if rawValueType.Kind() == reflect.Int64 {
var x = int(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUintType:
if rawValueType.Kind() == reflect.Int64 {
var x = uint(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUint32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Complex64Type:
var x complex64
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
case core.Complex128Type:
var x complex128
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
} // switch fieldType
} // switch fieldType.Kind()
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value if rawValueType == core.TimeType {
if !hasAssigned { hasAssigned = true
data, err := value2Bytes(&rawValue)
t := vv.Convert(core.TimeType).Interface().(time.Time)
z, _ := t.Zone()
// set new location if database don't save timezone or give an incorrect timezone
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location
session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
t.Minute(), t.Second(), t.Nanosecond(), dbTZ)
}
t = t.In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else if rawValueType == core.IntType || rawValueType == core.Int64Type ||
rawValueType == core.Int32Type {
hasAssigned = true
t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else {
if d, ok := vv.Interface().([]uint8); ok {
hasAssigned = true
t, err := session.byte2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else if d, ok := vv.Interface().(string); ok {
hasAssigned = true
t, err := session.str2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else {
return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface())
}
}
} else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
// !<winxxp>! 增加支持sql.Scanner接口的结构如sql.NullString
hasAssigned = true
if err := nulVal.Scan(vv.Interface()); err != nil {
session.engine.logger.Error("sql.Sanner error:", err.Error())
hasAssigned = false
}
} else if col.SQLType.IsJson() {
if rawValueType.Kind() == reflect.String {
hasAssigned = true
x := reflect.New(fieldType)
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
} else if rawValueType.Kind() == reflect.Slice {
hasAssigned = true
x := reflect.New(fieldType)
if len(vv.Bytes()) > 0 {
err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
}
} else if session.statement.UseCascade {
table, err := session.engine.autoMapType(*fieldValue)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = session.bytes2Value(col, fieldValue, data); err != nil { hasAssigned = true
if len(table.PrimaryKeys) != 1 {
return nil, errors.New("unsupported non or composited primary key cascade")
}
var pk = make(core.PK, len(table.PrimaryKeys))
pk[0], err = asKind(vv, rawValueType)
if err != nil {
return nil, err return nil, err
} }
if !isPKZero(pk) {
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
// property to be fetched lazily
structInter := reflect.New(fieldValue.Type())
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
if err != nil {
return nil, err
}
if has {
fieldValue.Set(structInter.Elem())
} else {
return nil, errors.New("cascade obj is not exist")
}
}
}
case reflect.Ptr:
// !nashtsai! TODO merge duplicated codes above
switch fieldType {
// following types case matching ptr's native type, therefore assign ptr directly
case core.PtrStringType:
if rawValueType.Kind() == reflect.String {
x := vv.String()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrBoolType:
if rawValueType.Kind() == reflect.Bool {
x := vv.Bool()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrTimeType:
if rawValueType == core.PtrTimeType {
hasAssigned = true
var x = rawValue.Interface().(time.Time)
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrFloat64Type:
if rawValueType.Kind() == reflect.Float64 {
x := vv.Float()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUint64Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint64(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt64Type:
if rawValueType.Kind() == reflect.Int64 {
x := vv.Int()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrFloat32Type:
if rawValueType.Kind() == reflect.Float64 {
var x = float32(vv.Float())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrIntType:
if rawValueType.Kind() == reflect.Int64 {
var x = int(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUintType:
if rawValueType.Kind() == reflect.Int64 {
var x = uint(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUint32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Complex64Type:
var x complex64
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
case core.Complex128Type:
var x complex128
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
} // switch fieldType
} // switch fieldType.Kind()
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value
if !hasAssigned {
data, err := value2Bytes(&rawValue)
if err != nil {
return nil, err
}
if err = session.bytes2Value(col, fieldValue, data); err != nil {
return nil, err
} }
} }
} }
@ -828,15 +834,6 @@ func (session *Session) LastSQL() (string, []interface{}) {
return session.lastSQL, session.lastSQLArgs return session.lastSQL, session.lastSQLArgs
} }
// tbName get some table's table name
func (session *Session) tbNameNoSchema(table *core.Table) string {
if len(session.statement.AltTableName) > 0 {
return session.statement.AltTableName
}
return table.Name
}
// Unscoped always disable struct tag "deleted" // Unscoped always disable struct tag "deleted"
func (session *Session) Unscoped() *Session { func (session *Session) Unscoped() *Session {
session.statement.Unscoped() session.statement.Unscoped()

View File

@ -4,6 +4,121 @@
package xorm package xorm
import (
"reflect"
"strings"
"time"
"github.com/go-xorm/core"
)
type incrParam struct {
colName string
arg interface{}
}
type decrParam struct {
colName string
arg interface{}
}
type exprParam struct {
colName string
expr string
}
type columnMap []string
func (m columnMap) contain(colName string) bool {
if len(m) == 0 {
return false
}
n := len(colName)
for _, mk := range m {
if len(mk) != n {
continue
}
if strings.EqualFold(mk, colName) {
return true
}
}
return false
}
func (m *columnMap) add(colName string) bool {
if m.contain(colName) {
return false
}
*m = append(*m, colName)
return true
}
func setColumnInt(bean interface{}, col *core.Column, t int64) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t)
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t))
}
}
}
func setColumnTime(bean interface{}, col *core.Column, t time.Time) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Struct:
v.Set(reflect.ValueOf(t).Convert(v.Type()))
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t.Unix())
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t.Unix()))
}
}
}
func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) {
if len(m) == 0 {
return false, false
}
n := len(col.Name)
for mk := range m {
if len(mk) != n {
continue
}
if strings.EqualFold(mk, col.Name) {
return m[mk], true
}
}
return false, false
}
func col2NewCols(columns ...string) []string {
newColumns := make([]string, 0, len(columns))
for _, col := range columns {
col = strings.Replace(col, "`", "", -1)
col = strings.Replace(col, `"`, "", -1)
ccols := strings.Split(col, ",")
for _, c := range ccols {
newColumns = append(newColumns, strings.TrimSpace(c))
}
}
return newColumns
}
// Incr provides a query string like "count = count + 1" // Incr provides a query string like "count = count + 1"
func (session *Session) Incr(column string, arg ...interface{}) *Session { func (session *Session) Incr(column string, arg ...interface{}) *Session {
session.statement.Incr(column, arg...) session.statement.Incr(column, arg...)

View File

@ -27,7 +27,7 @@ func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string,
return ErrCacheFailed return ErrCacheFailed
} }
cacher := session.engine.getCacher2(table) cacher := session.engine.getCacher(tableName)
pkColumns := table.PKColumns() pkColumns := table.PKColumns()
ids, err := core.GetCacheSql(cacher, tableName, newsql, args) ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil { if err != nil {
@ -79,7 +79,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
defer session.Close() defer session.Close()
} }
if err := session.statement.setRefValue(rValue(bean)); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return 0, err return 0, err
} }
@ -199,7 +199,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
}) })
} }
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { if cacher := session.engine.getCacher(tableName); cacher != nil && session.statement.UseCache {
session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...) session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...)
} }

View File

@ -57,7 +57,7 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) {
} }
if beanValue.Elem().Kind() == reflect.Struct { if beanValue.Elem().Kind() == reflect.Struct {
if err := session.statement.setRefValue(beanValue.Elem()); err != nil { if err := session.statement.setRefBean(bean[0]); err != nil {
return false, err return false, err
} }
} }

View File

@ -29,6 +29,39 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{})
return session.find(rowsSlicePtr, condiBean...) return session.find(rowsSlicePtr, condiBean...)
} }
// FindAndCount find the results and also return the counts
func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...interface{}) (int64, error) {
if session.isAutoClose {
defer session.Close()
}
session.autoResetStatement = false
err := session.find(rowsSlicePtr, condiBean...)
if err != nil {
return 0, err
}
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map {
return 0, errors.New("needs a pointer to a slice or a map")
}
sliceElementType := sliceValue.Type().Elem()
if sliceElementType.Kind() == reflect.Ptr {
sliceElementType = sliceElementType.Elem()
}
session.autoResetStatement = true
if session.statement.selectStr != "" {
session.statement.selectStr = ""
}
if session.statement.OrderStr != "" {
session.statement.OrderStr = ""
}
return session.Count(reflect.New(sliceElementType).Interface())
}
func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error { func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map { if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map {
@ -42,7 +75,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
if sliceElementType.Kind() == reflect.Ptr { if sliceElementType.Kind() == reflect.Ptr {
if sliceElementType.Elem().Kind() == reflect.Struct { if sliceElementType.Elem().Kind() == reflect.Struct {
pv := reflect.New(sliceElementType.Elem()) pv := reflect.New(sliceElementType.Elem())
if err := session.statement.setRefValue(pv.Elem()); err != nil { if err := session.statement.setRefValue(pv); err != nil {
return err return err
} }
} else { } else {
@ -50,7 +83,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
} }
} else if sliceElementType.Kind() == reflect.Struct { } else if sliceElementType.Kind() == reflect.Struct {
pv := reflect.New(sliceElementType) pv := reflect.New(sliceElementType)
if err := session.statement.setRefValue(pv.Elem()); err != nil { if err := session.statement.setRefValue(pv); err != nil {
return err return err
} }
} else { } else {
@ -128,7 +161,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
} }
args = append(session.statement.joinArgs, condArgs...) args = append(session.statement.joinArgs, condArgs...)
sqlStr, err = session.statement.genSelectSQL(columnStr, condSQL) sqlStr, err = session.statement.genSelectSQL(columnStr, condSQL, true, true)
if err != nil { if err != nil {
return err return err
} }
@ -143,7 +176,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
} }
if session.canCache() { if session.canCache() {
if cacher := session.engine.getCacher2(table); cacher != nil && if cacher := session.engine.getCacher(table.Name); cacher != nil &&
!session.statement.IsDistinct && !session.statement.IsDistinct &&
!session.statement.unscoped { !session.statement.unscoped {
err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...)
@ -288,6 +321,12 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
return ErrCacheFailed return ErrCacheFailed
} }
tableName := session.statement.TableName()
cacher := session.engine.getCacher(tableName)
if cacher == nil {
return nil
}
for _, filter := range session.engine.dialect.Filters() { for _, filter := range session.engine.dialect.Filters() {
sqlStr = filter.Do(sqlStr, session.engine.dialect, session.statement.RefTable) sqlStr = filter.Do(sqlStr, session.engine.dialect, session.statement.RefTable)
} }
@ -297,9 +336,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
return ErrCacheFailed return ErrCacheFailed
} }
tableName := session.statement.TableName()
table := session.statement.RefTable table := session.statement.RefTable
cacher := session.engine.getCacher2(table)
ids, err := core.GetCacheSql(cacher, tableName, newsql, args) ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil { if err != nil {
rows, err := session.queryRows(newsql, args...) rows, err := session.queryRows(newsql, args...)

View File

@ -31,7 +31,7 @@ func (session *Session) get(bean interface{}) (bool, error) {
} }
if beanValue.Elem().Kind() == reflect.Struct { if beanValue.Elem().Kind() == reflect.Struct {
if err := session.statement.setRefValue(beanValue.Elem()); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return false, err return false, err
} }
} }
@ -57,7 +57,7 @@ func (session *Session) get(bean interface{}) (bool, error) {
table := session.statement.RefTable table := session.statement.RefTable
if session.canCache() && beanValue.Elem().Kind() == reflect.Struct { if session.canCache() && beanValue.Elem().Kind() == reflect.Struct {
if cacher := session.engine.getCacher2(table); cacher != nil && if cacher := session.engine.getCacher(table.Name); cacher != nil &&
!session.statement.unscoped { !session.statement.unscoped {
has, err := session.cacheGet(bean, sqlStr, args...) has, err := session.cacheGet(bean, sqlStr, args...)
if err != ErrCacheFailed { if err != ErrCacheFailed {
@ -134,8 +134,9 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf
return false, ErrCacheFailed return false, ErrCacheFailed
} }
cacher := session.engine.getCacher2(session.statement.RefTable)
tableName := session.statement.TableName() tableName := session.statement.TableName()
cacher := session.engine.getCacher(tableName)
session.engine.logger.Debug("[cacheGet] find sql:", newsql, args) session.engine.logger.Debug("[cacheGet] find sql:", newsql, args)
table := session.statement.RefTable table := session.statement.RefTable
ids, err := core.GetCacheSql(cacher, tableName, newsql, args) ids, err := core.GetCacheSql(cacher, tableName, newsql, args)

View File

@ -66,11 +66,12 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
return 0, errors.New("could not insert a empty slice") return 0, errors.New("could not insert a empty slice")
} }
if err := session.statement.setRefValue(reflect.ValueOf(sliceValue.Index(0).Interface())); err != nil { if err := session.statement.setRefBean(sliceValue.Index(0).Interface()); err != nil {
return 0, err return 0, err
} }
if len(session.statement.TableName()) <= 0 { tableName := session.statement.TableName()
if len(tableName) <= 0 {
return 0, ErrTableNotFound return 0, ErrTableNotFound
} }
@ -115,15 +116,11 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
if col.IsDeleted { if col.IsDeleted {
continue continue
} }
if session.statement.ColumnStr != "" { if session.statement.omitColumnMap.contain(col.Name) {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok { continue
continue
}
} }
if session.statement.OmitStr != "" { if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) {
if _, ok := getFlagForColumn(session.statement.columnMap, col); ok { continue
continue
}
} }
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
val, t := session.engine.nowTime(col) val, t := session.engine.nowTime(col)
@ -170,15 +167,11 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
if col.IsDeleted { if col.IsDeleted {
continue continue
} }
if session.statement.ColumnStr != "" { if session.statement.omitColumnMap.contain(col.Name) {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok { continue
continue
}
} }
if session.statement.OmitStr != "" { if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) {
if _, ok := getFlagForColumn(session.statement.columnMap, col); ok { continue
continue
}
} }
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
val, t := session.engine.nowTime(col) val, t := session.engine.nowTime(col)
@ -211,38 +204,33 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
} }
cleanupProcessorsClosures(&session.beforeClosures) cleanupProcessorsClosures(&session.beforeClosures)
var sql = "INSERT INTO %s (%v%v%v) VALUES (%v)" var sql string
var statement string
var tableName = session.statement.TableName()
if session.engine.dialect.DBType() == core.ORACLE { if session.engine.dialect.DBType() == core.ORACLE {
sql = "INSERT ALL INTO %s (%v%v%v) VALUES (%v) SELECT 1 FROM DUAL"
temp := fmt.Sprintf(") INTO %s (%v%v%v) VALUES (", temp := fmt.Sprintf(") INTO %s (%v%v%v) VALUES (",
session.engine.Quote(tableName), session.engine.Quote(tableName),
session.engine.QuoteStr(), session.engine.QuoteStr(),
strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()), strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
session.engine.QuoteStr()) session.engine.QuoteStr())
statement = fmt.Sprintf(sql, sql = fmt.Sprintf("INSERT ALL INTO %s (%v%v%v) VALUES (%v) SELECT 1 FROM DUAL",
session.engine.Quote(tableName), session.engine.Quote(tableName),
session.engine.QuoteStr(), session.engine.QuoteStr(),
strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()), strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
session.engine.QuoteStr(), session.engine.QuoteStr(),
strings.Join(colMultiPlaces, temp)) strings.Join(colMultiPlaces, temp))
} else { } else {
statement = fmt.Sprintf(sql, sql = fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)",
session.engine.Quote(tableName), session.engine.Quote(tableName),
session.engine.QuoteStr(), session.engine.QuoteStr(),
strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()), strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
session.engine.QuoteStr(), session.engine.QuoteStr(),
strings.Join(colMultiPlaces, "),(")) strings.Join(colMultiPlaces, "),("))
} }
res, err := session.exec(statement, args...) res, err := session.exec(sql, args...)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { session.cacheInsert(tableName)
session.cacheInsert(table, tableName)
}
lenAfterClosures := len(session.afterClosures) lenAfterClosures := len(session.afterClosures)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
@ -298,7 +286,7 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) {
} }
func (session *Session) innerInsert(bean interface{}) (int64, error) { func (session *Session) innerInsert(bean interface{}) (int64, error) {
if err := session.statement.setRefValue(rValue(bean)); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return 0, err return 0, err
} }
if len(session.statement.TableName()) <= 0 { if len(session.statement.TableName()) <= 0 {
@ -316,8 +304,8 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
if processor, ok := interface{}(bean).(BeforeInsertProcessor); ok { if processor, ok := interface{}(bean).(BeforeInsertProcessor); ok {
processor.BeforeInsert() processor.BeforeInsert()
} }
// --
colNames, args, err := genCols(session.statement.RefTable, session, bean, false, false) colNames, args, err := session.genInsertColumns(bean)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -402,9 +390,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
defer handleAfterInsertProcessorFunc(bean) defer handleAfterInsertProcessorFunc(bean)
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { session.cacheInsert(tableName)
session.cacheInsert(table, tableName)
}
if table.Version != "" && session.statement.checkVersion { if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean) verValue, err := table.VersionColumn().ValueOf(bean)
@ -447,9 +433,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
} }
defer handleAfterInsertProcessorFunc(bean) defer handleAfterInsertProcessorFunc(bean)
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { session.cacheInsert(tableName)
session.cacheInsert(table, tableName)
}
if table.Version != "" && session.statement.checkVersion { if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean) verValue, err := table.VersionColumn().ValueOf(bean)
@ -490,9 +474,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
defer handleAfterInsertProcessorFunc(bean) defer handleAfterInsertProcessorFunc(bean)
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { session.cacheInsert(tableName)
session.cacheInsert(table, tableName)
}
if table.Version != "" && session.statement.checkVersion { if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean) verValue, err := table.VersionColumn().ValueOf(bean)
@ -539,16 +521,104 @@ func (session *Session) InsertOne(bean interface{}) (int64, error) {
return session.innerInsert(bean) return session.innerInsert(bean)
} }
func (session *Session) cacheInsert(table *core.Table, tables ...string) error { func (session *Session) cacheInsert(table string) error {
if table == nil { if !session.statement.UseCache {
return ErrCacheFailed return nil
} }
cacher := session.engine.getCacher(table)
cacher := session.engine.getCacher2(table) if cacher == nil {
for _, t := range tables { return nil
session.engine.logger.Debug("[cache] clear sql:", t)
cacher.ClearIds(t)
} }
session.engine.logger.Debug("[cache] clear sql:", table)
cacher.ClearIds(table)
return nil return nil
} }
// genInsertColumns generates insert needed columns
func (session *Session) genInsertColumns(bean interface{}) ([]string, []interface{}, error) {
table := session.statement.RefTable
colNames := make([]string, 0, len(table.ColumnsSeq()))
args := make([]interface{}, 0, len(table.ColumnsSeq()))
for _, col := range table.Columns() {
if col.MapType == core.ONLYFROMDB {
continue
}
if col.IsDeleted {
continue
}
if session.statement.omitColumnMap.contain(col.Name) {
continue
}
if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) {
continue
}
if _, ok := session.statement.incrColumns[col.Name]; ok {
continue
} else if _, ok := session.statement.decrColumns[col.Name]; ok {
continue
}
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return nil, nil, err
}
fieldValue := *fieldValuePtr
if col.IsAutoIncrement {
switch fieldValue.Type().Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
if fieldValue.Int() == 0 {
continue
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
if fieldValue.Uint() == 0 {
continue
}
case reflect.String:
if len(fieldValue.String()) == 0 {
continue
}
case reflect.Ptr:
if fieldValue.Pointer() == 0 {
continue
}
}
}
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int
fieldValue = reflect.ValueOf(nilValue)
}
}
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
} else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
} else {
arg, err := session.value2Interface(col, fieldValue)
if err != nil {
return colNames, args, err
}
args = append(args, arg)
}
colNames = append(colNames, col.Name)
}
return colNames, args, nil
}

View File

@ -64,13 +64,17 @@ func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interfa
} }
} }
if err := session.statement.processIDParam(); err != nil {
return "", nil, err
}
condSQL, condArgs, err := builder.ToSQL(session.statement.cond) condSQL, condArgs, err := builder.ToSQL(session.statement.cond)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
args := append(session.statement.joinArgs, condArgs...) args := append(session.statement.joinArgs, condArgs...)
sqlStr, err := session.statement.genSelectSQL(columnStr, condSQL) sqlStr, err := session.statement.genSelectSQL(columnStr, condSQL, true, true)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }

View File

@ -6,9 +6,7 @@ package xorm
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"reflect"
"strings" "strings"
"github.com/go-xorm/core" "github.com/go-xorm/core"
@ -34,8 +32,7 @@ func (session *Session) CreateTable(bean interface{}) error {
} }
func (session *Session) createTable(bean interface{}) error { func (session *Session) createTable(bean interface{}) error {
v := rValue(bean) if err := session.statement.setRefBean(bean); err != nil {
if err := session.statement.setRefValue(v); err != nil {
return err return err
} }
@ -54,8 +51,7 @@ func (session *Session) CreateIndexes(bean interface{}) error {
} }
func (session *Session) createIndexes(bean interface{}) error { func (session *Session) createIndexes(bean interface{}) error {
v := rValue(bean) if err := session.statement.setRefBean(bean); err != nil {
if err := session.statement.setRefValue(v); err != nil {
return err return err
} }
@ -78,8 +74,7 @@ func (session *Session) CreateUniques(bean interface{}) error {
} }
func (session *Session) createUniques(bean interface{}) error { func (session *Session) createUniques(bean interface{}) error {
v := rValue(bean) if err := session.statement.setRefBean(bean); err != nil {
if err := session.statement.setRefValue(v); err != nil {
return err return err
} }
@ -103,8 +98,7 @@ func (session *Session) DropIndexes(bean interface{}) error {
} }
func (session *Session) dropIndexes(bean interface{}) error { func (session *Session) dropIndexes(bean interface{}) error {
v := rValue(bean) if err := session.statement.setRefBean(bean); err != nil {
if err := session.statement.setRefValue(v); err != nil {
return err return err
} }
@ -128,11 +122,7 @@ func (session *Session) DropTable(beanOrTableName interface{}) error {
} }
func (session *Session) dropTable(beanOrTableName interface{}) error { func (session *Session) dropTable(beanOrTableName interface{}) error {
tableName, err := session.engine.tableName(beanOrTableName) tableName := session.engine.TableName(beanOrTableName)
if err != nil {
return err
}
var needDrop = true var needDrop = true
if !session.engine.dialect.SupportDropIfExists() { if !session.engine.dialect.SupportDropIfExists() {
sqlStr, args := session.engine.dialect.TableCheckSql(tableName) sqlStr, args := session.engine.dialect.TableCheckSql(tableName)
@ -144,8 +134,8 @@ func (session *Session) dropTable(beanOrTableName interface{}) error {
} }
if needDrop { if needDrop {
sqlStr := session.engine.Dialect().DropTableSql(tableName) sqlStr := session.engine.Dialect().DropTableSql(session.engine.TableName(tableName, true))
_, err = session.exec(sqlStr) _, err := session.exec(sqlStr)
return err return err
} }
return nil return nil
@ -157,10 +147,7 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error)
defer session.Close() defer session.Close()
} }
tableName, err := session.engine.tableName(beanOrTableName) tableName := session.engine.TableName(beanOrTableName)
if err != nil {
return false, err
}
return session.isTableExist(tableName) return session.isTableExist(tableName)
} }
@ -173,24 +160,15 @@ func (session *Session) isTableExist(tableName string) (bool, error) {
// IsTableEmpty if table have any records // IsTableEmpty if table have any records
func (session *Session) IsTableEmpty(bean interface{}) (bool, error) { func (session *Session) IsTableEmpty(bean interface{}) (bool, error) {
v := rValue(bean) if session.isAutoClose {
t := v.Type() defer session.Close()
if t.Kind() == reflect.String {
if session.isAutoClose {
defer session.Close()
}
return session.isTableEmpty(bean.(string))
} else if t.Kind() == reflect.Struct {
rows, err := session.Count(bean)
return rows == 0, err
} }
return false, errors.New("bean should be a struct or struct's point") return session.isTableEmpty(session.engine.TableName(bean))
} }
func (session *Session) isTableEmpty(tableName string) (bool, error) { func (session *Session) isTableEmpty(tableName string) (bool, error) {
var total int64 var total int64
sqlStr := fmt.Sprintf("select count(*) from %s", session.engine.Quote(tableName)) sqlStr := fmt.Sprintf("select count(*) from %s", session.engine.Quote(session.engine.TableName(tableName, true)))
err := session.queryRow(sqlStr).Scan(&total) err := session.queryRow(sqlStr).Scan(&total)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -255,6 +233,12 @@ func (session *Session) Sync2(beans ...interface{}) error {
return err return err
} }
session.autoResetStatement = false
defer func() {
session.autoResetStatement = true
session.resetStatement()
}()
var structTables []*core.Table var structTables []*core.Table
for _, bean := range beans { for _, bean := range beans {
@ -264,7 +248,8 @@ func (session *Session) Sync2(beans ...interface{}) error {
return err return err
} }
structTables = append(structTables, table) structTables = append(structTables, table)
var tbName = session.tbNameNoSchema(table) tbName := engine.TableName(bean)
tbNameWithSchema := engine.TableName(tbName, true)
var oriTable *core.Table var oriTable *core.Table
for _, tb := range tables { for _, tb := range tables {
@ -309,32 +294,32 @@ func (session *Session) Sync2(beans ...interface{}) error {
if engine.dialect.DBType() == core.MYSQL || if engine.dialect.DBType() == core.MYSQL ||
engine.dialect.DBType() == core.POSTGRES { engine.dialect.DBType() == core.POSTGRES {
engine.logger.Infof("Table %s column %s change type from %s to %s\n", engine.logger.Infof("Table %s column %s change type from %s to %s\n",
tbName, col.Name, curType, expectedType) tbNameWithSchema, col.Name, curType, expectedType)
_, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col)) _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col))
} else { } else {
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n", engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n",
tbName, col.Name, curType, expectedType) tbNameWithSchema, col.Name, curType, expectedType)
} }
} else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) { } else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) {
if engine.dialect.DBType() == core.MYSQL { if engine.dialect.DBType() == core.MYSQL {
if oriCol.Length < col.Length { if oriCol.Length < col.Length {
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n",
tbName, col.Name, oriCol.Length, col.Length) tbNameWithSchema, col.Name, oriCol.Length, col.Length)
_, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col)) _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col))
} }
} }
} else { } else {
if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') { if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') {
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s", engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s",
tbName, col.Name, curType, expectedType) tbNameWithSchema, col.Name, curType, expectedType)
} }
} }
} else if expectedType == core.Varchar { } else if expectedType == core.Varchar {
if engine.dialect.DBType() == core.MYSQL { if engine.dialect.DBType() == core.MYSQL {
if oriCol.Length < col.Length { if oriCol.Length < col.Length {
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n",
tbName, col.Name, oriCol.Length, col.Length) tbNameWithSchema, col.Name, oriCol.Length, col.Length)
_, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col)) _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col))
} }
} }
} }
@ -348,7 +333,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
} }
} else { } else {
session.statement.RefTable = table session.statement.RefTable = table
session.statement.tableName = tbName session.statement.tableName = tbNameWithSchema
err = session.addColumn(col.Name) err = session.addColumn(col.Name)
} }
if err != nil { if err != nil {
@ -371,7 +356,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
if oriIndex != nil { if oriIndex != nil {
if oriIndex.Type != index.Type { if oriIndex.Type != index.Type {
sql := engine.dialect.DropIndexSql(tbName, oriIndex) sql := engine.dialect.DropIndexSql(tbNameWithSchema, oriIndex)
_, err = session.exec(sql) _, err = session.exec(sql)
if err != nil { if err != nil {
return err return err
@ -387,7 +372,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
for name2, index2 := range oriTable.Indexes { for name2, index2 := range oriTable.Indexes {
if _, ok := foundIndexNames[name2]; !ok { if _, ok := foundIndexNames[name2]; !ok {
sql := engine.dialect.DropIndexSql(tbName, index2) sql := engine.dialect.DropIndexSql(tbNameWithSchema, index2)
_, err = session.exec(sql) _, err = session.exec(sql)
if err != nil { if err != nil {
return err return err
@ -398,12 +383,12 @@ func (session *Session) Sync2(beans ...interface{}) error {
for name, index := range addedNames { for name, index := range addedNames {
if index.Type == core.UniqueType { if index.Type == core.UniqueType {
session.statement.RefTable = table session.statement.RefTable = table
session.statement.tableName = tbName session.statement.tableName = tbNameWithSchema
err = session.addUnique(tbName, name) err = session.addUnique(tbNameWithSchema, name)
} else if index.Type == core.IndexType { } else if index.Type == core.IndexType {
session.statement.RefTable = table session.statement.RefTable = table
session.statement.tableName = tbName session.statement.tableName = tbNameWithSchema
err = session.addIndex(tbName, name) err = session.addIndex(tbNameWithSchema, name)
} }
if err != nil { if err != nil {
return err return err
@ -428,7 +413,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
for _, colName := range table.ColumnsSeq() { for _, colName := range table.ColumnsSeq() {
if oriTable.GetColumn(colName) == nil { if oriTable.GetColumn(colName) == nil {
engine.logger.Warnf("Table %s has column %s but struct has not related field", table.Name, colName) engine.logger.Warnf("Table %s has column %s but struct has not related field", engine.TableName(table.Name, true), colName)
} }
} }
} }

View File

@ -24,6 +24,7 @@ func (session *Session) Rollback() error {
if !session.isAutoCommit && !session.isCommitedOrRollbacked { if !session.isAutoCommit && !session.isCommitedOrRollbacked {
session.saveLastSQL(session.engine.dialect.RollBackStr()) session.saveLastSQL(session.engine.dialect.RollBackStr())
session.isCommitedOrRollbacked = true session.isCommitedOrRollbacked = true
session.isAutoCommit = true
return session.tx.Rollback() return session.tx.Rollback()
} }
return nil return nil
@ -34,6 +35,7 @@ func (session *Session) Commit() error {
if !session.isAutoCommit && !session.isCommitedOrRollbacked { if !session.isAutoCommit && !session.isCommitedOrRollbacked {
session.saveLastSQL("COMMIT") session.saveLastSQL("COMMIT")
session.isCommitedOrRollbacked = true session.isCommitedOrRollbacked = true
session.isAutoCommit = true
var err error var err error
if err = session.tx.Commit(); err == nil { if err = session.tx.Commit(); err == nil {
// handle processors after tx committed // handle processors after tx committed

View File

@ -40,7 +40,7 @@ func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string,
} }
} }
cacher := session.engine.getCacher2(table) cacher := session.engine.getCacher(tableName)
session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:])
ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:])
if err != nil { if err != nil {
@ -167,7 +167,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
var isMap = t.Kind() == reflect.Map var isMap = t.Kind() == reflect.Map
var isStruct = t.Kind() == reflect.Struct var isStruct = t.Kind() == reflect.Struct
if isStruct { if isStruct {
if err := session.statement.setRefValue(v); err != nil { if err := session.statement.setRefBean(bean); err != nil {
return 0, err return 0, err
} }
@ -176,12 +176,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
} }
if session.statement.ColumnStr == "" { if session.statement.ColumnStr == "" {
colNames, args = buildUpdates(session.engine, session.statement.RefTable, bean, false, false, colNames, args = session.statement.buildUpdates(bean, false, false,
false, false, session.statement.allUseBool, session.statement.useAllCols, false, false, true)
session.statement.mustColumnMap, session.statement.nullableMap,
session.statement.columnMap, true, session.statement.unscoped)
} else { } else {
colNames, args, err = genCols(session.statement.RefTable, session, bean, true, true) colNames, args, err = session.genUpdateColumns(bean)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -202,7 +200,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
table := session.statement.RefTable table := session.statement.RefTable
if session.statement.UseAutoTime && table != nil && table.Updated != "" { if session.statement.UseAutoTime && table != nil && table.Updated != "" {
if _, ok := session.statement.columnMap[strings.ToLower(table.Updated)]; !ok { if !session.statement.columnMap.contain(table.Updated) &&
!session.statement.omitColumnMap.contain(table.Updated) {
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?") colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
col := table.UpdatedColumn() col := table.UpdatedColumn()
val, t := session.engine.nowTime(col) val, t := session.engine.nowTime(col)
@ -362,12 +361,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
} }
} }
if table != nil { if cacher := session.engine.getCacher(tableName); cacher != nil && session.statement.UseCache {
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { //session.cacheUpdate(table, tableName, sqlStr, args...)
//session.cacheUpdate(table, tableName, sqlStr, args...) session.engine.logger.Debug("[cacheUpdate] clear table ", tableName)
cacher.ClearIds(tableName) cacher.ClearIds(tableName)
cacher.ClearBeans(tableName) cacher.ClearBeans(tableName)
}
} }
// handle after update processors // handle after update processors
@ -402,3 +400,92 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
return res.RowsAffected() return res.RowsAffected()
} }
func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interface{}, error) {
table := session.statement.RefTable
colNames := make([]string, 0, len(table.ColumnsSeq()))
args := make([]interface{}, 0, len(table.ColumnsSeq()))
for _, col := range table.Columns() {
if !col.IsVersion && !col.IsCreated && !col.IsUpdated {
if session.statement.omitColumnMap.contain(col.Name) {
continue
}
}
if col.MapType == core.ONLYFROMDB {
continue
}
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return nil, nil, err
}
fieldValue := *fieldValuePtr
if col.IsAutoIncrement {
switch fieldValue.Type().Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
if fieldValue.Int() == 0 {
continue
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
if fieldValue.Uint() == 0 {
continue
}
case reflect.String:
if len(fieldValue.String()) == 0 {
continue
}
case reflect.Ptr:
if fieldValue.Pointer() == 0 {
continue
}
}
}
if col.IsDeleted || col.IsCreated {
continue
}
if len(session.statement.columnMap) > 0 {
if !session.statement.columnMap.contain(col.Name) {
continue
} else if _, ok := session.statement.incrColumns[col.Name]; ok {
continue
} else if _, ok := session.statement.decrColumns[col.Name]; ok {
continue
}
}
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int
fieldValue = reflect.ValueOf(nilValue)
}
}
if col.IsUpdated && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
} else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
} else {
arg, err := session.value2Interface(col, fieldValue)
if err != nil {
return colNames, args, err
}
args = append(args, arg)
}
colNames = append(colNames, session.engine.Quote(col.Name)+" = ?")
}
return colNames, args, nil
}

View File

@ -5,7 +5,6 @@
package xorm package xorm
import ( import (
"bytes"
"database/sql/driver" "database/sql/driver"
"encoding/json" "encoding/json"
"errors" "errors"
@ -18,21 +17,6 @@ import (
"github.com/go-xorm/core" "github.com/go-xorm/core"
) )
type incrParam struct {
colName string
arg interface{}
}
type decrParam struct {
colName string
arg interface{}
}
type exprParam struct {
colName string
expr string
}
// Statement save all the sql info for executing SQL // Statement save all the sql info for executing SQL
type Statement struct { type Statement struct {
RefTable *core.Table RefTable *core.Table
@ -47,7 +31,6 @@ type Statement struct {
HavingStr string HavingStr string
ColumnStr string ColumnStr string
selectStr string selectStr string
columnMap map[string]bool
useAllCols bool useAllCols bool
OmitStr string OmitStr string
AltTableName string AltTableName string
@ -67,6 +50,8 @@ type Statement struct {
allUseBool bool allUseBool bool
checkVersion bool checkVersion bool
unscoped bool unscoped bool
columnMap columnMap
omitColumnMap columnMap
mustColumnMap map[string]bool mustColumnMap map[string]bool
nullableMap map[string]bool nullableMap map[string]bool
incrColumns map[string]incrParam incrColumns map[string]incrParam
@ -89,7 +74,8 @@ func (statement *Statement) Init() {
statement.HavingStr = "" statement.HavingStr = ""
statement.ColumnStr = "" statement.ColumnStr = ""
statement.OmitStr = "" statement.OmitStr = ""
statement.columnMap = make(map[string]bool) statement.columnMap = columnMap{}
statement.omitColumnMap = columnMap{}
statement.AltTableName = "" statement.AltTableName = ""
statement.tableName = "" statement.tableName = ""
statement.idParam = nil statement.idParam = nil
@ -221,34 +207,33 @@ func (statement *Statement) setRefValue(v reflect.Value) error {
if err != nil { if err != nil {
return err return err
} }
statement.tableName = statement.Engine.tbName(v) statement.tableName = statement.Engine.TableName(v, true)
return nil return nil
} }
// Table tempororily set table name, the parameter could be a string or a pointer of struct func (statement *Statement) setRefBean(bean interface{}) error {
func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { var err error
v := rValue(tableNameOrBean) statement.RefTable, err = statement.Engine.autoMapType(rValue(bean))
t := v.Type() if err != nil {
if t.Kind() == reflect.String { return err
statement.AltTableName = tableNameOrBean.(string)
} else if t.Kind() == reflect.Struct {
var err error
statement.RefTable, err = statement.Engine.autoMapType(v)
if err != nil {
statement.Engine.logger.Error(err)
return statement
}
statement.AltTableName = statement.Engine.tbName(v)
} }
return statement statement.tableName = statement.Engine.TableName(bean, true)
return nil
} }
// Auto generating update columnes and values according a struct // Auto generating update columnes and values according a struct
func buildUpdates(engine *Engine, table *core.Table, bean interface{}, func (statement *Statement) buildUpdates(bean interface{},
includeVersion bool, includeUpdated bool, includeNil bool, includeVersion, includeUpdated, includeNil,
includeAutoIncr bool, allUseBool bool, useAllCols bool, includeAutoIncr, update bool) ([]string, []interface{}) {
mustColumnMap map[string]bool, nullableMap map[string]bool, engine := statement.Engine
columnMap map[string]bool, update, unscoped bool) ([]string, []interface{}) { table := statement.RefTable
allUseBool := statement.allUseBool
useAllCols := statement.useAllCols
mustColumnMap := statement.mustColumnMap
nullableMap := statement.nullableMap
columnMap := statement.columnMap
omitColumnMap := statement.omitColumnMap
unscoped := statement.unscoped
var colNames = make([]string, 0) var colNames = make([]string, 0)
var args = make([]interface{}, 0) var args = make([]interface{}, 0)
@ -268,7 +253,14 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{},
if col.IsDeleted && !unscoped { if col.IsDeleted && !unscoped {
continue continue
} }
if use, ok := columnMap[strings.ToLower(col.Name)]; ok && !use { if omitColumnMap.contain(col.Name) {
continue
}
if len(columnMap) > 0 && !columnMap.contain(col.Name) {
continue
}
if col.MapType == core.ONLYFROMDB {
continue continue
} }
@ -604,17 +596,10 @@ func (statement *Statement) col2NewColsWithQuote(columns ...string) []string {
} }
func (statement *Statement) colmap2NewColsWithQuote() []string { func (statement *Statement) colmap2NewColsWithQuote() []string {
newColumns := make([]string, 0, len(statement.columnMap)) newColumns := make([]string, len(statement.columnMap), len(statement.columnMap))
for col := range statement.columnMap { copy(newColumns, statement.columnMap)
fields := strings.Split(strings.TrimSpace(col), ".") for i := 0; i < len(statement.columnMap); i++ {
if len(fields) == 1 { newColumns[i] = statement.Engine.Quote(newColumns[i])
newColumns = append(newColumns, statement.Engine.quote(fields[0]))
} else if len(fields) == 2 {
newColumns = append(newColumns, statement.Engine.quote(fields[0])+"."+
statement.Engine.quote(fields[1]))
} else {
panic(errors.New("unwanted colnames"))
}
} }
return newColumns return newColumns
} }
@ -642,10 +627,11 @@ func (statement *Statement) Select(str string) *Statement {
func (statement *Statement) Cols(columns ...string) *Statement { func (statement *Statement) Cols(columns ...string) *Statement {
cols := col2NewCols(columns...) cols := col2NewCols(columns...)
for _, nc := range cols { for _, nc := range cols {
statement.columnMap[strings.ToLower(nc)] = true statement.columnMap.add(nc)
} }
newColumns := statement.colmap2NewColsWithQuote() newColumns := statement.colmap2NewColsWithQuote()
statement.ColumnStr = strings.Join(newColumns, ", ") statement.ColumnStr = strings.Join(newColumns, ", ")
statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.quote("*"), "*", -1) statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.quote("*"), "*", -1)
return statement return statement
@ -680,7 +666,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement {
func (statement *Statement) Omit(columns ...string) { func (statement *Statement) Omit(columns ...string) {
newColumns := col2NewCols(columns...) newColumns := col2NewCols(columns...)
for _, nc := range newColumns { for _, nc := range newColumns {
statement.columnMap[strings.ToLower(nc)] = false statement.omitColumnMap = append(statement.omitColumnMap, nc)
} }
statement.OmitStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", "))) statement.OmitStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", ")))
} }
@ -719,10 +705,9 @@ func (statement *Statement) OrderBy(order string) *Statement {
// Desc generate `ORDER BY xx DESC` // Desc generate `ORDER BY xx DESC`
func (statement *Statement) Desc(colNames ...string) *Statement { func (statement *Statement) Desc(colNames ...string) *Statement {
var buf bytes.Buffer var buf builder.StringBuilder
fmt.Fprintf(&buf, statement.OrderStr)
if len(statement.OrderStr) > 0 { if len(statement.OrderStr) > 0 {
fmt.Fprint(&buf, ", ") fmt.Fprint(&buf, statement.OrderStr, ", ")
} }
newColNames := statement.col2NewColsWithQuote(colNames...) newColNames := statement.col2NewColsWithQuote(colNames...)
fmt.Fprintf(&buf, "%v DESC", strings.Join(newColNames, " DESC, ")) fmt.Fprintf(&buf, "%v DESC", strings.Join(newColNames, " DESC, "))
@ -732,10 +717,9 @@ func (statement *Statement) Desc(colNames ...string) *Statement {
// Asc provide asc order by query condition, the input parameters are columns. // Asc provide asc order by query condition, the input parameters are columns.
func (statement *Statement) Asc(colNames ...string) *Statement { func (statement *Statement) Asc(colNames ...string) *Statement {
var buf bytes.Buffer var buf builder.StringBuilder
fmt.Fprintf(&buf, statement.OrderStr)
if len(statement.OrderStr) > 0 { if len(statement.OrderStr) > 0 {
fmt.Fprint(&buf, ", ") fmt.Fprint(&buf, statement.OrderStr, ", ")
} }
newColNames := statement.col2NewColsWithQuote(colNames...) newColNames := statement.col2NewColsWithQuote(colNames...)
fmt.Fprintf(&buf, "%v ASC", strings.Join(newColNames, " ASC, ")) fmt.Fprintf(&buf, "%v ASC", strings.Join(newColNames, " ASC, "))
@ -743,48 +727,35 @@ func (statement *Statement) Asc(colNames ...string) *Statement {
return statement return statement
} }
// Table tempororily set table name, the parameter could be a string or a pointer of struct
func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
v := rValue(tableNameOrBean)
t := v.Type()
if t.Kind() == reflect.Struct {
var err error
statement.RefTable, err = statement.Engine.autoMapType(v)
if err != nil {
statement.Engine.logger.Error(err)
return statement
}
}
statement.AltTableName = statement.Engine.TableName(tableNameOrBean, true)
return statement
}
// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN // Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement { func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement {
var buf bytes.Buffer var buf builder.StringBuilder
if len(statement.JoinStr) > 0 { if len(statement.JoinStr) > 0 {
fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP)
} else { } else {
fmt.Fprintf(&buf, "%v JOIN ", joinOP) fmt.Fprintf(&buf, "%v JOIN ", joinOP)
} }
switch tablename.(type) { tbName := statement.Engine.TableName(tablename, true)
case []string:
t := tablename.([]string)
if len(t) > 1 {
fmt.Fprintf(&buf, "%v AS %v", statement.Engine.Quote(t[0]), statement.Engine.Quote(t[1]))
} else if len(t) == 1 {
fmt.Fprintf(&buf, statement.Engine.Quote(t[0]))
}
case []interface{}:
t := tablename.([]interface{})
l := len(t)
var table string
if l > 0 {
f := t[0]
v := rValue(f)
t := v.Type()
if t.Kind() == reflect.String {
table = f.(string)
} else if t.Kind() == reflect.Struct {
table = statement.Engine.tbName(v)
}
}
if l > 1 {
fmt.Fprintf(&buf, "%v AS %v", statement.Engine.Quote(table),
statement.Engine.Quote(fmt.Sprintf("%v", t[1])))
} else if l == 1 {
fmt.Fprintf(&buf, statement.Engine.Quote(table))
}
default:
fmt.Fprintf(&buf, statement.Engine.Quote(fmt.Sprintf("%v", tablename)))
}
fmt.Fprintf(&buf, " ON %v", condition) fmt.Fprintf(&buf, "%s ON %v", tbName, condition)
statement.JoinStr = buf.String() statement.JoinStr = buf.String()
statement.joinArgs = append(statement.joinArgs, args...) statement.joinArgs = append(statement.joinArgs, args...)
return statement return statement
@ -809,18 +780,20 @@ func (statement *Statement) Unscoped() *Statement {
} }
func (statement *Statement) genColumnStr() string { func (statement *Statement) genColumnStr() string {
var buf bytes.Buffer
if statement.RefTable == nil { if statement.RefTable == nil {
return "" return ""
} }
var buf builder.StringBuilder
columns := statement.RefTable.Columns() columns := statement.RefTable.Columns()
for _, col := range columns { for _, col := range columns {
if statement.OmitStr != "" { if statement.omitColumnMap.contain(col.Name) {
if _, ok := getFlagForColumn(statement.columnMap, col); ok { continue
continue }
}
if len(statement.columnMap) > 0 && !statement.columnMap.contain(col.Name) {
continue
} }
if col.MapType == core.ONLYTODB { if col.MapType == core.ONLYTODB {
@ -831,10 +804,6 @@ func (statement *Statement) genColumnStr() string {
buf.WriteString(", ") buf.WriteString(", ")
} }
if col.IsPrimaryKey && statement.Engine.Dialect().DBType() == "ql" {
buf.WriteString("id() AS ")
}
if statement.JoinStr != "" { if statement.JoinStr != "" {
if statement.TableAlias != "" { if statement.TableAlias != "" {
buf.WriteString(statement.TableAlias) buf.WriteString(statement.TableAlias)
@ -859,11 +828,13 @@ func (statement *Statement) genCreateTableSQL() string {
func (statement *Statement) genIndexSQL() []string { func (statement *Statement) genIndexSQL() []string {
var sqls []string var sqls []string
tbName := statement.TableName() tbName := statement.TableName()
quote := statement.Engine.Quote for _, index := range statement.RefTable.Indexes {
for idxName, index := range statement.RefTable.Indexes {
if index.Type == core.IndexType { if index.Type == core.IndexType {
sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)), sql := statement.Engine.dialect.CreateIndexSql(tbName, index)
quote(tbName), quote(strings.Join(index.Cols, quote(",")))) /*idxTBName := strings.Replace(tbName, ".", "_", -1)
idxTBName = strings.Replace(idxTBName, `"`, "", -1)
sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(idxTBName, idxName)),
quote(tbName), quote(strings.Join(index.Cols, quote(","))))*/
sqls = append(sqls, sql) sqls = append(sqls, sql)
} }
} }
@ -889,16 +860,18 @@ func (statement *Statement) genUniqueSQL() []string {
func (statement *Statement) genDelIndexSQL() []string { func (statement *Statement) genDelIndexSQL() []string {
var sqls []string var sqls []string
tbName := statement.TableName() tbName := statement.TableName()
idxPrefixName := strings.Replace(tbName, `"`, "", -1)
idxPrefixName = strings.Replace(idxPrefixName, `.`, "_", -1)
for idxName, index := range statement.RefTable.Indexes { for idxName, index := range statement.RefTable.Indexes {
var rIdxName string var rIdxName string
if index.Type == core.UniqueType { if index.Type == core.UniqueType {
rIdxName = uniqueName(tbName, idxName) rIdxName = uniqueName(idxPrefixName, idxName)
} else if index.Type == core.IndexType { } else if index.Type == core.IndexType {
rIdxName = indexName(tbName, idxName) rIdxName = indexName(idxPrefixName, idxName)
} }
sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(rIdxName)) sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(statement.Engine.TableName(rIdxName, true)))
if statement.Engine.dialect.IndexOnTable() { if statement.Engine.dialect.IndexOnTable() {
sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(statement.TableName())) sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(tbName))
} }
sqls = append(sqls, sql) sqls = append(sqls, sql)
} }
@ -949,7 +922,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{},
v := rValue(bean) v := rValue(bean)
isStruct := v.Kind() == reflect.Struct isStruct := v.Kind() == reflect.Struct
if isStruct { if isStruct {
statement.setRefValue(v) statement.setRefBean(bean)
} }
var columnStr = statement.ColumnStr var columnStr = statement.ColumnStr
@ -982,13 +955,17 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{},
if err := statement.mergeConds(bean); err != nil { if err := statement.mergeConds(bean); err != nil {
return "", nil, err return "", nil, err
} }
} else {
if err := statement.processIDParam(); err != nil {
return "", nil, err
}
} }
condSQL, condArgs, err := builder.ToSQL(statement.cond) condSQL, condArgs, err := builder.ToSQL(statement.cond)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
sqlStr, err := statement.genSelectSQL(columnStr, condSQL) sqlStr, err := statement.genSelectSQL(columnStr, condSQL, true, true)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -1001,7 +978,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa
var condArgs []interface{} var condArgs []interface{}
var err error var err error
if len(beans) > 0 { if len(beans) > 0 {
statement.setRefValue(rValue(beans[0])) statement.setRefBean(beans[0])
condSQL, condArgs, err = statement.genConds(beans[0]) condSQL, condArgs, err = statement.genConds(beans[0])
} else { } else {
condSQL, condArgs, err = builder.ToSQL(statement.cond) condSQL, condArgs, err = builder.ToSQL(statement.cond)
@ -1018,7 +995,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa
selectSQL = "count(*)" selectSQL = "count(*)"
} }
} }
sqlStr, err := statement.genSelectSQL(selectSQL, condSQL) sqlStr, err := statement.genSelectSQL(selectSQL, condSQL, false, false)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -1027,7 +1004,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa
} }
func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) { func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) {
statement.setRefValue(rValue(bean)) statement.setRefBean(bean)
var sumStrs = make([]string, 0, len(columns)) var sumStrs = make([]string, 0, len(columns))
for _, colName := range columns { for _, colName := range columns {
@ -1043,7 +1020,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri
return "", nil, err return "", nil, err
} }
sqlStr, err := statement.genSelectSQL(sumSelect, condSQL) sqlStr, err := statement.genSelectSQL(sumSelect, condSQL, true, true)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -1051,27 +1028,20 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri
return sqlStr, append(statement.joinArgs, condArgs...), nil return sqlStr, append(statement.joinArgs, condArgs...), nil
} }
func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, err error) { func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, needOrderBy bool) (string, error) {
var distinct string var (
distinct string
dialect = statement.Engine.Dialect()
quote = statement.Engine.Quote
fromStr = " FROM "
top, mssqlCondi, whereStr string
)
if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") {
distinct = "DISTINCT " distinct = "DISTINCT "
} }
var dialect = statement.Engine.Dialect()
var quote = statement.Engine.Quote
var top string
var mssqlCondi string
if err := statement.processIDParam(); err != nil {
return "", err
}
var buf bytes.Buffer
if len(condSQL) > 0 { if len(condSQL) > 0 {
fmt.Fprintf(&buf, " WHERE %v", condSQL) whereStr = " WHERE " + condSQL
} }
var whereStr = buf.String()
var fromStr = " FROM "
if dialect.DBType() == core.MSSQL && strings.Contains(statement.TableName(), "..") { if dialect.DBType() == core.MSSQL && strings.Contains(statement.TableName(), "..") {
fromStr += statement.TableName() fromStr += statement.TableName()
@ -1118,9 +1088,10 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, e
} }
var orderStr string var orderStr string
if len(statement.OrderStr) > 0 { if needOrderBy && len(statement.OrderStr) > 0 {
orderStr = " ORDER BY " + statement.OrderStr orderStr = " ORDER BY " + statement.OrderStr
} }
var groupStr string var groupStr string
if len(statement.GroupByStr) > 0 { if len(statement.GroupByStr) > 0 {
groupStr = " GROUP BY " + statement.GroupByStr groupStr = " GROUP BY " + statement.GroupByStr
@ -1130,45 +1101,50 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, e
} }
} }
// !nashtsai! REVIEW Sprintf is considered slowest mean of string concatnation, better to work with builder pattern var buf builder.StringBuilder
a = fmt.Sprintf("SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr) fmt.Fprintf(&buf, "SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr)
if len(mssqlCondi) > 0 { if len(mssqlCondi) > 0 {
if len(whereStr) > 0 { if len(whereStr) > 0 {
a += " AND " + mssqlCondi fmt.Fprint(&buf, " AND ", mssqlCondi)
} else { } else {
a += " WHERE " + mssqlCondi fmt.Fprint(&buf, " WHERE ", mssqlCondi)
} }
} }
if statement.GroupByStr != "" { if statement.GroupByStr != "" {
a = fmt.Sprintf("%v GROUP BY %v", a, statement.GroupByStr) fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr)
} }
if statement.HavingStr != "" { if statement.HavingStr != "" {
a = fmt.Sprintf("%v %v", a, statement.HavingStr) fmt.Fprint(&buf, " ", statement.HavingStr)
} }
if statement.OrderStr != "" { if needOrderBy && statement.OrderStr != "" {
a = fmt.Sprintf("%v ORDER BY %v", a, statement.OrderStr) fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr)
} }
if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE { if needLimit {
if statement.Start > 0 { if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE {
a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start) if statement.Start > 0 {
} else if statement.LimitN > 0 { fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", statement.LimitN, statement.Start)
a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN) } else if statement.LimitN > 0 {
} fmt.Fprint(&buf, " LIMIT ", statement.LimitN)
} else if dialect.DBType() == core.ORACLE { }
if statement.Start != 0 || statement.LimitN != 0 { } else if dialect.DBType() == core.ORACLE {
a = fmt.Sprintf("SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d", columnStr, columnStr, a, statement.Start+statement.LimitN, statement.Start) if statement.Start != 0 || statement.LimitN != 0 {
oldString := buf.String()
buf.Reset()
fmt.Fprintf(&buf, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d",
columnStr, columnStr, oldString, statement.Start+statement.LimitN, statement.Start)
}
} }
} }
if statement.IsForUpdate { if statement.IsForUpdate {
a = dialect.ForUpdateSql(a) return dialect.ForUpdateSql(buf.String()), nil
} }
return return buf.String(), nil
} }
func (statement *Statement) processIDParam() error { func (statement *Statement) processIDParam() error {
if statement.idParam == nil { if statement.idParam == nil || statement.RefTable == nil {
return nil return nil
} }

View File

@ -17,7 +17,7 @@ import (
const ( const (
// Version show the xorm's version // Version show the xorm's version
Version string = "0.6.4.0910" Version string = "0.7.0.0504"
) )
func regDrvsNDialects() bool { func regDrvsNDialects() bool {
@ -31,7 +31,7 @@ func regDrvsNDialects() bool {
"mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }},
"mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }},
"postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, "postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }},
"pgx": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, "pgx": {"postgres", func() core.Driver { return &pqDriverPgx{} }, func() core.Dialect { return &postgres{} }},
"sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }}, "sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }},
"oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }}, "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }},
"goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }}, "goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }},
@ -90,6 +90,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
TagIdentifier: "xorm", TagIdentifier: "xorm",
TZLocation: time.Local, TZLocation: time.Local,
tagHandlers: defaultTagHandlers, tagHandlers: defaultTagHandlers,
cachers: make(map[string]core.Cacher),
} }
if uri.DbType == core.SQLITE { if uri.DbType == core.SQLITE {
@ -108,6 +109,13 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
return engine, nil return engine, nil
} }
// NewEngineWithParams new a db manager with params. The params will be passed to dialect.
func NewEngineWithParams(driverName string, dataSourceName string, params map[string]string) (*Engine, error) {
engine, err := NewEngine(driverName, dataSourceName)
engine.dialect.SetParams(params)
return engine, err
}
// Clone clone an engine // Clone clone an engine
func (engine *Engine) Clone() (*Engine, error) { func (engine *Engine) Clone() (*Engine, error) {
return NewEngine(engine.DriverName(), engine.DataSourceName()) return NewEngine(engine.DriverName(), engine.DataSourceName())