diff --git a/Gopkg.lock b/Gopkg.lock index df8203267..38573b671 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -288,17 +288,19 @@ [[projects]] name = "github.com/go-xorm/builder" packages = ["."] - revision = "488224409dd8aa2ce7a5baf8d10d55764a913738" + revision = "dc8bf48f58fab2b4da338ffd25191905fd741b8f" + version = "v0.3.0" [[projects]] name = "github.com/go-xorm/core" packages = ["."] - revision = "cb1d0ca71f42d3ee1bf4aba7daa16099bc31a7e9" + revision = "c10e21e7e1cec20e09398f2dfae385e58c8df555" + version = "v0.6.0" [[projects]] name = "github.com/go-xorm/xorm" packages = ["."] - revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03" + revision = "ad69f7d8f0861a29438154bb0a20b60501298480" [[projects]] branch = "master" @@ -701,6 +703,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "59451a3ad1d449f75c5e9035daf542a377c5c4a397e219bebec0aa0007ab9c39" + inputs-digest = "5ae18d543bbb8186589c003422b333097d67bb5fed8b4c294be70c012ccffc94" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 933c858ce..65155cea9 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -33,7 +33,7 @@ ignored = ["google.golang.org/appengine*"] [[override]] name = "github.com/go-xorm/xorm" #version = "0.6.5" - revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03" + revision = "ad69f7d8f0861a29438154bb0a20b60501298480" [[override]] name = "github.com/go-sql-driver/mysql" diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index c4dd741ca..f087e9913 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -316,6 +316,9 @@ DEFAULT_KEEP_EMAIL_PRIVATE = false ; Default value for AllowCreateOrganization ; Every new user will have rights set to create organizations depending on this setting 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 = true ; Default value for EnableTimetracking diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index c7e80b957..5ed2ce43e 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -182,6 +182,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `CAPTCHA_TYPE`: **image**: \[image, 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 +- `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default. ## Webhook (`webhook`) diff --git a/main.go b/main.go index 179132f58..e73f1d60a 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" // register supported doc types + _ "code.gitea.io/gitea/modules/markup/csv" _ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/orgmode" diff --git a/models/action.go b/models/action.go index a6fb209a4..adf30bb88 100644 --- a/models/action.go +++ b/models/action.go @@ -477,6 +477,10 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err } 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 } } diff --git a/models/error.go b/models/error.go index 075704447..029c33aba 100644 --- a/models/error.go +++ b/models/error.go @@ -1259,3 +1259,88 @@ func IsErrU2FRegistrationNotExist(err error) bool { _, ok := err.(ErrU2FRegistrationNotExist) 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) +} diff --git a/models/issue.go b/models/issue.go index d97266b4e..1ac200c80 100644 --- a/models/issue.go +++ b/models/issue.go @@ -649,6 +649,20 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, if issue.IsClosed == isClosed { 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 if isClosed { issue.ClosedUnix = util.TimeStampNow() @@ -1283,7 +1297,7 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) { And("`comment`.type = ?", CommentTypeComment). And("`user`.is_active = ?", true). And("`user`.prohibit_login = ?", false). - Join("INNER", "user", "`user`.id = `comment`.poster_id"). + Join("INNER", "`user`", "`user`.id = `comment`.poster_id"). Distinct("poster_id"). Find(&userIDs); err != nil { 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() } + +// 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) +} diff --git a/models/issue_comment.go b/models/issue_comment.go index 1c7c57dd0..ad276e61f 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -66,6 +66,10 @@ const ( CommentTypeModifiedDeadline // Removed a due date CommentTypeRemovedDeadline + // Dependency added + CommentTypeAddDependency + //Dependency removed + CommentTypeRemoveDependency ) // CommentTag defines comment tag type @@ -81,23 +85,25 @@ const ( // Comment represents a comment in commit and issue page. type Comment struct { - ID int64 `xorm:"pk autoincr"` - Type CommentType - PosterID int64 `xorm:"INDEX"` - Poster *User `xorm:"-"` - IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-"` - LabelID int64 - Label *Label `xorm:"-"` - OldMilestoneID int64 - MilestoneID int64 - OldMilestone *Milestone `xorm:"-"` - Milestone *Milestone `xorm:"-"` - AssigneeID int64 - RemovedAssignee bool - Assignee *User `xorm:"-"` - OldTitle string - NewTitle string + ID int64 `xorm:"pk autoincr"` + Type CommentType + PosterID int64 `xorm:"INDEX"` + Poster *User `xorm:"-"` + IssueID int64 `xorm:"INDEX"` + Issue *Issue `xorm:"-"` + LabelID int64 + Label *Label `xorm:"-"` + OldMilestoneID int64 + MilestoneID int64 + OldMilestone *Milestone `xorm:"-"` + Milestone *Milestone `xorm:"-"` + AssigneeID int64 + RemovedAssignee bool + Assignee *User `xorm:"-"` + OldTitle string + NewTitle string + DependentIssueID int64 + DependentIssue *Issue `xorm:"-"` CommitID int64 Line int64 @@ -281,6 +287,15 @@ func (c *Comment) LoadAssigneeUser() error { 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 // and mentioned people. 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 { LabelID = opts.Label.ID } + comment := &Comment{ - Type: opts.Type, - PosterID: opts.Doer.ID, - Poster: opts.Doer, - IssueID: opts.Issue.ID, - LabelID: LabelID, - OldMilestoneID: opts.OldMilestoneID, - MilestoneID: opts.MilestoneID, - RemovedAssignee: opts.RemovedAssignee, - AssigneeID: opts.AssigneeID, - CommitID: opts.CommitID, - CommitSHA: opts.CommitSHA, - Line: opts.LineNum, - Content: opts.Content, - OldTitle: opts.OldTitle, - NewTitle: opts.NewTitle, + Type: opts.Type, + PosterID: opts.Doer.ID, + Poster: opts.Doer, + IssueID: opts.Issue.ID, + LabelID: LabelID, + OldMilestoneID: opts.OldMilestoneID, + MilestoneID: opts.MilestoneID, + RemovedAssignee: opts.RemovedAssignee, + AssigneeID: opts.AssigneeID, + CommitID: opts.CommitID, + CommitSHA: opts.CommitSHA, + Line: opts.LineNum, + Content: opts.Content, + OldTitle: opts.OldTitle, + NewTitle: opts.NewTitle, + DependentIssueID: opts.DependentIssueID, } if _, err = e.Insert(comment); err != nil { 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 type CreateCommentOptions struct { Type CommentType @@ -557,17 +607,18 @@ type CreateCommentOptions struct { Issue *Issue Label *Label - OldMilestoneID int64 - MilestoneID int64 - AssigneeID int64 - RemovedAssignee bool - OldTitle string - NewTitle string - CommitID int64 - CommitSHA string - LineNum int64 - Content string - Attachments []string // UUIDs of attachments + DependentIssueID int64 + OldMilestoneID int64 + MilestoneID int64 + AssigneeID int64 + RemovedAssignee bool + OldTitle string + NewTitle string + CommitID int64 + CommitSHA string + LineNum int64 + Content string + Attachments []string // UUIDs of attachments } // CreateComment creates comment of issue or commit. diff --git a/models/issue_dependency.go b/models/issue_dependency.go new file mode 100644 index 000000000..157e9257c --- /dev/null +++ b/models/issue_dependency.go @@ -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 +} diff --git a/models/issue_dependency_test.go b/models/issue_dependency_test.go new file mode 100644 index 000000000..571bce318 --- /dev/null +++ b/models/issue_dependency_test.go @@ -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) +} diff --git a/models/issue_list.go b/models/issue_list.go index 05130a6ee..fdfca71af 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -166,7 +166,7 @@ func (issues IssueList) loadAssignees(e Engine) error { var assignees = make(map[int64][]*User, len(issues)) 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()). Rows(new(AssigneeIssue)) if err != nil { diff --git a/models/issue_watch.go b/models/issue_watch.go index 3e7d24821..579c91547 100644 --- a/models/issue_watch.go +++ b/models/issue_watch.go @@ -67,7 +67,7 @@ func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error Where("`issue_watch`.issue_id = ?", issueID). And("`user`.is_active = ?", true). 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) return } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index cc262d810..48c4228fe 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -192,6 +192,8 @@ var migrations = []Migration{ NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics), // v69 -> v70 NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), + // v70 -> v71 + NewMigration("add issue_dependencies", addIssueDependencies), } // Migrate database to current version diff --git a/models/migrations/v70.go b/models/migrations/v70.go new file mode 100644 index 000000000..4ce1d4ee5 --- /dev/null +++ b/models/migrations/v70.go @@ -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 +} diff --git a/models/models.go b/models/models.go index aaf1370fd..9477b6950 100644 --- a/models/models.go +++ b/models/models.go @@ -118,6 +118,7 @@ func init() { new(TrackedTime), new(DeletedBranch), new(RepoIndexerStatus), + new(IssueDependency), new(LFSLock), new(Reaction), new(IssueAssignees), diff --git a/models/org.go b/models/org.go index 23f6c58bf..bd5fc825b 100644 --- a/models/org.go +++ b/models/org.go @@ -383,7 +383,7 @@ func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) { func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) { ous := make([]*OrgUser, 0, 10) 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) if !all { // Only show public organizations @@ -575,7 +575,7 @@ func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, return teams, e. Where("`team_user`.org_id = ?", org.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). Asc("`user`.name"). Cols(cols...). diff --git a/models/repo.go b/models/repo.go index c795deee8..3c4908b0d 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1345,7 +1345,11 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err units = append(units, RepoUnit{ RepoID: repo.ID, 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 { units = append(units, RepoUnit{ @@ -1954,7 +1958,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { var repo 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)). And("`user`.lower_name = ?", strings.ToLower(ownerName)). Get(&repo) diff --git a/models/repo_unit.go b/models/repo_unit.go index 49b62ec9c..1e1778356 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -73,6 +73,7 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) { type IssuesConfig struct { EnableTimetracker bool AllowOnlyContributorsToTrackTime bool + EnableDependencies bool } // FromDB fills up a IssuesConfig from serialized format. @@ -165,7 +166,6 @@ func (r *RepoUnit) IssuesConfig() *IssuesConfig { func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { return r.Config.(*ExternalTrackerConfig) } - func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { return units, e.Where("repo_id = ?", repoID).Find(&units) } diff --git a/models/repo_watch.go b/models/repo_watch.go index 8019027c1..95c7e44e9 100644 --- a/models/repo_watch.go +++ b/models/repo_watch.go @@ -54,7 +54,7 @@ func getWatchers(e Engine, repoID int64) ([]*Watch, error) { return watches, e.Where("`watch`.repo_id=?", repoID). And("`user`.is_active=?", true). And("`user`.prohibit_login=?", false). - Join("INNER", "user", "`user`.id = `watch`.user_id"). + Join("INNER", "`user`", "`user`.id = `watch`.user_id"). Find(&watches) } diff --git a/models/user.go b/models/user.go index 0b7af8df6..32b9bfec9 100644 --- a/models/user.go +++ b/models/user.go @@ -374,9 +374,9 @@ func (u *User) GetFollowers(page int) ([]*User, error) { Limit(ItemsPerPage, (page-1)*ItemsPerPage). Where("follow.follow_id=?", u.ID) if setting.UsePostgreSQL { - sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`) + sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id") } 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) } @@ -393,9 +393,9 @@ func (u *User) GetFollowing(page int) ([]*User, error) { Limit(ItemsPerPage, (page-1)*ItemsPerPage). Where("follow.user_id=?", u.ID) if setting.UsePostgreSQL { - sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`) + sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id") } 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) } diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 8a18ab0fa..deb62fbdf 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -113,6 +113,7 @@ type RepoSettingForm struct { PullsAllowSquash bool EnableTimetracker bool AllowOnlyContributorsToTrackTime bool + EnableIssueDependencies bool // Admin settings EnableHealthCheck bool diff --git a/modules/context/repo.go b/modules/context/repo.go index 1fe5482ce..7239b353f 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -104,6 +104,11 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b 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 func (r *Repository) GetCommitsCount() (int64, error) { var contextName string diff --git a/modules/lfs/server.go b/modules/lfs/server.go index dc1279177..d6543816b 100644 --- a/modules/lfs/server.go +++ b/modules/lfs/server.go @@ -85,9 +85,12 @@ type link struct { 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 func ObjectOidHandler(ctx *context.Context) { - if !setting.LFS.StartServer { writeStatus(ctx, 404) return @@ -110,6 +113,11 @@ func ObjectOidHandler(ctx *context.Context) { } 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) if err != nil { log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err) @@ -222,7 +230,7 @@ func PostHandler(ctx *context.Context) { return } - if !oidRegExp.MatchString(rv.Oid) { + if !isOidValid(rv.Oid) { writeStatus(ctx, 404) return } @@ -249,7 +257,6 @@ func PostHandler(ctx *context.Context) { // BatchHandler provides the batch api func BatchHandler(ctx *context.Context) { - if !setting.LFS.StartServer { writeStatus(ctx, 404) return @@ -266,6 +273,10 @@ func BatchHandler(ctx *context.Context) { // Create a response object for _, object := range bv.Objects { + if !isOidValid(object.Oid) { + continue + } + repository, err := models.GetRepositoryByOwnerAndName(object.User, object.Repo) if err != nil { @@ -292,12 +303,10 @@ func BatchHandler(ctx *context.Context) { continue } - if oidRegExp.MatchString(object.Oid) { - // Object is not found - meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID}) - if err == nil { - responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta))) - } + // Object is not found + meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID}) + if err == nil { + responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta))) } } diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go new file mode 100644 index 000000000..077947e77 --- /dev/null +++ b/modules/markup/csv/csv.go @@ -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(``) + for { + fields, err := rd.Read() + if err == io.EOF { + break + } + if err != nil { + continue + } + tmpBlock.WriteString("") + for _, field := range fields { + tmpBlock.WriteString("") + } + tmpBlock.WriteString("") + } + tmpBlock.WriteString("
") + tmpBlock.WriteString(html.EscapeString(field)) + tmpBlock.WriteString("
") + + return tmpBlock.Bytes() +} diff --git a/modules/markup/csv/csv_test.go b/modules/markup/csv/csv_test.go new file mode 100644 index 000000000..f050296ce --- /dev/null +++ b/modules/markup/csv/csv_test.go @@ -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": "
a
", + "1,2": "
12
", + "
": "
<br/>
", + } + + for k, v := range kases { + res := parser.Render([]byte(k), "", nil, false) + assert.EqualValues(t, v, string(res)) + } +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 72be6689f..504304813 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1180,6 +1180,7 @@ var Service struct { DefaultAllowCreateOrganization bool EnableTimetracking bool DefaultEnableTimetracking bool + DefaultEnableDependencies bool DefaultAllowOnlyContributorsToTrackTime bool NoReplyAddress string @@ -1210,6 +1211,7 @@ func newService() { if Service.EnableTimetracking { 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.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org") diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 6206db445..f77ae2e09 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -32,16 +32,8 @@ twofa_scratch=Zwei-Faktor-Einmalpasswort passcode=PIN 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_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 repository=Repository @@ -130,7 +122,6 @@ federated_avatar_lookup=Föderierte Profilbilder einschalten federated_avatar_lookup_popup=Föderierte Profilbilder via Libravatar aktivieren. disable_registration=Registrierung deaktivieren 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_popup=Benutzeranmeldung via OpenID 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. 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 FIDO U2F 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_nickname=Nickname u2f_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel, um diesen zu registrieren. 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_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.assignees=Zuständig issues.new.clear_assignees=Zuständige entfernen -issues.new.no_assignees=Niemand zugewiesen issues.no_ref=Keine Branch/Tag angegeben issues.create=Issue erstellen 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.done=Fertig -topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen -topic.format_prompt=Themen müssen mit einem Buchstaben oder einer Zahl beginnen. Sie können Bindestriche (-) enthalten und dürfen nicht länger als 35 Zeichen sein [org] org_name_holder=Name der Organisation @@ -1273,8 +1258,6 @@ dashboard.operation_switch=Wechseln dashboard.operation_run=Ausführen dashboard.clean_unbind_oauth=Nicht verbundene OAuth-Verbindungen löschen 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_success=Alle Repository-Archive wurden gelöscht. 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.register_email_confirm=E-Mail-Bestätigung benötigt zum Registrieren 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_signin=OpenID-Anmeldung aktivieren config.show_registration_button=Schaltfläche zum Registrieren anzeigen diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 84b435adb..ce07cb269 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -32,16 +32,16 @@ twofa_scratch = Two-Factor Scratch Code passcode = Passcode 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_use_twofa = Use a two-factor code from your phone -u2f_error = We can't read your security key! -u2f_unsupported_browser = Your browser don't support U2F keys. Please try another browser. -u2f_error_1 = An unknown error occured. Please retry. -u2f_error_2 = Please make sure that you're using an encrypted connection (https://) and visiting the correct URL. -u2f_error_3 = The server could not proceed 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_5 = Timeout reached before your key could be read. Please reload to retry. +u2f_error = Could not read your security key. +u2f_unsupported_browser = Your browser does not support U2F security keys. +u2f_error_1 = An unknown error occurred. Please retry. +u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL. +u2f_error_3 = The server could not process your request. +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 this page and retry. u2f_reload = Reload repository = Repository @@ -130,7 +130,7 @@ federated_avatar_lookup = Enable Federated Avatars federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar. disable_registration = Disable Self-Registration 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_popup = Enable user sign-in via OpenID. 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. 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 FIDO U2F standard. -u2f_require_twofa = Two-Factor-Authentication must be enrolled in order to use security keys. +u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the FIDO U2F standard. +u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys. u2f_register_key = Add Security Key u2f_nickname = Nickname u2f_press_button = Press the button on your security key to register it. 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_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.assignees = 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.create = Create Issue 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_remove = "removed the due date %s %s" 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 = `%[2]s added a new dependency %[3]s` +issues.dependency.removed_dependency = `%[2]s 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.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.done = Done -topic.count_prompt = You can't select more than 25 topics -topic.format_prompt = Topics must start with a letter or number, can include hyphens(-) and must be no more than 35 characters long +topic.count_prompt = You can not select more than 25 topics +topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long. [org] org_name_holder = Organization Name @@ -1276,8 +1302,8 @@ dashboard.operation_switch = Switch dashboard.operation_run = Run dashboard.clean_unbind_oauth = Clean unbound OAuth connections 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_success = All not activated accounts have been deleted. +dashboard.delete_inactivate_accounts = Delete all unactivated accounts +dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted. dashboard.delete_repo_archives = Delete all repository archives dashboard.delete_repo_archives_success = All repository archives have been deleted. 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.register_email_confirm = Require Email Confirmation to Register 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_signin = Enable OpenID Sign-In 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_allow_only_contributors_to_track_time = Let Only Contributors Track Time config.no_reply_address = Hidden Email Domain +config.default_enable_dependencies = Enable Issue Dependencies by Default config.webhook_config = Webhook Configuration config.queue_length = Queue Length diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 65240c87d..a69aad09a 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -32,11 +32,6 @@ passcode=Contraseña u2f_insert_key=Inserte su clave de seguridad 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 repository=Repositorio diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 3f8a6c2a9..098ff005a 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -32,16 +32,8 @@ twofa_scratch=Code de secours pour l'authentification à deux facteurs passcode=Code d'accès 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_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 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. 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. -allow_only_external_registration_popup=N'autoriser l'inscription qu'à partir des services externes. openid_signin=Activer l'inscription OpenID openid_signin_popup=Activer l'authentification via 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. 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 FIDO U2F. -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_nickname=Pseudonyme 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_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_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.assignees=Affecté à issues.new.clear_assignees=Supprimer les affectations -issues.new.no_assignees=Aucune affectation issues.no_ref=Aucune branche/tag spécifiés issues.create=Créer un ticket 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.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_name_holder=Nom de l'organisation @@ -1471,7 +1456,6 @@ config.db_path=Emplacement config.service_config=Configuration du service 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.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_signin=Activer la connexion avec OpenID config.show_registration_button=Afficher le bouton d'enregistrement diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 50aa4c656..8d0961b97 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -32,16 +32,8 @@ twofa_scratch=Kode Awal Dua Faktor passcode=Kode Akses 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_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 repository=Repositori diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 7a8909800..980c3770c 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -32,16 +32,8 @@ twofa_scratch=Codice di recupero per la verifica in due passaggi passcode=Codice 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_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 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. disable_registration=Disattiva Self-Registration 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_popup=Attiva registrazione utente via OpenID. 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. 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 FIDO U2F. -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_nickname=Nickname u2f_press_button=Premi il pulsante sulla tua chiave di sicurezza per registrarla. 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_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.assignees=Assegnatari issues.new.clear_assignees=Cancella assegnatari -issues.new.no_assignees=Nessuno assegnato issues.no_ref=Nessun Branch/Tag specificato issues.create=Crea Problema 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.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_name_holder=Nome dell'Organizzazione @@ -1472,7 +1456,6 @@ config.db_path=Percorso config.service_config=Configurazione Servizio config.register_email_confirm=Richiedere la conferma Email per registrarsi 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_signin=Attiva l'accesso tramite OpenID config.show_registration_button=Mostra Pulsane Registrazione diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 4bb962cb0..d835c6478 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -32,16 +32,8 @@ twofa_scratch=Divu faktoru vienreizējais kods passcode=Kods 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_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 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. 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. -allow_only_external_registration_popup=Ļaut reģistrēties tikai izmantojot ārējos pakalpojumus. openid_signin=Iespējot OpenID autorizāciju openid_signin_popup=Iespējot lietotāju autorizāciju ar 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. 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 FIDO U2F 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_nickname=Segvārds 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_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_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.assignees=Atbildīgie 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.create=Pieteikt problēmu 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.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_name_holder=Organizācijas nosaukums @@ -1269,8 +1254,6 @@ dashboard.operation_switch=Pārslēgt dashboard.operation_run=Palaist 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.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_success=Visu repozitoriju arhīvi tika izdzēsti. 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.register_email_confirm=Reģistrējoties pieprasīt apstiprināt e-pasta adresi 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_signin=Iespējot OpenID autorizāciju config.show_registration_button=Rādīt reģistrēšanās pogu diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 82b323077..c5898a999 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -32,14 +32,8 @@ twofa_scratch=Eenmalige twee factor authenticatie code passcode=PIN 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_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 repository=Repository diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index f7b8ed079..4590c7cf5 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -32,16 +32,16 @@ twofa_scratch=Código de backup da autenticação de dois fatores passcode=Senha 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_use_twofa=Use um código de dois fatores no seu telefone -u2f_error=Não conseguimos ler sua chave de segurança! -u2f_unsupported_browser=Seu navegador não suporta chaves U2F. Por favor, tente outro navegador. +u2f_error=Não foi possível ler sua chave de segurança. +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_2=Por favor, certifique-se de que você está usando uma conexão criptografada (https://) e visite a URL correta. -u2f_error_3=O servidor não pôde prosseguir com 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_5=Tempo limite atingido antes de sua chave poder ser lida. Por favor, recarregue para tentar novamente. +u2f_error_2=Por favor, certifique-se de usar a URL correto, criptografado (https://). +u2f_error_3=O servidor não pôde processar sua solicitação. +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 esta página e tente novamente. u2f_reload=Recarregar repository=Repositório @@ -53,7 +53,7 @@ new_mirror=Novo mirror new_fork=Novo Fork de Repositório new_org=Nova organização manage_org=Gerenciar organizações -admin_panel=Administração do site +admin_panel=Administração geral account_settings=Configurações da conta settings=Configurações 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. 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. repo_path=Caminho raíz do repositó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. 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. -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_popup=Habilitar o acesso de usuários 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. 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 FIDO U2F. -u2f_require_twofa=Autenticação de dois fatores deve estar inscrita para usar chaves de seguranç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 FIDO U2F. +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_nickname=Apelido 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_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_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.or=ou editor.cancel_lower=Cancelar -editor.commit_changes=Confirmar alterações +editor.commit_changes=Realizar commit das alterações editor.add_tmpl=Adicionar '%s/' editor.add=Adicionar '%s' editor.update=Atualizar '%s' @@ -650,7 +650,7 @@ issues.new.open_milestone=Marcos abertos issues.new.closed_milestone=Marcos fechados issues.new.assignees=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.create=Criar issue issues.new_label=Nova etiqueta @@ -682,7 +682,7 @@ issues.filter_milestone_no_select=Todos os marcos issues.filter_assignee=Atribuído issues.filter_assginee_no_select=Todos os responsáveis 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.created_by_you=Criado por 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_remove=removeu a data limite %s %s 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=`%[2]s adicionou uma nova dependência %[3]s` +issues.dependency.removed_dependency=`%[2]s 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.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.has_pull_request=`Um pull request entre esses branches já existe: %[2]s#%[3]d` pulls.create=Criar pull request -pulls.title_desc=quer mesclar %[1]d commits de %[2]s em %[3]s +pulls.title_desc=quer realizar o merge de %[1]d commits de %[2]s em %[3]s pulls.merged_title_desc=fez merge dos %[1]d commits de %[2]s em %[3]s %[4]s pulls.tab_conversation=Conversação pulls.tab_commits=Commits @@ -818,14 +845,14 @@ milestones.new=Novo marco milestones.open_tab=%d Aberto milestones.close_tab=%d Fechado milestones.closed=Fechado %s -milestones.no_due_date=Sem prazo +milestones.no_due_date=Sem data limite milestones.open=Reabrir milestones.close=Fechar milestones.new_subheader=Marcos organizam as issues e acompanham o progresso. milestones.create=Criar marco milestones.title=Título milestones.desc=Descrição -milestones.due_date=Prazo (opcional) +milestones.due_date=Data limite (opcional) milestones.clear=Limpar milestones.invalid_due_date_format=Formato da data limite deve ser 'dd/mm/aaaa'. 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_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.filter_sort.closest_due_date=Prazo mais próximo -milestones.filter_sort.furthest_due_date=Prazo mais longe +milestones.filter_sort.closest_due_date=Data limite mais próxima +milestones.filter_sort.furthest_due_date=Data limite mais distante milestones.filter_sort.least_complete=Menos completo milestones.filter_sort.most_complete=Mais completo 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.title.issues_1=+%d Issue activity.title.issues_n=+%d Issues -activity.title.issues_closed_by=%s fechado por %s -activity.title.issues_created_by=%s criado por %s +activity.title.issues_closed_by=%s fechada por %s +activity.title.issues_created_by=%s criada por %s activity.closed_issue_label=Fechado activity.new_issues_count_1=Nova issue activity.new_issues_count_n=Novas issues activity.new_issue_label=Aberto activity.title.unresolved_conv_1=%d conversa não resolvida 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.title.releases_1=%d Versão 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.delete=Excluir este repositório settings.delete_desc=A exclusão de um repositório é permanente e não pode ser desfeita. -settings.delete_notices_1=-Esta operação NÃO PODERÁ ser desfeita. +settings.delete_notices_1=- Esta operação NÃO PODERÁ ser desfeita. settings.delete_notices_2=- Essa operação excluirá permanentemente o repositório %s, 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.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.done=Feito topic.count_prompt=Você não pode selecionar mais de 25 tópicos -topic.format_prompt=Tópicos devem começar com uma letra ou um número, podem incluir hífens (-) e não devem ter mais de 35 caracteres +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_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. 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_url=URL base do Gitea 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.register_email_confirm=Exigir confirmação de e-mail para se cadastrar 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_signin=Habilitar acesso via OpenID 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_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.default_enable_dependencies=Habilitar dependências de issue por padrão config.webhook_config=Configuração de Hook da Web config.queue_length=Tamanho da fila @@ -1586,7 +1614,7 @@ create_repo=criou o repositório %s rename_repo=renomeou o repositório %[1]s para %[3]s commit_repo=fez push para %[3]s em %[4]s create_issue=`abriu a issue %s#%[2]s` -close_issue=`fechou issue %s#%[2]s` +close_issue=`fechou a issue %s#%[2]s` reopen_issue=`reabriu a issue %s#%[2]s` create_pull_request=`criou o pull request %s#%[2]s` close_pull_request=`fechou o pull request %s#%[2]s` diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 73a929759..5b932f327 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -32,16 +32,8 @@ twofa_scratch=Двухфакторный scratch-код passcode=Пароль u2f_insert_key=Вставьте ключ безопасности -u2f_sign_in=Нажмите кнопку на ключе безопасности. Если вы не можете найти кнопку, вставьте его снова. u2f_press_button=Пожалуйста нажмите кнопку на вашем ключе безопасности… 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=Обновить repository=Репозиторий @@ -130,7 +122,6 @@ federated_avatar_lookup=Включить федеративные аватары federated_avatar_lookup_popup=Включите поиск федеративного аватара для использования службы с открытым исходным кодом на основе libravatar. disable_registration=Отключить самостоятельную регистрацию disable_registration_popup=Запретить самостоятельную регистрацию. Только администраторы смогут создавать новые учетные записи пользователей. -allow_only_external_registration_popup=Включить регистрацию только через сторонние сервисы. openid_signin=Включение входа через OpenID openid_signin_popup=Включение входа через OpenID. openid_signup=Включить саморегистрацию OpenID @@ -463,13 +454,10 @@ then_enter_passcode=И введите пароль, показанный в пр passcode_invalid=Неверный пароль. попробуйте снова. twofa_enrolled=Для вашего аккаунта была включена двухфакторная аутентификация. Сохраните ваш scratch-токен (%s), он не сохраняется на сервере! -u2f_desc=Ключами безопасности являются аппаратные устройства, содержащие криптографические ключи. Они могут использоваться для двухфакторной аутентификации. Ключ безопасности должен поддерживать стандарт FIDO U2F. -u2f_require_twofa=Для использования ключей безопасности необходимо включить двухфакторную аутентификацию. u2f_register_key=Добавить ключ безопасности u2f_nickname=Имя пользователя u2f_press_button=Нажмите кнопку на ключе безопасности, чтобы зарегистрировать его. u2f_delete_key=Удалить ключ безопасности -u2f_delete_key_desc=Если вы удалите ключ безопасности, вы не сможете использовать его для входа. Вы уверены? manage_account_links=Управление привязанными аккаунтами manage_account_links_desc=Эти внешние аккаунты привязаны к вашему аккаунту Gitea. @@ -650,7 +638,6 @@ issues.new.open_milestone=Открыть этап issues.new.closed_milestone=Завершенные этапы issues.new.assignees=Назначенные issues.new.clear_assignees=Убрать ответственных -issues.new.no_assignees=Никто не назначен issues.no_ref=Не указана ветка или тэг issues.create=Добавить задачу issues.new_label=Новая метка @@ -1168,8 +1155,6 @@ branch.protected_deletion_failed=Ветка '%s' защищена. Её нель topic.manage_topics=Редактировать тематические метки topic.done=Сохранить -topic.count_prompt=Вы не можете выбрать более 25 тем -topic.format_prompt=Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов [org] org_name_holder=Название организации @@ -1274,8 +1259,6 @@ dashboard.operation_switch=Переключить dashboard.operation_run=Запуск dashboard.clean_unbind_oauth=Очистить список незавершённых авторизаций OAuth dashboard.clean_unbind_oauth_success=Все незавершённые связи OAuth были удалены. -dashboard.delete_inactivate_accounts=Удалить все неактивированные учетные записи -dashboard.delete_inactivate_accounts_success=Все не активированные учетные записи были удалены. dashboard.delete_repo_archives=Удаление всех архивов репозиториев dashboard.delete_repo_archives_success=Все архивы репозиториев удалены. dashboard.delete_missing_repos=Удалить все записи о репозиториях с отсутствующими файлами Git @@ -1483,7 +1466,6 @@ config.db_path=Путь config.service_config=Сервисная конфигурация config.register_email_confirm=Требуется подтверждение по электронной почте config.disable_register=Отключить самостоятельную регистрацию -config.allow_only_external_registration=Включить регистрацию только через сторонние сервисы config.enable_openid_signup=Включить cамостоятельную регистрацию OpenID config.enable_openid_signin=Включение входа через OpenID config.show_registration_button=Показать кнопку регистрации diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index b6b0d2e91..7146f0219 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -32,16 +32,8 @@ twofa_scratch=Tvåfaktorsskrapkod passcode=Kod 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_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 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. disable_registration=Inaktivera Självregistrering 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_popup=Aktivera användarinloggning via 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. 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 FIDO U2F 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_nickname=Smeknamn 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_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_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.assignees=Tilldelade issues.new.clear_assignees=Rensa tilldelade -issues.new.no_assignees=Ingen tilldelad issues.no_ref=Ingen branch/Tag specificerad issues.create=Skapa Ärende 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.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_name_holder=Organisationsnamn @@ -1229,8 +1214,6 @@ dashboard.operation_switch=Byt till dashboard.operation_run=Kör dashboard.clean_unbind_oauth=Rena obundna OAuth anslutningar 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_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. @@ -1417,7 +1400,6 @@ config.db_path=Sökväg config.service_config=Tjänstkonfiguration config.register_email_confirm=Kräv mejlbekräftelse för att registrera 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_signin=Aktivera OpenID-inloggning config.show_registration_button=Visa registreringsknapp diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 839ee3806..e32a4ecb1 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -32,16 +32,8 @@ twofa_scratch=Двофакторний одноразовий пароль passcode=Код доступу u2f_insert_key=Вставте ключ безпеки -u2f_sign_in=Натисніть кнопку на ключі безпеки. Якщо не вдається знайти кнопки, повторно вставте ключ. u2f_press_button=Будь ласка, натисніть кнопку на ключі захисту... 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=Оновити repository=Репозиторій @@ -130,7 +122,6 @@ federated_avatar_lookup=Увімкнути федеративні аватари federated_avatar_lookup_popup=Увімкнути зовнішний Аватар за допомогою Libravatar. disable_registration=Вимкнути самостійну реєстрацію disable_registration_popup=Вимкнути самостійну реєстрацію користувачів, тільки адміністратор може створювати нові облікові записи. -allow_only_external_registration_popup=Включити реєстрацію тільки через зовнішні сервіси. openid_signin=Увімкнути реєстрацію за допомогою OpenID openid_signin_popup=Увімкнути вхід за допомогою OpenID. openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID @@ -463,13 +454,10 @@ then_enter_passcode=І введіть пароль, який відобража passcode_invalid=Некоректний пароль. Спробуй ще раз. twofa_enrolled=Для вашого облікового запису було включена двофакторна автентифікація. Зберігайте свій scratch-токен (%s) у безпечному місці, оскільки він показується лише один раз! -u2f_desc=Ключами безпеки є апаратні пристрої, що містять криптографічні ключі. Вони можуть використовуватися для двофакторної автентифікації. Ключ безпеки повинен підтримувати стандарт FIDO U2F. -u2f_require_twofa=Для використання ключів безпеки необхідно зареєструвати двофакторну аутентифікацію. u2f_register_key=Додати ключ безпеки u2f_nickname=Псевдонім u2f_press_button=Натисніть кнопку на ключі безпеки, щоб зареєструвати його. u2f_delete_key=Видалити ключ безпеки -u2f_delete_key_desc=Якщо ви видалите ключ безпеки, ви не зможете використати його для входу. Ти впевнені? manage_account_links=Керування обліковими записами manage_account_links_desc=Ці зовнішні акаунти прив'язані до вашого аккаунту Gitea. @@ -650,7 +638,6 @@ issues.new.open_milestone=Активні етапи issues.new.closed_milestone=Закриті етапи issues.new.assignees=Виконавеці issues.new.clear_assignees=Прибрати виконавеців -issues.new.no_assignees=Ніхто не призначений issues.no_ref=Не вказана гілка або тег issues.create=Створити проблему issues.new_label=Нова мітка @@ -781,6 +768,14 @@ issues.due_date_added=додав(ла) дату завершення %s %s issues.due_date_modified=термін змінено з %s %s на %s issues.due_date_remove=видалив(ла) дату завершення %s %s 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=
0%
[2]s додано нову залежність %[3]s' +issues.dependency.removed_dependency=
0%
[2]s видалено залежність %[3]s' +issues.dependency.blocks_short=Блоки pulls.desc=Увімкнути запити на злиття та інтерфейс узгодження правок. pulls.new=Новий запит на злиття @@ -1168,8 +1163,6 @@ branch.protected_deletion_failed=Гілка '%s' захищена. Її не м topic.manage_topics=Керувати тематичними мітками topic.done=Готово -topic.count_prompt=Ви не можете вибрати більше 25 тем -topic.format_prompt=Теми мають починатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів [org] org_name_holder=Назва організації @@ -1274,8 +1267,6 @@ dashboard.operation_switch=Перемкнути dashboard.operation_run=Запустити dashboard.clean_unbind_oauth=Очистити список незавершених авторизацій OAuth dashboard.clean_unbind_oauth_success=Всі незавершені зв'язки OAuth були видалені. -dashboard.delete_inactivate_accounts=Видалити всі неактивовані облікові записи -dashboard.delete_inactivate_accounts_success=Усі неактивовані облікові записи успішно видалено. dashboard.delete_repo_archives=Видалити всі архіви репозиторіїв dashboard.delete_repo_archives_success=Всі архіви репозиторіїв були видалені. dashboard.delete_missing_repos=Видалити всі записи про репозиторії з відсутніми файлами Git @@ -1483,7 +1474,6 @@ config.db_path=Шлях config.service_config=Конфігурація сервісу config.register_email_confirm=Потрібно підтвердити електронну пошту для реєстрації config.disable_register=Вимкнути самостійну реєстрацію -config.allow_only_external_registration=Включити реєстрацію тільки через сторонні сервіси config.enable_openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID config.enable_openid_signin=Увімкнути реєстрацію за допомогою OpenID config.show_registration_button=Показувати кнопку "Реєстрація diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 53a4a0426..1c9565580 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -32,16 +32,8 @@ twofa_scratch=两步验证口令 passcode=验证码 u2f_insert_key=插入安全密钥 -u2f_sign_in=按下安全密钥上的按钮。如果找不到按钮, 请重新插入。 u2f_press_button=请按下安全密钥上的按钮。 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=重新加载 repository=仓库 @@ -130,7 +122,6 @@ federated_avatar_lookup=启用 Federated 头像 federated_avatar_lookup_popup=启用 Federated Avatars 查找以使用开源的 Libravatar 服务。 disable_registration=禁止用户自助注册 disable_registration_popup=禁用用户自助注册。只有管理员才能创建新的用户帐户。 -allow_only_external_registration_popup=仅允许通过外部服务注册。 openid_signin=启用 OpenID 登录 openid_signin_popup=启用通过 OpenID 登录 openid_signup=启用 OpenID 自助注册 @@ -463,13 +454,10 @@ then_enter_passcode=并输入应用程序中显示的密码: passcode_invalid=密码不正确。再试一次。 twofa_enrolled=你的账号已经启用了两步验证。请保存初始令牌(%s)到一个安全的地方,此令牌仅当前显示一次。 -u2f_desc=安全密钥是包含加密算法的硬件设备。它们可以用于两步验证。安全密钥必须支持 FIDO U2F 标准。 -u2f_require_twofa=必须开启两步验证才能使用安全密钥。 u2f_register_key=添加安全密钥 u2f_nickname=昵称 u2f_press_button=按安全密钥上的按钮进行注册。 u2f_delete_key=移除安全密钥 -u2f_delete_key_desc=如果移除安全密钥, 则无法再使用它登录。是否确定? manage_account_links=管理绑定过的账号 manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。 @@ -650,7 +638,6 @@ issues.new.open_milestone=开启中的里程碑 issues.new.closed_milestone=已关闭的里程碑 issues.new.assignees=指派成员 issues.new.clear_assignees=取消指派成员 -issues.new.no_assignees=未指派成员 issues.no_ref=分支/标记未指定 issues.create=创建工单 issues.new_label=创建标签 @@ -1167,8 +1154,6 @@ branch.protected_deletion_failed=分支 '%s' 已被保护,不可删除。 topic.manage_topics=管理主题 topic.done=保存 -topic.count_prompt=您最多选择25个主题 -topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符 [org] org_name_holder=组织名称 @@ -1273,8 +1258,6 @@ dashboard.operation_switch=开关 dashboard.operation_run=执行 dashboard.clean_unbind_oauth=清理未绑定的 OAuth 连接 dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth 连接已被删除。 -dashboard.delete_inactivate_accounts=删除所有未激活的帐户 -dashboard.delete_inactivate_accounts_success=所有未激活的帐户都已删除。 dashboard.delete_repo_archives=删除所有仓库存档 dashboard.delete_repo_archives_success=所有仓库存档清除成功! dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库 @@ -1482,7 +1465,6 @@ config.db_path=数据库路径 config.service_config=服务配置 config.register_email_confirm=需要电子邮件确认注册 config.disable_register=禁止用户注册 -config.allow_only_external_registration=仅允许通过外部服务注册 config.enable_openid_signup=启用 OpenID 自注册 config.enable_openid_signin=启用 OpenID 登录 config.show_registration_button=显示注册按钮 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 7c0242f70..f26a4a6c8 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -32,16 +32,8 @@ twofa_scratch=兩步驟驗證備用碼 passcode=驗證碼 u2f_insert_key=插入安全金鑰 -u2f_sign_in=按下安全金鑰上的按鈕。如果找不到按鈕, 請重新插入。 u2f_press_button=請按下安全金鑰上的按鈕… 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=重新載入 repository=儲存庫 @@ -129,7 +121,6 @@ federated_avatar_lookup=開啟聯合大頭貼 federated_avatar_lookup_popup=開啟聯合頭像查詢並使用基於開放源碼的 libravatar 服務 disable_registration=關閉註冊功能 disable_registration_popup=關閉註冊功能,只有管理員可以新增帳號。 -allow_only_external_registration_popup=僅允許通過外部服務註冊。 openid_signin=啟用 OpenID 登入 openid_signin_popup=啟用 OpenID 登入 openid_signup=啟用 OpenID 註冊 @@ -432,13 +423,10 @@ or_enter_secret=或者輸入密碼: %s then_enter_passcode=然後輸入應用程序中顯示的驗證碼: passcode_invalid=無效的驗證碼,請重試。 -u2f_desc=安全密鑰是包含加密密鑰的硬體設備。 它們可以用於兩步驟認證。 安全密鑰必須符合 FIDO U2F 標準。 -u2f_require_twofa=必須先開啟兩步驟驗證才能使用安全密鑰。 u2f_register_key=新增安全密鑰 u2f_nickname=暱稱 u2f_press_button=按下安全密鑰上的密碼進行註冊。 u2f_delete_key=移除安全密鑰 -u2f_delete_key_desc=如果您移除安全密鑰,則無法再使用它登錄。 確定嗎? manage_account_links=管理已連結的帳號 manage_account_links_desc=這些外部帳號與您的 Gitea 帳號相關聯。 @@ -591,7 +579,6 @@ issues.new.open_milestone=開啟中的里程碑 issues.new.closed_milestone=已關閉的里程碑 issues.new.assignees=指派成員 issues.new.clear_assignees=取消指派成員 -issues.new.no_assignees=未指派成員 issues.no_ref=未指定分支或標籤 issues.create=建立問題 issues.new_label=建立標籤 @@ -1260,7 +1247,6 @@ config.db_path=資料庫路徑 config.service_config=服務設定 config.register_email_confirm=要求註冊時確認電子郵件 config.disable_register=關閉註冊功能 -config.allow_only_external_registration=僅允許通過外部服務註冊 config.enable_openid_signup=啟用 OpenID 註冊 config.enable_openid_signin=啟用 OpenID 登入 config.show_registration_button=顯示註冊按鈕 diff --git a/public/js/index.js b/public/js/index.js index 21a55d0e4..696b63e77 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1769,6 +1769,7 @@ $(document).ready(function () { initTopicbar(); initU2FAuth(); initU2FRegister(); + initIssueList(); // Repo clone url. 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 + ' ' + issue.title, + 'value' : issue.id + }); + }); + return filteredResponse; + }, + }, + + fullTextSearch: true + }) + ; +} diff --git a/public/vendor/librejs.html b/public/vendor/librejs.html index e170afa38..9b0363875 100644 --- a/public/vendor/librejs.html +++ b/public/vendor/librejs.html @@ -11,122 +11,122 @@ - jquery.are-you-sure.js + jquery.are-you-sure.js Expat jquery.areyousure-1.9.0.tar.gz - jquery.min.js + jquery.min.js Expat jquery-1.11.3.min.js - semantic.min.js + semantic.min.js Expat semantic-UI-2.3.1.tar.gz - index.js + index.js Expat index.js - draw.js + draw.js Expat draw.js - clipboard.min.js + clipboard.min.js Expat clipboard-1.5.9.tar.gz - gitgraph.js + gitgraph.js BSD 3-Clause gitgraph.js-latest - vue.min.js + vue.min.js Expat vue.js-v2.1.10.tar.gz - emojify.min.js + emojify.min.js Expat emojify-1.1.0.tar.gz - loadCSS.min.js + loadCSS.min.js Expat loadCSS-1.3.1.tar.gz - cssrelpreload.min.js + cssrelpreload.min.js Expat loadCSS-1.3.1.tar.gz - dropzone.js + dropzone.js Expat dropzone.js-4.2.0.tar.gz - highlight.pack.js + highlight.pack.js BSD 3-Clause highlight.js-9.6.0.tar.gz - jquery.datetimepicker.js + jquery.datetimepicker.js Expat jquery.datetimepicker-2.4.5.tar.gz - jquery.minicolors.min.js + jquery.minicolors.min.js Expat jquery.minicolors-2.2.3.tar.gz - loadmode.js + loadmode.js Expat codemirror-5.17.0.tar.gz - meta.js + meta.js Expat codemirror-5.17.0.tar.gz - simplemde.min.js + simplemde.min.js Expat simplemde-markdown-editor-1.10.1.tar.gz - pdf.js + pdf.js Apache-2.0-only pdf.js-v1.4.20.tar.gz - u2f-api + u2f-api Expat u2f-api-1.0.8.zip - font-awesome - fonts + font-awesome - fonts OFL font-awesome-4.6.0.zip - font-awesome - code + font-awesome - code Expat font-awesome-4.6.0.zip - octicons + octicons Expat octicons-v4.3.0.tar.gz - swagger-ui + swagger-ui Apache-2.0 swagger-ui-v3.0.4.tar.gz diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 76f58b244..f8ef0fe3d 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -7,6 +7,7 @@ package repo import ( "fmt" + "net/http" "strings" "code.gitea.io/gitea/models" @@ -208,6 +209,10 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { if form.Closed { 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) return } @@ -325,6 +330,10 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { } if form.State != 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) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 78d96c647..c346d81e3 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -6,6 +6,7 @@ package repo import ( "fmt" + "net/http" "strings" "code.gitea.io/git" @@ -378,6 +379,10 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { } if form.State != 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) return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 6f7dc4459..27c95cd9d 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "strconv" "strings" "time" @@ -302,6 +303,9 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models. } ctx.Data["Branches"] = brs + // Contains true if the user can create issue dependencies + ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User) + 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. participants[0] = issue.Poster for _, comment = range issue.Comments { @@ -721,6 +728,11 @@ func ViewIssue(ctx *context.Context) { ctx.ServerError("LoadAssigneeUser", err) 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) } + // Get Dependencies + ctx.Data["BlockedByDependencies"], err = issue.BlockedByDependencies() + ctx.Data["BlockingDependencies"], err = issue.BlockingDependencies() + ctx.Data["Participants"] = participants ctx.Data["NumParticipants"] = len(participants) ctx.Data["Issue"] = issue @@ -971,6 +987,12 @@ func UpdateIssueStatus(ctx *context.Context) { } for _, issue := range issues { 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) return } @@ -1034,6 +1056,17 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { } else { if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil { 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 { log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed) diff --git a/routers/repo/issue_dependency.go b/routers/repo/issue_dependency.go new file mode 100644 index 000000000..730271126 --- /dev/null +++ b/routers/repo/issue_dependency.go @@ -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 + } +} diff --git a/routers/repo/pull.go b/routers/repo/pull.go index dfe07ca44..c8d65731e 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -524,6 +524,18 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { pr.Issue = issue 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 models.IsErrInvalidMergeStyle(err) { ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 0d44cb50a..fa3bd434d 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -202,6 +202,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { Config: &models.IssuesConfig{ EnableTimetracker: form.EnableTimetracker, AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, + EnableDependencies: form.EnableIssueDependencies, }, }) } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index b3c42881c..8be16c815 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -527,6 +527,10 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) 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.Group("/times", func() { m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually) diff --git a/routers/user/auth.go b/routers/user/auth.go index 4852d47ae..9a24108c7 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -320,7 +321,7 @@ func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthFo handleSignInFull(ctx, u, remember, false) 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 } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 21aa8756f..c3d76687f 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -150,6 +150,8 @@ {{end}}
{{.i18n.Tr "admin.config.no_reply_address"}}
{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}
+
{{.i18n.Tr "admin.config.default_enable_dependencies"}}
+
{{.i18n.Tr "admin.config.active_code_lives"}}
{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index e4bbc1399..2ef96defa 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -72,7 +72,7 @@ {{.i18n.Tr "repo.branch.delete_html"}}
-

{{.i18n.Tr "repo.branch.delete_desc"}}

+

{{.i18n.Tr "repo.branch.delete_desc" | Str2html}}

{{template "base/delete_modal_actions" .}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 6edc34207..fa4b989ca 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -137,7 +137,7 @@ {{.i18n.Tr "repo.branch.delete" .HeadTarget }}
-

{{.i18n.Tr "repo.branch.delete_desc"}}

+

{{.i18n.Tr "repo.branch.delete_desc" | Str2html}}

{{template "base/delete_modal_actions" .}} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 4f185e4ae..6a8cd1dac 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -1,7 +1,7 @@ {{range .Issue.Comments}} {{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} - + {{if eq .Type 0}}
@@ -65,7 +65,6 @@ {{end}}
- {{else if eq .Type 1}}
@@ -233,5 +232,33 @@ {{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}}
- {{end}} + {{else if eq .Type 19}} +
+ + + + + + {{$.i18n.Tr "repo.issues.dependency.added_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}} + + +
+ {{else if eq .Type 20}} +
+ + + + + + {{$.i18n.Tr "repo.issues.dependency.removed_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}} + + +
+ {{end}} {{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 11552d08d..606ac303e 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -249,5 +249,142 @@ {{end}} + + {{if .Repository.IsDependenciesEnabled}} +
+ +
+ {{.i18n.Tr "repo.issues.dependency.title"}} +
+ {{if .BlockedByDependencies}} + + {{.i18n.Tr "repo.issues.dependency.blocked_by_short"}}: + +
+ {{range .BlockedByDependencies}} +
+
+ {{if $.CanCreateIssueDependencies}} + + + + {{end}} + {{if .IsClosed}} +
+ +
+ {{else}} +
+ +
+ {{end}} +
+
#{{.Index}}
+ {{.Title}} +
+ {{end}} +
+ {{end}} + + {{if .BlockingDependencies}} + + {{.i18n.Tr "repo.issues.dependency.blocks_short"}}: + +
+ {{range .BlockingDependencies}} +
+
+ {{if $.CanCreateIssueDependencies}} + + + + {{end}} + {{if .IsClosed}} +
+ +
+ {{else}} +
+ +
+ {{end}} +
+
#{{.Index}}
+ {{.Title}} +
+ {{end}} +
+ {{end}} + + {{if (and (not .BlockedByDependencies) (not .BlockingDependencies))}} +

{{if .Issue.IsPull}} + {{.i18n.Tr "repo.issues.dependency.pr_no_dependencies"}} + {{else}} + {{.i18n.Tr "repo.issues.dependency.issue_no_dependencies"}} + {{end}}

+ {{end}} + + {{if .CanCreateIssueDependencies}} +
+
+ {{$.CsrfTokenHtml}} +
+ + +
+
+
+ {{end}} +
+{{if .CanCreateIssueDependencies}} + + + + + +{{end}} + {{end}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index aa6a76dbf..744fa8206 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -153,6 +153,12 @@ {{end}} +
+
+ + +
+
diff --git a/vendor/github.com/go-xorm/builder/builder.go b/vendor/github.com/go-xorm/builder/builder.go index 1253b9887..80586ce4e 100644 --- a/vendor/github.com/go-xorm/builder/builder.go +++ b/vendor/github.com/go-xorm/builder/builder.go @@ -4,6 +4,10 @@ package builder +import ( + "fmt" +) + type optype byte const ( @@ -29,6 +33,9 @@ type Builder struct { joins []join inserts Eq updates []Eq + orderBy string + groupBy string + having string } // Select creates a select Builder @@ -67,6 +74,11 @@ func (b *Builder) From(tableName string) *Builder { return b } +// TableName returns the table name +func (b *Builder) TableName() string { + return b.tableName +} + // Into sets insert table name func (b *Builder) Into(tableName string) *Builder { b.tableName = tableName @@ -178,6 +190,33 @@ func (b *Builder) ToSQL() (string, []interface{}, error) { 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 func ToSQL(cond interface{}) (string, []interface{}, error) { switch cond.(type) { diff --git a/vendor/github.com/go-xorm/builder/builder_insert.go b/vendor/github.com/go-xorm/builder/builder_insert.go index decec9313..9b213ec73 100644 --- a/vendor/github.com/go-xorm/builder/builder_insert.go +++ b/vendor/github.com/go-xorm/builder/builder_insert.go @@ -15,7 +15,7 @@ func (b *Builder) insertWriteTo(w Writer) error { return errors.New("no table indicated") } 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 { @@ -26,7 +26,9 @@ func (b *Builder) insertWriteTo(w Writer) error { var bs []byte var valBuffer = bytes.NewBuffer(bs) var i = 0 - for col, value := range b.inserts { + + for _, col := range b.inserts.sortedKeys() { + value := b.inserts[col] fmt.Fprint(w, col) if e, ok := value.(expr); ok { fmt.Fprint(valBuffer, e.sql) diff --git a/vendor/github.com/go-xorm/builder/builder_select.go b/vendor/github.com/go-xorm/builder/builder_select.go index 3a3967ccc..deaacbd02 100644 --- a/vendor/github.com/go-xorm/builder/builder_select.go +++ b/vendor/github.com/go-xorm/builder/builder_select.go @@ -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 } 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 { return err } } - if !b.cond.IsValid() { - return nil + if b.cond.IsValid() { + 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 { - return err + if len(b.groupBy) > 0 { + 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 } diff --git a/vendor/github.com/go-xorm/builder/cond.go b/vendor/github.com/go-xorm/builder/cond.go index 77dd139bf..c0c2553de 100644 --- a/vendor/github.com/go-xorm/builder/cond.go +++ b/vendor/github.com/go-xorm/builder/cond.go @@ -5,7 +5,6 @@ package builder import ( - "bytes" "io" ) @@ -19,15 +18,15 @@ var _ Writer = NewWriter() // BytesWriter implments Writer and save SQL in bytes.Buffer type BytesWriter struct { - writer *bytes.Buffer - buffer []byte + writer *StringBuilder args []interface{} } // NewWriter creates a new string writer func NewWriter() *BytesWriter { - w := &BytesWriter{} - w.writer = bytes.NewBuffer(w.buffer) + w := &BytesWriter{ + writer: &StringBuilder{}, + } return w } diff --git a/vendor/github.com/go-xorm/builder/cond_compare.go b/vendor/github.com/go-xorm/builder/cond_compare.go index e10ef7447..1c293719c 100644 --- a/vendor/github.com/go-xorm/builder/cond_compare.go +++ b/vendor/github.com/go-xorm/builder/cond_compare.go @@ -10,7 +10,13 @@ import "fmt" func WriteMap(w Writer, data map[string]interface{}, op string) error { var args = make([]interface{}, 0, len(data)) 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) { case expr: if _, err := fmt.Fprintf(w, "%s%s(", k, op); err != nil { diff --git a/vendor/github.com/go-xorm/builder/cond_eq.go b/vendor/github.com/go-xorm/builder/cond_eq.go index 8777727ff..79d795e6d 100644 --- a/vendor/github.com/go-xorm/builder/cond_eq.go +++ b/vendor/github.com/go-xorm/builder/cond_eq.go @@ -4,7 +4,10 @@ package builder -import "fmt" +import ( + "fmt" + "sort" +) // Incr implements a type used by Eq type Incr int @@ -19,7 +22,8 @@ var _ Cond = Eq{} func (eq Eq) opWriteTo(op string, w Writer) error { var i = 0 - for k, v := range eq { + for _, k := range eq.sortedKeys() { + v := eq[k] switch v.(type) { case []int, []int64, []string, []int32, []int16, []int8, []uint, []uint64, []uint32, []uint16, []interface{}: 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 { 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 +} diff --git a/vendor/github.com/go-xorm/builder/cond_like.go b/vendor/github.com/go-xorm/builder/cond_like.go index 9291f12c9..e34202f8b 100644 --- a/vendor/github.com/go-xorm/builder/cond_like.go +++ b/vendor/github.com/go-xorm/builder/cond_like.go @@ -16,7 +16,7 @@ func (like Like) WriteTo(w Writer) error { if _, err := fmt.Fprintf(w, "%s LIKE ?", like[0]); err != nil { 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] == '%' { w.Append(like[1]) } else { diff --git a/vendor/github.com/go-xorm/builder/cond_neq.go b/vendor/github.com/go-xorm/builder/cond_neq.go index d07b2b18e..3a8f3136d 100644 --- a/vendor/github.com/go-xorm/builder/cond_neq.go +++ b/vendor/github.com/go-xorm/builder/cond_neq.go @@ -4,7 +4,10 @@ package builder -import "fmt" +import ( + "fmt" + "sort" +) // Neq defines not equal conditions type Neq map[string]interface{} @@ -15,7 +18,8 @@ var _ Cond = Neq{} func (neq Neq) WriteTo(w Writer) error { var args = make([]interface{}, 0, len(neq)) var i = 0 - for k, v := range neq { + for _, k := range neq.sortedKeys() { + v := neq[k] switch v.(type) { case []int, []int64, []string, []int32, []int16, []int8: 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 { 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 +} diff --git a/vendor/github.com/go-xorm/builder/cond_not.go b/vendor/github.com/go-xorm/builder/cond_not.go index 294a7e072..667dfe729 100644 --- a/vendor/github.com/go-xorm/builder/cond_not.go +++ b/vendor/github.com/go-xorm/builder/cond_not.go @@ -21,6 +21,18 @@ func (not Not) WriteTo(w Writer) error { if _, err := fmt.Fprint(w, "("); err != nil { 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 { @@ -32,6 +44,18 @@ func (not Not) WriteTo(w Writer) error { if _, err := fmt.Fprint(w, ")"); err != nil { 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 diff --git a/vendor/github.com/go-xorm/builder/strings_builder.go b/vendor/github.com/go-xorm/builder/strings_builder.go new file mode 100644 index 000000000..d4de8717e --- /dev/null +++ b/vendor/github.com/go-xorm/builder/strings_builder.go @@ -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 +} diff --git a/vendor/github.com/go-xorm/core/column.go b/vendor/github.com/go-xorm/core/column.go index 65370bb5b..20912b713 100644 --- a/vendor/github.com/go-xorm/core/column.go +++ b/vendor/github.com/go-xorm/core/column.go @@ -147,12 +147,12 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { } fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1]) } 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() { - 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 diff --git a/vendor/github.com/go-xorm/core/db.go b/vendor/github.com/go-xorm/core/db.go index 6111c4b33..9969fa431 100644 --- a/vendor/github.com/go-xorm/core/db.go +++ b/vendor/github.com/go-xorm/core/db.go @@ -7,6 +7,11 @@ import ( "fmt" "reflect" "regexp" + "sync" +) + +var ( + DefaultCacheSize = 200 ) 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 } +type cacheStruct struct { + value reflect.Value + idx int +} + type DB struct { *sql.DB - Mapper IMapper + Mapper IMapper + reflectCache map[reflect.Type]*cacheStruct + reflectCacheMutex sync.RWMutex } func Open(driverName, dataSourceName string) (*DB, error) { @@ -68,11 +80,32 @@ func Open(driverName, dataSourceName string) (*DB, error) { if err != nil { 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 { - 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) { @@ -83,7 +116,7 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { } return nil, err } - return &Rows{rows, db.Mapper}, nil + return &Rows{rows, db}, nil } 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 { *sql.Stmt - Mapper IMapper - names map[string]int + db *DB + names map[string]int } func (db *DB) Prepare(query string) (*Stmt, error) { @@ -145,7 +178,7 @@ func (db *DB) Prepare(query string) (*Stmt, error) { if err != nil { 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) { @@ -179,7 +212,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) { if err != nil { return nil, err } - return &Rows{rows, s.Mapper}, nil + return &Rows{rows, s.db}, nil } func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { @@ -274,7 +307,7 @@ func (EmptyScanner) Scan(src interface{}) error { type Tx struct { *sql.Tx - Mapper IMapper + db *DB } func (db *DB) Begin() (*Tx, error) { @@ -282,7 +315,7 @@ func (db *DB) Begin() (*Tx, error) { if err != nil { return nil, err } - return &Tx{tx, db.Mapper}, nil + return &Tx{tx, db}, nil } func (tx *Tx) Prepare(query string) (*Stmt, error) { @@ -298,7 +331,7 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) { if err != nil { return nil, err } - return &Stmt{stmt, tx.Mapper, names}, nil + return &Stmt{stmt, tx.db, names}, nil } func (tx *Tx) Stmt(stmt *Stmt) *Stmt { @@ -327,7 +360,7 @@ func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { if err != nil { 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) { diff --git a/vendor/github.com/go-xorm/core/dialect.go b/vendor/github.com/go-xorm/core/dialect.go index 6f2e81d01..c288a0847 100644 --- a/vendor/github.com/go-xorm/core/dialect.go +++ b/vendor/github.com/go-xorm/core/dialect.go @@ -74,6 +74,7 @@ type Dialect interface { GetIndexes(tableName string) (map[string]*Index, error) Filters() []Filter + SetParams(params map[string]string) } func OpenDialect(dialect Dialect) (*DB, error) { @@ -148,7 +149,8 @@ func (db *Base) SupportDropIfExists() bool { } 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) { @@ -289,6 +291,9 @@ func (b *Base) LogSQL(sql string, args []interface{}) { } } +func (b *Base) SetParams(params map[string]string) { +} + var ( dialects = map[string]func() Dialect{} ) diff --git a/vendor/github.com/go-xorm/core/filter.go b/vendor/github.com/go-xorm/core/filter.go index 60caaf290..35b0ece67 100644 --- a/vendor/github.com/go-xorm/core/filter.go +++ b/vendor/github.com/go-xorm/core/filter.go @@ -37,9 +37,9 @@ func (q *Quoter) Quote(content string) string { func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string { quoter := NewQuoter(dialect) if table != nil && len(table.PrimaryKeys) == 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) - return 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) + return strings.Replace(sql, " (id) ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) } return sql } diff --git a/vendor/github.com/go-xorm/core/index.go b/vendor/github.com/go-xorm/core/index.go index 73b95175a..9aa1b7ac9 100644 --- a/vendor/github.com/go-xorm/core/index.go +++ b/vendor/github.com/go-xorm/core/index.go @@ -22,6 +22,8 @@ type Index struct { func (index *Index) XName(tableName string) string { if !strings.HasPrefix(index.Name, "UQE_") && !strings.HasPrefix(index.Name, "IDX_") { + tableName = strings.Replace(tableName, `"`, "", -1) + tableName = strings.Replace(tableName, `.`, "_", -1) if index.Type == UniqueType { return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) } diff --git a/vendor/github.com/go-xorm/core/rows.go b/vendor/github.com/go-xorm/core/rows.go index 4a4acaa4c..580de4f9c 100644 --- a/vendor/github.com/go-xorm/core/rows.go +++ b/vendor/github.com/go-xorm/core/rows.go @@ -9,7 +9,7 @@ import ( type Rows struct { *sql.Rows - Mapper IMapper + db *DB } func (rs *Rows) ToMapString() ([]map[string]string, error) { @@ -105,7 +105,7 @@ func (rs *Rows) ScanStructByName(dest interface{}) error { newDest := make([]interface{}, len(cols)) var v EmptyScanner 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() { newDest[j] = f.Addr().Interface() } else { @@ -116,36 +116,6 @@ func (rs *Rows) ScanStructByName(dest interface{}) error { 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 func (rs *Rows) ScanSlice(dest interface{}) error { vv := reflect.ValueOf(dest) @@ -197,9 +167,7 @@ func (rs *Rows) ScanMap(dest interface{}) error { vvv := vv.Elem() for i, _ := range cols { - newDest[i] = ReflectNew(vvv.Type().Elem()).Interface() - //v := reflect.New(vvv.Type().Elem()) - //newDest[i] = v.Interface() + newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface() } err = rs.Rows.Scan(newDest...) @@ -215,32 +183,6 @@ func (rs *Rows) ScanMap(dest interface{}) error { 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 { rows *Rows // One of these two will be non-nil: diff --git a/vendor/github.com/go-xorm/core/table.go b/vendor/github.com/go-xorm/core/table.go index 88199bedd..b5d079404 100644 --- a/vendor/github.com/go-xorm/core/table.go +++ b/vendor/github.com/go-xorm/core/table.go @@ -49,7 +49,6 @@ func NewTable(name string, t reflect.Type) *Table { } func (table *Table) columnsByName(name string) []*Column { - n := len(name) 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 { - cols := table.columnsByName(name) if cols != nil && idx < len(cols) { diff --git a/vendor/github.com/go-xorm/core/type.go b/vendor/github.com/go-xorm/core/type.go index 8010a2220..5cbf93057 100644 --- a/vendor/github.com/go-xorm/core/type.go +++ b/vendor/github.com/go-xorm/core/type.go @@ -69,15 +69,18 @@ var ( Enum = "ENUM" Set = "SET" - Char = "CHAR" - Varchar = "VARCHAR" - NVarchar = "NVARCHAR" - TinyText = "TINYTEXT" - Text = "TEXT" - Clob = "CLOB" - MediumText = "MEDIUMTEXT" - LongText = "LONGTEXT" - Uuid = "UUID" + Char = "CHAR" + Varchar = "VARCHAR" + NVarchar = "NVARCHAR" + TinyText = "TINYTEXT" + Text = "TEXT" + NText = "NTEXT" + Clob = "CLOB" + MediumText = "MEDIUMTEXT" + LongText = "LONGTEXT" + Uuid = "UUID" + UniqueIdentifier = "UNIQUEIDENTIFIER" + SysName = "SYSNAME" Date = "DATE" DateTime = "DATETIME" @@ -128,10 +131,12 @@ var ( NVarchar: TEXT_TYPE, TinyText: TEXT_TYPE, Text: TEXT_TYPE, + NText: TEXT_TYPE, MediumText: TEXT_TYPE, LongText: TEXT_TYPE, Uuid: TEXT_TYPE, Clob: TEXT_TYPE, + SysName: TEXT_TYPE, Date: TIME_TYPE, DateTime: TIME_TYPE, @@ -148,11 +153,12 @@ var ( Binary: BLOB_TYPE, VarBinary: BLOB_TYPE, - TinyBlob: BLOB_TYPE, - Blob: BLOB_TYPE, - MediumBlob: BLOB_TYPE, - LongBlob: BLOB_TYPE, - Bytea: BLOB_TYPE, + TinyBlob: BLOB_TYPE, + Blob: BLOB_TYPE, + MediumBlob: BLOB_TYPE, + LongBlob: BLOB_TYPE, + Bytea: BLOB_TYPE, + UniqueIdentifier: BLOB_TYPE, Bool: NUMERIC_TYPE, @@ -289,9 +295,9 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: 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("") - case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: + case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier: return reflect.TypeOf([]byte{}) case Bool: return reflect.TypeOf(true) diff --git a/vendor/github.com/go-xorm/xorm/dialect_mysql.go b/vendor/github.com/go-xorm/xorm/dialect_mysql.go index 99100b232..f2b4ff7a7 100644 --- a/vendor/github.com/go-xorm/xorm/dialect_mysql.go +++ b/vendor/github.com/go-xorm/xorm/dialect_mysql.go @@ -172,12 +172,33 @@ type mysql struct { allowAllFiles bool allowOldPasswords bool clientFoundRows bool + rowFormat string } func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { 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 { var res string switch t := c.SQLType.Name; t { @@ -487,6 +508,59 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { 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 { return []core.Filter{&core.IdFilter{}} } diff --git a/vendor/github.com/go-xorm/xorm/dialect_postgres.go b/vendor/github.com/go-xorm/xorm/dialect_postgres.go index d6f71acc1..1f74bd312 100644 --- a/vendor/github.com/go-xorm/xorm/dialect_postgres.go +++ b/vendor/github.com/go-xorm/xorm/dialect_postgres.go @@ -769,14 +769,21 @@ var ( DefaultPostgresSchema = "public" ) +const postgresPublicSchema = "public" + type postgres struct { core.Base - schema string } func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { - db.schema = DefaultPostgresSchema - return db.Base.Init(d, db, uri, drivername, dataSourceName) + err := 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 { @@ -873,32 +880,42 @@ func (db *postgres) IndexOnTable() bool { } 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 ` + - `WHERE tablename = ? AND indexname = ?`, args + `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args } func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args + if len(db.Schema) == 0 { + 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 { - return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", - tableName, col.Name, db.SqlType(col)) + if len(db.Schema) == 0 { + 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 { - //var unique string quote := db.Quote idxName := index.Name + tableName = strings.Replace(tableName, `"`, "", -1) + tableName = strings.Replace(tableName, `.`, "_", -1) + if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { 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) } } + if db.Uri.Schema != "" { + idxName = db.Uri.Schema + "." + idxName + } return fmt.Sprintf("DROP INDEX %v", quote(idxName)) } func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { - args := []interface{}{tableName, colName} - query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + - " AND column_name = $2" + args := []interface{}{db.Schema, tableName, colName} + query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_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) 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) { - // FIXME: the schema should be replaced by user custom's - args := []interface{}{tableName, db.schema} + args := []interface{}{tableName} 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 = '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_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 -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) 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) { - args := []interface{}{db.schema} - s := fmt.Sprintf("SELECT tablename FROM pg_tables WHERE schemaname = $1") + args := []interface{}{} + 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) 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) { - args := []interface{}{db.schema, tableName} - s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE schemaname=$1 AND tablename=$2") + args := []interface{}{tableName} + 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) rows, err := db.DB().Query(s, args...) @@ -1182,3 +1223,15 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { 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) +} diff --git a/vendor/github.com/go-xorm/xorm/engine.go b/vendor/github.com/go-xorm/xorm/engine.go index 444611afb..846889c36 100644 --- a/vendor/github.com/go-xorm/xorm/engine.go +++ b/vendor/github.com/go-xorm/xorm/engine.go @@ -49,6 +49,35 @@ type Engine struct { tagHandlers map[string]tagHandler 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 @@ -165,7 +194,7 @@ func (engine *Engine) Quote(value string) string { } // 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 { return } @@ -245,13 +274,7 @@ func (engine *Engine) NoCascade() *Session { // MapCacher Set a table use a special cacher func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error { - v := rValue(bean) - tb, err := engine.autoMapType(v) - if err != nil { - return err - } - - tb.Cacher = cacher + engine.setCacher(engine.TableName(bean, true), cacher) return nil } @@ -536,33 +559,6 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D 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 func (engine *Engine) Cascade(trueOrFalse ...bool) *Session { session := engine.NewSession() @@ -846,7 +842,7 @@ func (engine *Engine) TableInfo(bean interface{}) *Table { if err != nil { 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) { @@ -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 type TableName interface { TableName() string @@ -881,21 +868,9 @@ var ( func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { t := v.Type() - table := engine.newTable() - 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 := core.NewEmptyTable() table.Type = t + table.Name = engine.tbNameForMap(v) var idFieldColName string var hasCacheTag, hasNoCacheTag bool @@ -1049,15 +1024,15 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { if hasCacheTag { if engine.Cacher != nil { // !nash! use engine's cacher if provided engine.logger.Info("enable cache on table:", table.Name) - table.Cacher = engine.Cacher + engine.setCacher(table.Name, engine.Cacher) } else { 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 { - engine.logger.Info("no cache on table:", table.Name) - table.Cacher = nil + engine.logger.Info("disable cache on table:", table.Name) + engine.setCacher(table.Name, nil) } return table, nil @@ -1116,7 +1091,25 @@ func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) { pk := make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { 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() { case reflect.String: pk[i], err = engine.idTypeAssertion(col, pkField.String()) @@ -1162,26 +1155,10 @@ func (engine *Engine) CreateUniques(bean interface{}) error { return session.CreateUniques(bean) } -func (engine *Engine) getCacher2(table *core.Table) core.Cacher { - return table.Cacher -} - // ClearCacheBean if enabled cache, clear the cache bean func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { - v := rValue(bean) - t := v.Type() - 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 - } + tableName := engine.TableName(bean) + cacher := engine.getCacher(tableName) if cacher != nil { cacher.ClearIds(tableName) 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 func (engine *Engine) ClearCache(beans ...interface{}) error { for _, bean := range beans { - v := rValue(bean) - t := v.Type() - 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 - } + tableName := engine.TableName(bean) + cacher := engine.getCacher(tableName) if cacher != nil { cacher.ClearIds(tableName) cacher.ClearBeans(tableName) @@ -1224,13 +1188,13 @@ func (engine *Engine) Sync(beans ...interface{}) error { for _, bean := range beans { v := rValue(bean) - tableName := engine.tbName(v) + tableNameNoSchema := engine.TableName(bean) table, err := engine.autoMapType(v) if err != nil { return err } - isExist, err := session.Table(bean).isTableExist(tableName) + isExist, err := session.Table(bean).isTableExist(tableNameNoSchema) if err != nil { return err } @@ -1256,12 +1220,12 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } else { for _, col := range table.Columns() { - isExist, err := engine.dialect.IsColumnExist(tableName, col.Name) + isExist, err := engine.dialect.IsColumnExist(tableNameNoSchema, col.Name) if err != nil { return err } if !isExist { - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } err = session.addColumn(col.Name) @@ -1272,35 +1236,35 @@ func (engine *Engine) Sync(beans ...interface{}) error { } for name, index := range table.Indexes { - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } if index.Type == core.UniqueType { - isExist, err := session.isIndexExist2(tableName, index.Cols, true) + isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true) if err != nil { return err } if !isExist { - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } - err = session.addUnique(tableName, name) + err = session.addUnique(tableNameNoSchema, name) if err != nil { return err } } } 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 { return err } if !isExist { - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } - err = session.addIndex(tableName, name) + err = session.addIndex(tableNameNoSchema, name) if err != nil { return err } @@ -1453,6 +1417,13 @@ func (engine *Engine) Find(beans interface{}, condiBeans ...interface{}) error { 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 // are conditions. func (engine *Engine) Iterate(bean interface{}, fun IterFunc) error { @@ -1629,6 +1600,11 @@ func (engine *Engine) SetTZDatabase(tz *time.Location) { 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" func (engine *Engine) Unscoped() *Session { session := engine.NewSession() diff --git a/vendor/github.com/go-xorm/xorm/engine_cond.go b/vendor/github.com/go-xorm/xorm/engine_cond.go index 6c8e3879c..4dde8662e 100644 --- a/vendor/github.com/go-xorm/xorm/engine_cond.go +++ b/vendor/github.com/go-xorm/xorm/engine_cond.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "reflect" + "strings" "time" "github.com/go-xorm/builder" @@ -51,7 +52,9 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, fieldValuePtr, err := col.ValueOf(bean) if err != nil { - engine.logger.Error(err) + if !strings.Contains(err.Error(), "is not valid") { + engine.logger.Warn(err) + } continue } diff --git a/vendor/github.com/go-xorm/xorm/engine_table.go b/vendor/github.com/go-xorm/xorm/engine_table.go new file mode 100644 index 000000000..94871a4bc --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/engine_table.go @@ -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 "" +} diff --git a/vendor/github.com/go-xorm/xorm/error.go b/vendor/github.com/go-xorm/xorm/error.go index cfeefc31e..a223fc4a8 100644 --- a/vendor/github.com/go-xorm/xorm/error.go +++ b/vendor/github.com/go-xorm/xorm/error.go @@ -6,23 +6,44 @@ package xorm import ( "errors" + "fmt" ) var ( // ErrParamsType params error ErrParamsType = errors.New("Params type error") // ErrTableNotFound table not found error - ErrTableNotFound = errors.New("Not found table") + ErrTableNotFound = errors.New("Table not found") // ErrUnSupportedType unsupported error ErrUnSupportedType = errors.New("Unsupported type error") - // ErrNotExist record is not exist error - ErrNotExist = errors.New("Not exist error") + // ErrNotExist record does not exist error + ErrNotExist = errors.New("Record does not exist") // ErrCacheFailed cache failed error ErrCacheFailed = errors.New("Cache failed") // 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 = errors.New("Not implemented") // 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) +} diff --git a/vendor/github.com/go-xorm/xorm/helpers.go b/vendor/github.com/go-xorm/xorm/helpers.go index f39ed4725..f1705782e 100644 --- a/vendor/github.com/go-xorm/xorm/helpers.go +++ b/vendor/github.com/go-xorm/xorm/helpers.go @@ -11,7 +11,6 @@ import ( "sort" "strconv" "strings" - "time" "github.com/go-xorm/core" ) @@ -293,19 +292,6 @@ func structName(v reflect.Type) string { 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 { if len(left) != len(right) { return false @@ -320,154 +306,6 @@ func sliceEq(left, right []string) bool { 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 { 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 -} diff --git a/vendor/github.com/go-xorm/xorm/interface.go b/vendor/github.com/go-xorm/xorm/interface.go index 9a3b6da0b..0bc12ba00 100644 --- a/vendor/github.com/go-xorm/xorm/interface.go +++ b/vendor/github.com/go-xorm/xorm/interface.go @@ -30,6 +30,7 @@ type Interface interface { Exec(string, ...interface{}) (sql.Result, error) Exist(bean ...interface{}) (bool, error) Find(interface{}, ...interface{}) error + FindAndCount(interface{}, ...interface{}) (int64, error) Get(interface{}) (bool, error) GroupBy(keys string) *Session ID(interface{}) *Session @@ -41,6 +42,7 @@ type Interface interface { IsTableExist(beanOrTableName interface{}) (bool, error) Iterate(interface{}, IterFunc) error Limit(int, ...int) *Session + MustCols(columns ...string) *Session NoAutoCondition(...bool) *Session NotIn(string, ...interface{}) *Session Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session @@ -75,6 +77,7 @@ type EngineInterface interface { Dialect() core.Dialect DropTables(...interface{}) error DumpAllToFile(fp string, tp ...core.DbType) error + GetCacher(string) core.Cacher GetColumnMapper() core.IMapper GetDefaultCacher() core.Cacher GetTableMapper() core.IMapper @@ -83,9 +86,11 @@ type EngineInterface interface { NewSession() *Session NoAutoTime() *Session Quote(string) string + SetCacher(string, core.Cacher) SetDefaultCacher(core.Cacher) SetLogLevel(core.LogLevel) SetMapper(core.IMapper) + SetSchema(string) SetTZDatabase(tz *time.Location) SetTZLocation(tz *time.Location) ShowSQL(show ...bool) @@ -93,6 +98,7 @@ type EngineInterface interface { Sync2(...interface{}) error StoreEngine(storeEngine string) *Session TableInfo(bean interface{}) *Table + TableName(interface{}, ...bool) string UnMapType(reflect.Type) } diff --git a/vendor/github.com/go-xorm/xorm/rows.go b/vendor/github.com/go-xorm/xorm/rows.go index 31e29ae26..54ec7f37a 100644 --- a/vendor/github.com/go-xorm/xorm/rows.go +++ b/vendor/github.com/go-xorm/xorm/rows.go @@ -32,7 +32,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { var args []interface{} 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 } @@ -94,8 +94,7 @@ func (rows *Rows) Scan(bean interface{}) error { return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) } - dataStruct := rValue(bean) - if err := rows.session.statement.setRefValue(dataStruct); err != nil { + if err := rows.session.statement.setRefBean(bean); err != nil { return err } @@ -104,6 +103,7 @@ func (rows *Rows) Scan(bean interface{}) error { return err } + dataStruct := rValue(bean) _, err = rows.session.slice2Bean(scanResults, rows.fields, bean, &dataStruct, rows.session.statement.RefTable) if err != nil { return err diff --git a/vendor/github.com/go-xorm/xorm/session.go b/vendor/github.com/go-xorm/xorm/session.go index 5c6cb5f9d..3775eb011 100644 --- a/vendor/github.com/go-xorm/xorm/session.go +++ b/vendor/github.com/go-xorm/xorm/session.go @@ -278,24 +278,22 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, 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 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 + return nil, ErrFieldIsNotExist{key, table.Name} } fieldValue, err := col.ValueOfV(dataStruct) if err != nil { - session.engine.logger.Error(err) - return nil + return nil, err } 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 + return nil, ErrFieldIsNotValid{key, table.Name} } - return fieldValue + + return fieldValue, nil } // 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 - if fieldValue := session.getField(dataStruct, key, table, idx); fieldValue != nil { - rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) - - // if row is null then ignore - if rawValue.Interface() == nil { - continue + fieldValue, err := session.getField(dataStruct, key, table, idx) + if err != nil { + if !strings.Contains(err.Error(), "is not valid") { + session.engine.logger.Warn(err) } + continue + } + if fieldValue == nil { + continue + } + rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) - if fieldValue.CanAddr() { - 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 - } - } else { + // if row is null then ignore + if rawValue.Interface() == nil { + continue + } + + if fieldValue.CanAddr() { + 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 } - 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 { return nil, err } continue } + } - rawValueType := reflect.TypeOf(rawValue.Interface()) - vv := reflect.ValueOf(rawValue.Interface()) - col := table.GetColumnIdx(key, idx) - if col.IsPrimaryKey { - pk = append(pk, rawValue.Interface()) + 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 { + return nil, err } - fieldType := fieldValue.Type() - hasAssigned := false + continue + } - 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() + rawValueType := reflect.TypeOf(rawValue.Interface()) + vv := reflect.ValueOf(rawValue.Interface()) + col := table.GetColumnIdx(key, idx) + if col.IsPrimaryKey { + pk = append(pk, rawValue.Interface()) + } + 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 { - 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 { - x := reflect.New(fieldType) - err := json.Unmarshal(bs, x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) + x := reflect.New(fieldType) + err := json.Unmarshal(bs, x.Interface()) + if err != nil { + return nil, err } + fieldValue.Set(x.Elem()) } - - continue } - switch fieldType.Kind() { - 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() - } + continue + } - hasAssigned = true - if len(bs) > 0 { - 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()) + switch fieldType.Kind() { + 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 + if len(bs) > 0 { + 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()) } + } + case reflect.Slice, reflect.Array: + switch rawValueType.Kind() { case reflect.Slice, reflect.Array: - switch rawValueType.Kind() { - case reflect.Slice, reflect.Array: - switch rawValueType.Elem().Kind() { - 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 { + switch rawValueType.Elem().Kind() { + case reflect.Uint8: + if fieldType.Elem().Kind() == reflect.Uint8 { hasAssigned = true - - 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 { - // !! 增加支持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 { + 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 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 { - 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 - 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() + } + 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 + } - // !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value - if !hasAssigned { - data, err := value2Bytes(&rawValue) + if rawValueType == core.TimeType { + hasAssigned = true + + 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 { + // !! 增加支持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 { 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 } + + 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 } -// 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" func (session *Session) Unscoped() *Session { session.statement.Unscoped() diff --git a/vendor/github.com/go-xorm/xorm/session_cols.go b/vendor/github.com/go-xorm/xorm/session_cols.go index 9972cb0ae..47d109c6c 100644 --- a/vendor/github.com/go-xorm/xorm/session_cols.go +++ b/vendor/github.com/go-xorm/xorm/session_cols.go @@ -4,6 +4,121 @@ 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" func (session *Session) Incr(column string, arg ...interface{}) *Session { session.statement.Incr(column, arg...) diff --git a/vendor/github.com/go-xorm/xorm/session_delete.go b/vendor/github.com/go-xorm/xorm/session_delete.go index 688b122ca..d9cf3ea93 100644 --- a/vendor/github.com/go-xorm/xorm/session_delete.go +++ b/vendor/github.com/go-xorm/xorm/session_delete.go @@ -27,7 +27,7 @@ func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, return ErrCacheFailed } - cacher := session.engine.getCacher2(table) + cacher := session.engine.getCacher(tableName) pkColumns := table.PKColumns() ids, err := core.GetCacheSql(cacher, tableName, newsql, args) if err != nil { @@ -79,7 +79,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { defer session.Close() } - if err := session.statement.setRefValue(rValue(bean)); err != nil { + if err := session.statement.setRefBean(bean); err != nil { 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...) } diff --git a/vendor/github.com/go-xorm/xorm/session_exist.go b/vendor/github.com/go-xorm/xorm/session_exist.go index 378a64837..74a660e85 100644 --- a/vendor/github.com/go-xorm/xorm/session_exist.go +++ b/vendor/github.com/go-xorm/xorm/session_exist.go @@ -57,7 +57,7 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { } 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 } } diff --git a/vendor/github.com/go-xorm/xorm/session_find.go b/vendor/github.com/go-xorm/xorm/session_find.go index f95dcfef2..46bbf26c9 100644 --- a/vendor/github.com/go-xorm/xorm/session_find.go +++ b/vendor/github.com/go-xorm/xorm/session_find.go @@ -29,6 +29,39 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) 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 { sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) 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.Elem().Kind() == reflect.Struct { pv := reflect.New(sliceElementType.Elem()) - if err := session.statement.setRefValue(pv.Elem()); err != nil { + if err := session.statement.setRefValue(pv); err != nil { return err } } else { @@ -50,7 +83,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } } else if sliceElementType.Kind() == reflect.Struct { pv := reflect.New(sliceElementType) - if err := session.statement.setRefValue(pv.Elem()); err != nil { + if err := session.statement.setRefValue(pv); err != nil { return err } } else { @@ -128,7 +161,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } 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 { return err } @@ -143,7 +176,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } 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.unscoped { err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) @@ -288,6 +321,12 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in return ErrCacheFailed } + tableName := session.statement.TableName() + cacher := session.engine.getCacher(tableName) + if cacher == nil { + return nil + } + for _, filter := range session.engine.dialect.Filters() { 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 } - tableName := session.statement.TableName() table := session.statement.RefTable - cacher := session.engine.getCacher2(table) ids, err := core.GetCacheSql(cacher, tableName, newsql, args) if err != nil { rows, err := session.queryRows(newsql, args...) diff --git a/vendor/github.com/go-xorm/xorm/session_get.go b/vendor/github.com/go-xorm/xorm/session_get.go index 68b37af7f..3b2c9493c 100644 --- a/vendor/github.com/go-xorm/xorm/session_get.go +++ b/vendor/github.com/go-xorm/xorm/session_get.go @@ -31,7 +31,7 @@ func (session *Session) get(bean interface{}) (bool, error) { } 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 } } @@ -57,7 +57,7 @@ func (session *Session) get(bean interface{}) (bool, error) { table := session.statement.RefTable 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 { has, err := session.cacheGet(bean, sqlStr, args...) if err != ErrCacheFailed { @@ -134,8 +134,9 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf return false, ErrCacheFailed } - cacher := session.engine.getCacher2(session.statement.RefTable) tableName := session.statement.TableName() + cacher := session.engine.getCacher(tableName) + session.engine.logger.Debug("[cacheGet] find sql:", newsql, args) table := session.statement.RefTable ids, err := core.GetCacheSql(cacher, tableName, newsql, args) diff --git a/vendor/github.com/go-xorm/xorm/session_insert.go b/vendor/github.com/go-xorm/xorm/session_insert.go index 129ee2309..2ea58fdaf 100644 --- a/vendor/github.com/go-xorm/xorm/session_insert.go +++ b/vendor/github.com/go-xorm/xorm/session_insert.go @@ -66,11 +66,12 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error 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 } - if len(session.statement.TableName()) <= 0 { + tableName := session.statement.TableName() + if len(tableName) <= 0 { return 0, ErrTableNotFound } @@ -115,15 +116,11 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsDeleted { continue } - if session.statement.ColumnStr != "" { - if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok { - continue - } + if session.statement.omitColumnMap.contain(col.Name) { + continue } - if session.statement.OmitStr != "" { - if _, ok := getFlagForColumn(session.statement.columnMap, col); ok { - continue - } + if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) { + continue } if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { val, t := session.engine.nowTime(col) @@ -170,15 +167,11 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsDeleted { continue } - if session.statement.ColumnStr != "" { - if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok { - continue - } + if session.statement.omitColumnMap.contain(col.Name) { + continue } - if session.statement.OmitStr != "" { - if _, ok := getFlagForColumn(session.statement.columnMap, col); ok { - continue - } + if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) { + continue } if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { val, t := session.engine.nowTime(col) @@ -211,38 +204,33 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error } cleanupProcessorsClosures(&session.beforeClosures) - var sql = "INSERT INTO %s (%v%v%v) VALUES (%v)" - var statement string - var tableName = session.statement.TableName() + var sql string 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 (", session.engine.Quote(tableName), session.engine.QuoteStr(), strings.Join(colNames, 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.QuoteStr(), strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()), session.engine.QuoteStr(), strings.Join(colMultiPlaces, temp)) } else { - statement = fmt.Sprintf(sql, + sql = fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", session.engine.Quote(tableName), session.engine.QuoteStr(), strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()), session.engine.QuoteStr(), strings.Join(colMultiPlaces, "),(")) } - res, err := session.exec(statement, args...) + res, err := session.exec(sql, args...) if err != nil { return 0, err } - if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { - session.cacheInsert(table, tableName) - } + session.cacheInsert(tableName) lenAfterClosures := len(session.afterClosures) 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) { - if err := session.statement.setRefValue(rValue(bean)); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return 0, err } 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 { processor.BeforeInsert() } - // -- - colNames, args, err := genCols(session.statement.RefTable, session, bean, false, false) + + colNames, args, err := session.genInsertColumns(bean) if err != nil { return 0, err } @@ -402,9 +390,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { defer handleAfterInsertProcessorFunc(bean) - if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { - session.cacheInsert(table, tableName) - } + session.cacheInsert(tableName) if table.Version != "" && session.statement.checkVersion { verValue, err := table.VersionColumn().ValueOf(bean) @@ -447,9 +433,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } defer handleAfterInsertProcessorFunc(bean) - if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { - session.cacheInsert(table, tableName) - } + session.cacheInsert(tableName) if table.Version != "" && session.statement.checkVersion { verValue, err := table.VersionColumn().ValueOf(bean) @@ -490,9 +474,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { defer handleAfterInsertProcessorFunc(bean) - if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { - session.cacheInsert(table, tableName) - } + session.cacheInsert(tableName) if table.Version != "" && session.statement.checkVersion { verValue, err := table.VersionColumn().ValueOf(bean) @@ -539,16 +521,104 @@ func (session *Session) InsertOne(bean interface{}) (int64, error) { return session.innerInsert(bean) } -func (session *Session) cacheInsert(table *core.Table, tables ...string) error { - if table == nil { - return ErrCacheFailed +func (session *Session) cacheInsert(table string) error { + if !session.statement.UseCache { + return nil } - - cacher := session.engine.getCacher2(table) - for _, t := range tables { - session.engine.logger.Debug("[cache] clear sql:", t) - cacher.ClearIds(t) + cacher := session.engine.getCacher(table) + if cacher == nil { + return nil } - + session.engine.logger.Debug("[cache] clear sql:", table) + cacher.ClearIds(table) 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 +} diff --git a/vendor/github.com/go-xorm/xorm/session_query.go b/vendor/github.com/go-xorm/xorm/session_query.go index f8098f849..5c9aeb391 100644 --- a/vendor/github.com/go-xorm/xorm/session_query.go +++ b/vendor/github.com/go-xorm/xorm/session_query.go @@ -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) if err != nil { return "", nil, err } 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 { return "", nil, err } diff --git a/vendor/github.com/go-xorm/xorm/session_schema.go b/vendor/github.com/go-xorm/xorm/session_schema.go index a2708b736..369ec72a4 100644 --- a/vendor/github.com/go-xorm/xorm/session_schema.go +++ b/vendor/github.com/go-xorm/xorm/session_schema.go @@ -6,9 +6,7 @@ package xorm import ( "database/sql" - "errors" "fmt" - "reflect" "strings" "github.com/go-xorm/core" @@ -34,8 +32,7 @@ func (session *Session) CreateTable(bean interface{}) error { } func (session *Session) createTable(bean interface{}) error { - v := rValue(bean) - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } @@ -54,8 +51,7 @@ func (session *Session) CreateIndexes(bean interface{}) error { } func (session *Session) createIndexes(bean interface{}) error { - v := rValue(bean) - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } @@ -78,8 +74,7 @@ func (session *Session) CreateUniques(bean interface{}) error { } func (session *Session) createUniques(bean interface{}) error { - v := rValue(bean) - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } @@ -103,8 +98,7 @@ func (session *Session) DropIndexes(bean interface{}) error { } func (session *Session) dropIndexes(bean interface{}) error { - v := rValue(bean) - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return err } @@ -128,11 +122,7 @@ func (session *Session) DropTable(beanOrTableName interface{}) error { } func (session *Session) dropTable(beanOrTableName interface{}) error { - tableName, err := session.engine.tableName(beanOrTableName) - if err != nil { - return err - } - + tableName := session.engine.TableName(beanOrTableName) var needDrop = true if !session.engine.dialect.SupportDropIfExists() { sqlStr, args := session.engine.dialect.TableCheckSql(tableName) @@ -144,8 +134,8 @@ func (session *Session) dropTable(beanOrTableName interface{}) error { } if needDrop { - sqlStr := session.engine.Dialect().DropTableSql(tableName) - _, err = session.exec(sqlStr) + sqlStr := session.engine.Dialect().DropTableSql(session.engine.TableName(tableName, true)) + _, err := session.exec(sqlStr) return err } return nil @@ -157,10 +147,7 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) defer session.Close() } - tableName, err := session.engine.tableName(beanOrTableName) - if err != nil { - return false, err - } + tableName := session.engine.TableName(beanOrTableName) return session.isTableExist(tableName) } @@ -173,24 +160,15 @@ func (session *Session) isTableExist(tableName string) (bool, error) { // IsTableEmpty if table have any records func (session *Session) IsTableEmpty(bean interface{}) (bool, error) { - v := rValue(bean) - t := v.Type() - - 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 + if session.isAutoClose { + defer session.Close() } - 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) { 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) if err != nil { if err == sql.ErrNoRows { @@ -255,6 +233,12 @@ func (session *Session) Sync2(beans ...interface{}) error { return err } + session.autoResetStatement = false + defer func() { + session.autoResetStatement = true + session.resetStatement() + }() + var structTables []*core.Table for _, bean := range beans { @@ -264,7 +248,8 @@ func (session *Session) Sync2(beans ...interface{}) error { return err } structTables = append(structTables, table) - var tbName = session.tbNameNoSchema(table) + tbName := engine.TableName(bean) + tbNameWithSchema := engine.TableName(tbName, true) var oriTable *core.Table for _, tb := range tables { @@ -309,32 +294,32 @@ func (session *Session) Sync2(beans ...interface{}) error { if engine.dialect.DBType() == core.MYSQL || engine.dialect.DBType() == core.POSTGRES { engine.logger.Infof("Table %s column %s change type from %s to %s\n", - tbName, col.Name, curType, expectedType) - _, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col)) + tbNameWithSchema, col.Name, curType, expectedType) + _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) } else { 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) { if engine.dialect.DBType() == core.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", - tbName, col.Name, oriCol.Length, col.Length) - _, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col)) + tbNameWithSchema, col.Name, oriCol.Length, col.Length) + _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) } } } else { if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') { 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 { if engine.dialect.DBType() == core.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", - tbName, col.Name, oriCol.Length, col.Length) - _, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col)) + tbNameWithSchema, col.Name, oriCol.Length, col.Length) + _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) } } } @@ -348,7 +333,7 @@ func (session *Session) Sync2(beans ...interface{}) error { } } else { session.statement.RefTable = table - session.statement.tableName = tbName + session.statement.tableName = tbNameWithSchema err = session.addColumn(col.Name) } if err != nil { @@ -371,7 +356,7 @@ func (session *Session) Sync2(beans ...interface{}) error { if oriIndex != nil { if oriIndex.Type != index.Type { - sql := engine.dialect.DropIndexSql(tbName, oriIndex) + sql := engine.dialect.DropIndexSql(tbNameWithSchema, oriIndex) _, err = session.exec(sql) if err != nil { return err @@ -387,7 +372,7 @@ func (session *Session) Sync2(beans ...interface{}) error { for name2, index2 := range oriTable.Indexes { if _, ok := foundIndexNames[name2]; !ok { - sql := engine.dialect.DropIndexSql(tbName, index2) + sql := engine.dialect.DropIndexSql(tbNameWithSchema, index2) _, err = session.exec(sql) if err != nil { return err @@ -398,12 +383,12 @@ func (session *Session) Sync2(beans ...interface{}) error { for name, index := range addedNames { if index.Type == core.UniqueType { session.statement.RefTable = table - session.statement.tableName = tbName - err = session.addUnique(tbName, name) + session.statement.tableName = tbNameWithSchema + err = session.addUnique(tbNameWithSchema, name) } else if index.Type == core.IndexType { session.statement.RefTable = table - session.statement.tableName = tbName - err = session.addIndex(tbName, name) + session.statement.tableName = tbNameWithSchema + err = session.addIndex(tbNameWithSchema, name) } if err != nil { return err @@ -428,7 +413,7 @@ func (session *Session) Sync2(beans ...interface{}) error { for _, colName := range table.ColumnsSeq() { 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) } } } diff --git a/vendor/github.com/go-xorm/xorm/session_tx.go b/vendor/github.com/go-xorm/xorm/session_tx.go index 84d2f7f9d..c8d759a31 100644 --- a/vendor/github.com/go-xorm/xorm/session_tx.go +++ b/vendor/github.com/go-xorm/xorm/session_tx.go @@ -24,6 +24,7 @@ func (session *Session) Rollback() error { if !session.isAutoCommit && !session.isCommitedOrRollbacked { session.saveLastSQL(session.engine.dialect.RollBackStr()) session.isCommitedOrRollbacked = true + session.isAutoCommit = true return session.tx.Rollback() } return nil @@ -34,6 +35,7 @@ func (session *Session) Commit() error { if !session.isAutoCommit && !session.isCommitedOrRollbacked { session.saveLastSQL("COMMIT") session.isCommitedOrRollbacked = true + session.isAutoCommit = true var err error if err = session.tx.Commit(); err == nil { // handle processors after tx committed diff --git a/vendor/github.com/go-xorm/xorm/session_update.go b/vendor/github.com/go-xorm/xorm/session_update.go index f55874566..84c7e7fec 100644 --- a/vendor/github.com/go-xorm/xorm/session_update.go +++ b/vendor/github.com/go-xorm/xorm/session_update.go @@ -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:]) ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) if err != nil { @@ -167,7 +167,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var isMap = t.Kind() == reflect.Map var isStruct = t.Kind() == reflect.Struct if isStruct { - if err := session.statement.setRefValue(v); err != nil { + if err := session.statement.setRefBean(bean); err != nil { return 0, err } @@ -176,12 +176,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } if session.statement.ColumnStr == "" { - colNames, args = buildUpdates(session.engine, session.statement.RefTable, bean, false, false, - false, false, session.statement.allUseBool, session.statement.useAllCols, - session.statement.mustColumnMap, session.statement.nullableMap, - session.statement.columnMap, true, session.statement.unscoped) + colNames, args = session.statement.buildUpdates(bean, false, false, + false, false, true) } else { - colNames, args, err = genCols(session.statement.RefTable, session, bean, true, true) + colNames, args, err = session.genUpdateColumns(bean) if err != nil { return 0, err } @@ -202,7 +200,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 table := session.statement.RefTable 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)+" = ?") col := table.UpdatedColumn() 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.getCacher2(table); cacher != nil && session.statement.UseCache { - //session.cacheUpdate(table, tableName, sqlStr, args...) - cacher.ClearIds(tableName) - cacher.ClearBeans(tableName) - } + if cacher := session.engine.getCacher(tableName); cacher != nil && session.statement.UseCache { + //session.cacheUpdate(table, tableName, sqlStr, args...) + session.engine.logger.Debug("[cacheUpdate] clear table ", tableName) + cacher.ClearIds(tableName) + cacher.ClearBeans(tableName) } // handle after update processors @@ -402,3 +400,92 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 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 +} diff --git a/vendor/github.com/go-xorm/xorm/statement.go b/vendor/github.com/go-xorm/xorm/statement.go index 6400425b2..7856936f5 100644 --- a/vendor/github.com/go-xorm/xorm/statement.go +++ b/vendor/github.com/go-xorm/xorm/statement.go @@ -5,7 +5,6 @@ package xorm import ( - "bytes" "database/sql/driver" "encoding/json" "errors" @@ -18,21 +17,6 @@ import ( "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 type Statement struct { RefTable *core.Table @@ -47,7 +31,6 @@ type Statement struct { HavingStr string ColumnStr string selectStr string - columnMap map[string]bool useAllCols bool OmitStr string AltTableName string @@ -67,6 +50,8 @@ type Statement struct { allUseBool bool checkVersion bool unscoped bool + columnMap columnMap + omitColumnMap columnMap mustColumnMap map[string]bool nullableMap map[string]bool incrColumns map[string]incrParam @@ -89,7 +74,8 @@ func (statement *Statement) Init() { statement.HavingStr = "" statement.ColumnStr = "" statement.OmitStr = "" - statement.columnMap = make(map[string]bool) + statement.columnMap = columnMap{} + statement.omitColumnMap = columnMap{} statement.AltTableName = "" statement.tableName = "" statement.idParam = nil @@ -221,34 +207,33 @@ func (statement *Statement) setRefValue(v reflect.Value) error { if err != nil { return err } - statement.tableName = statement.Engine.tbName(v) + statement.tableName = statement.Engine.TableName(v, true) return nil } -// 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.String { - 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) +func (statement *Statement) setRefBean(bean interface{}) error { + var err error + statement.RefTable, err = statement.Engine.autoMapType(rValue(bean)) + if err != nil { + return err } - return statement + statement.tableName = statement.Engine.TableName(bean, true) + return nil } // Auto generating update columnes and values according a struct -func buildUpdates(engine *Engine, table *core.Table, bean interface{}, - includeVersion bool, includeUpdated bool, includeNil bool, - includeAutoIncr bool, allUseBool bool, useAllCols bool, - mustColumnMap map[string]bool, nullableMap map[string]bool, - columnMap map[string]bool, update, unscoped bool) ([]string, []interface{}) { +func (statement *Statement) buildUpdates(bean interface{}, + includeVersion, includeUpdated, includeNil, + includeAutoIncr, update bool) ([]string, []interface{}) { + engine := statement.Engine + 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 args = make([]interface{}, 0) @@ -268,7 +253,14 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if col.IsDeleted && !unscoped { 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 } @@ -604,17 +596,10 @@ func (statement *Statement) col2NewColsWithQuote(columns ...string) []string { } func (statement *Statement) colmap2NewColsWithQuote() []string { - newColumns := make([]string, 0, len(statement.columnMap)) - for col := range statement.columnMap { - fields := strings.Split(strings.TrimSpace(col), ".") - if len(fields) == 1 { - 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")) - } + newColumns := make([]string, len(statement.columnMap), len(statement.columnMap)) + copy(newColumns, statement.columnMap) + for i := 0; i < len(statement.columnMap); i++ { + newColumns[i] = statement.Engine.Quote(newColumns[i]) } return newColumns } @@ -642,10 +627,11 @@ func (statement *Statement) Select(str string) *Statement { func (statement *Statement) Cols(columns ...string) *Statement { cols := col2NewCols(columns...) for _, nc := range cols { - statement.columnMap[strings.ToLower(nc)] = true + statement.columnMap.add(nc) } newColumns := statement.colmap2NewColsWithQuote() + statement.ColumnStr = strings.Join(newColumns, ", ") statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.quote("*"), "*", -1) return statement @@ -680,7 +666,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement { func (statement *Statement) Omit(columns ...string) { newColumns := col2NewCols(columns...) 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(", "))) } @@ -719,10 +705,9 @@ func (statement *Statement) OrderBy(order string) *Statement { // Desc generate `ORDER BY xx DESC` func (statement *Statement) Desc(colNames ...string) *Statement { - var buf bytes.Buffer - fmt.Fprintf(&buf, statement.OrderStr) + var buf builder.StringBuilder if len(statement.OrderStr) > 0 { - fmt.Fprint(&buf, ", ") + fmt.Fprint(&buf, statement.OrderStr, ", ") } newColNames := statement.col2NewColsWithQuote(colNames...) 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. func (statement *Statement) Asc(colNames ...string) *Statement { - var buf bytes.Buffer - fmt.Fprintf(&buf, statement.OrderStr) + var buf builder.StringBuilder if len(statement.OrderStr) > 0 { - fmt.Fprint(&buf, ", ") + fmt.Fprint(&buf, statement.OrderStr, ", ") } newColNames := statement.col2NewColsWithQuote(colNames...) fmt.Fprintf(&buf, "%v ASC", strings.Join(newColNames, " ASC, ")) @@ -743,48 +727,35 @@ func (statement *Statement) Asc(colNames ...string) *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 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 { fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) } else { fmt.Fprintf(&buf, "%v JOIN ", joinOP) } - switch tablename.(type) { - 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))) - } + tbName := statement.Engine.TableName(tablename, true) - fmt.Fprintf(&buf, " ON %v", condition) + fmt.Fprintf(&buf, "%s ON %v", tbName, condition) statement.JoinStr = buf.String() statement.joinArgs = append(statement.joinArgs, args...) return statement @@ -809,18 +780,20 @@ func (statement *Statement) Unscoped() *Statement { } func (statement *Statement) genColumnStr() string { - var buf bytes.Buffer if statement.RefTable == nil { return "" } + var buf builder.StringBuilder columns := statement.RefTable.Columns() for _, col := range columns { - if statement.OmitStr != "" { - if _, ok := getFlagForColumn(statement.columnMap, col); ok { - continue - } + if statement.omitColumnMap.contain(col.Name) { + continue + } + + if len(statement.columnMap) > 0 && !statement.columnMap.contain(col.Name) { + continue } if col.MapType == core.ONLYTODB { @@ -831,10 +804,6 @@ func (statement *Statement) genColumnStr() string { buf.WriteString(", ") } - if col.IsPrimaryKey && statement.Engine.Dialect().DBType() == "ql" { - buf.WriteString("id() AS ") - } - if statement.JoinStr != "" { if statement.TableAlias != "" { buf.WriteString(statement.TableAlias) @@ -859,11 +828,13 @@ func (statement *Statement) genCreateTableSQL() string { func (statement *Statement) genIndexSQL() []string { var sqls []string tbName := statement.TableName() - quote := statement.Engine.Quote - for idxName, index := range statement.RefTable.Indexes { + for _, index := range statement.RefTable.Indexes { if index.Type == core.IndexType { - sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)), - quote(tbName), quote(strings.Join(index.Cols, quote(",")))) + sql := statement.Engine.dialect.CreateIndexSql(tbName, index) + /*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) } } @@ -889,16 +860,18 @@ func (statement *Statement) genUniqueSQL() []string { func (statement *Statement) genDelIndexSQL() []string { var sqls []string tbName := statement.TableName() + idxPrefixName := strings.Replace(tbName, `"`, "", -1) + idxPrefixName = strings.Replace(idxPrefixName, `.`, "_", -1) for idxName, index := range statement.RefTable.Indexes { var rIdxName string if index.Type == core.UniqueType { - rIdxName = uniqueName(tbName, idxName) + rIdxName = uniqueName(idxPrefixName, idxName) } 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() { - sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(statement.TableName())) + sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(tbName)) } sqls = append(sqls, sql) } @@ -949,7 +922,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, v := rValue(bean) isStruct := v.Kind() == reflect.Struct if isStruct { - statement.setRefValue(v) + statement.setRefBean(bean) } var columnStr = statement.ColumnStr @@ -982,13 +955,17 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, if err := statement.mergeConds(bean); err != nil { return "", nil, err } + } else { + if err := statement.processIDParam(); err != nil { + return "", nil, err + } } condSQL, condArgs, err := builder.ToSQL(statement.cond) if err != nil { return "", nil, err } - sqlStr, err := statement.genSelectSQL(columnStr, condSQL) + sqlStr, err := statement.genSelectSQL(columnStr, condSQL, true, true) if err != nil { return "", nil, err } @@ -1001,7 +978,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa var condArgs []interface{} var err error if len(beans) > 0 { - statement.setRefValue(rValue(beans[0])) + statement.setRefBean(beans[0]) condSQL, condArgs, err = statement.genConds(beans[0]) } else { condSQL, condArgs, err = builder.ToSQL(statement.cond) @@ -1018,7 +995,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa selectSQL = "count(*)" } } - sqlStr, err := statement.genSelectSQL(selectSQL, condSQL) + sqlStr, err := statement.genSelectSQL(selectSQL, condSQL, false, false) if err != nil { 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) { - statement.setRefValue(rValue(bean)) + statement.setRefBean(bean) var sumStrs = make([]string, 0, len(columns)) for _, colName := range columns { @@ -1043,7 +1020,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri return "", nil, err } - sqlStr, err := statement.genSelectSQL(sumSelect, condSQL) + sqlStr, err := statement.genSelectSQL(sumSelect, condSQL, true, true) if err != nil { return "", nil, err } @@ -1051,27 +1028,20 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri return sqlStr, append(statement.joinArgs, condArgs...), nil } -func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, err error) { - var distinct string +func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, needOrderBy bool) (string, error) { + var ( + distinct string + dialect = statement.Engine.Dialect() + quote = statement.Engine.Quote + fromStr = " FROM " + top, mssqlCondi, whereStr string + ) if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { 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 { - 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(), "..") { fromStr += statement.TableName() @@ -1118,9 +1088,10 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, e } var orderStr string - if len(statement.OrderStr) > 0 { + if needOrderBy && len(statement.OrderStr) > 0 { orderStr = " ORDER BY " + statement.OrderStr } + var groupStr string if len(statement.GroupByStr) > 0 { 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 - a = fmt.Sprintf("SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr) + var buf builder.StringBuilder + fmt.Fprintf(&buf, "SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr) if len(mssqlCondi) > 0 { if len(whereStr) > 0 { - a += " AND " + mssqlCondi + fmt.Fprint(&buf, " AND ", mssqlCondi) } else { - a += " WHERE " + mssqlCondi + fmt.Fprint(&buf, " WHERE ", mssqlCondi) } } if statement.GroupByStr != "" { - a = fmt.Sprintf("%v GROUP BY %v", a, statement.GroupByStr) + fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr) } if statement.HavingStr != "" { - a = fmt.Sprintf("%v %v", a, statement.HavingStr) + fmt.Fprint(&buf, " ", statement.HavingStr) } - if statement.OrderStr != "" { - a = fmt.Sprintf("%v ORDER BY %v", a, statement.OrderStr) + if needOrderBy && statement.OrderStr != "" { + fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) } - if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE { - if statement.Start > 0 { - a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start) - } else if statement.LimitN > 0 { - a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN) - } - } else if dialect.DBType() == core.ORACLE { - if statement.Start != 0 || statement.LimitN != 0 { - 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 needLimit { + if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE { + if statement.Start > 0 { + fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", statement.LimitN, statement.Start) + } else if statement.LimitN > 0 { + fmt.Fprint(&buf, " LIMIT ", statement.LimitN) + } + } else if dialect.DBType() == core.ORACLE { + 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 { - a = dialect.ForUpdateSql(a) + return dialect.ForUpdateSql(buf.String()), nil } - return + return buf.String(), nil } func (statement *Statement) processIDParam() error { - if statement.idParam == nil { + if statement.idParam == nil || statement.RefTable == nil { return nil } diff --git a/vendor/github.com/go-xorm/xorm/xorm.go b/vendor/github.com/go-xorm/xorm/xorm.go index 4fdadf2fa..141c4897d 100644 --- a/vendor/github.com/go-xorm/xorm/xorm.go +++ b/vendor/github.com/go-xorm/xorm/xorm.go @@ -17,7 +17,7 @@ import ( const ( // Version show the xorm's version - Version string = "0.6.4.0910" + Version string = "0.7.0.0504" ) func regDrvsNDialects() bool { @@ -31,7 +31,7 @@ func regDrvsNDialects() bool { "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, 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{} }}, - "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{} }}, "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, 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", TZLocation: time.Local, tagHandlers: defaultTagHandlers, + cachers: make(map[string]core.Cacher), } if uri.DbType == core.SQLITE { @@ -108,6 +109,13 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { 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 func (engine *Engine) Clone() (*Engine, error) { return NewEngine(engine.DriverName(), engine.DataSourceName())