diff --git a/cmd/web.go b/cmd/web.go index 60bef9d03..0e8fc09b9 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -76,7 +76,6 @@ func runWeb(*cli.Context) { m.Get("/issues", reqSignIn, user.Issues) m.Get("/pulls", reqSignIn, user.Pulls) m.Get("/stars", reqSignIn, user.Stars) - m.Get("/help", routers.Help) m.Group("/api", func(r martini.Router) { m.Group("/v1", func(r martini.Router) { @@ -191,9 +190,12 @@ func runWeb(*cli.Context) { r.Get("/new", repo.CreateIssue) r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) - r.Post("/:index/assignee", repo.UpdateAssignee) + r.Post("/:index/label", repo.UpdateIssueLabel) r.Post("/:index/milestone", repo.UpdateIssueMilestone) + r.Post("/:index/assignee", repo.UpdateAssignee) r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) + r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) + r.Post("/labels/delete", repo.DeleteLabel) r.Get("/milestones", repo.Milestones) r.Get("/milestones/new", repo.NewMilestone) r.Post("/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost) diff --git a/gogs.go b/gogs.go index 16fd5ec76..45aa9f593 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/base" ) -const APP_VER = "0.3.5.0521 Alpha" +const APP_VER = "0.3.5.0523 Alpha" func init() { base.AppVer = APP_VER diff --git a/models/issue.go b/models/issue.go index 62cc9363b..3651c4611 100644 --- a/models/issue.go +++ b/models/issue.go @@ -17,6 +17,7 @@ import ( var ( ErrIssueNotExist = errors.New("Issue does not exist") + ErrLabelNotExist = errors.New("Label does not exist") ErrMilestoneNotExist = errors.New("Milestone does not exist") ) @@ -28,14 +29,15 @@ type Issue struct { Name string Repo *Repository `xorm:"-"` PosterId int64 - Poster *User `xorm:"-"` + Poster *User `xorm:"-"` + LabelIds string `xorm:"TEXT"` + Labels []*Label `xorm:"-"` MilestoneId int64 AssigneeId int64 Assignee *User `xorm:"-"` IsRead bool `xorm:"-"` IsPull bool // Indicates whether is a pull request or not. IsClosed bool - Labels string `xorm:"TEXT"` Content string `xorm:"TEXT"` RenderedContent string `xorm:"-"` Priority int @@ -54,11 +56,37 @@ func (i *Issue) GetPoster() (err error) { return err } +func (i *Issue) GetLabels() error { + if len(i.LabelIds) < 3 { + return nil + } + + strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$") + i.Labels = make([]*Label, 0, len(strIds)) + for _, strId := range strIds { + id, _ := base.StrTo(strId).Int64() + if id > 0 { + l, err := GetLabelById(id) + if err != nil { + if err == ErrLabelNotExist { + continue + } + return err + } + i.Labels = append(i.Labels, l) + } + } + return nil +} + func (i *Issue) GetAssignee() (err error) { if i.AssigneeId == 0 { return nil } i.Assignee, err = GetUserById(i.AssigneeId) + if err == ErrUserNotExist { + return nil + } return err } @@ -108,7 +136,7 @@ func GetIssueById(id int64) (*Issue, error) { } // GetIssues returns a list of issues by given conditions. -func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortType string) ([]Issue, error) { +func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) { sess := orm.Limit(20, (page-1)*20) if rid > 0 { @@ -127,9 +155,9 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortTy sess.And("milestone_id=?", mid) } - if len(labels) > 0 { - for _, label := range strings.Split(labels, ",") { - sess.And("labels like '%$" + label + "|%'") + if len(labelIds) > 0 { + for _, label := range strings.Split(labelIds, ",") { + sess.And("label_ids like '%$" + label + "|%'") } } @@ -155,6 +183,13 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortTy return issues, err } +// GetIssuesByLabel returns a list of issues by given label and repository. +func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) { + issues := make([]*Issue, 0, 10) + err := orm.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues) + return issues, err +} + // GetIssueCountByPoster returns number of issues of repository by poster. func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 { count, _ := orm.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue)) @@ -175,7 +210,6 @@ type IssueUser struct { IssueId int64 RepoId int64 MilestoneId int64 - Labels string `xorm:"TEXT"` IsRead bool IsAssigned bool IsMentioned bool @@ -400,6 +434,98 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error { return nil } +// .____ ___. .__ +// | | _____ \_ |__ ____ | | +// | | \__ \ | __ \_/ __ \| | +// | |___ / __ \| \_\ \ ___/| |__ +// |_______ (____ /___ /\___ >____/ +// \/ \/ \/ \/ + +// Label represents a label of repository for issues. +type Label struct { + Id int64 + RepoId int64 `xorm:"INDEX"` + Name string + Color string `xorm:"VARCHAR(7)"` + NumIssues int + NumClosedIssues int + NumOpenIssues int `xorm:"-"` + IsChecked bool `xorm:"-"` +} + +// CalOpenIssues calculates the open issues of label. +func (m *Label) CalOpenIssues() { + m.NumOpenIssues = m.NumIssues - m.NumClosedIssues +} + +// NewLabel creates new label of repository. +func NewLabel(l *Label) error { + _, err := orm.Insert(l) + return err +} + +// GetLabelById returns a label by given ID. +func GetLabelById(id int64) (*Label, error) { + l := &Label{Id: id} + has, err := orm.Get(l) + if err != nil { + return nil, err + } else if !has { + return nil, ErrLabelNotExist + } + return l, nil +} + +// GetLabels returns a list of labels of given repository ID. +func GetLabels(repoId int64) ([]*Label, error) { + labels := make([]*Label, 0, 10) + err := orm.Where("repo_id=?", repoId).Find(&labels) + return labels, err +} + +// UpdateLabel updates label information. +func UpdateLabel(l *Label) error { + _, err := orm.Id(l.Id).Update(l) + return err +} + +// DeleteLabel delete a label of given repository. +func DeleteLabel(repoId int64, strId string) error { + id, _ := base.StrTo(strId).Int64() + l, err := GetLabelById(id) + if err != nil { + if err == ErrLabelNotExist { + return nil + } + return err + } + + issues, err := GetIssuesByLabel(repoId, strId) + if err != nil { + return err + } + + sess := orm.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + for _, issue := range issues { + issue.LabelIds = strings.Replace(issue.LabelIds, "$"+strId+"|", "", -1) + if _, err = sess.Id(issue.Id).AllCols().Update(issue); err != nil { + sess.Rollback() + return err + } + } + + if _, err = sess.Delete(l); err != nil { + sess.Rollback() + return err + } + return sess.Commit() +} + // _____ .__.__ __ // / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ @@ -611,42 +737,6 @@ func DeleteMilestone(m *Milestone) (err error) { return sess.Commit() } -// .____ ___. .__ -// | | _____ \_ |__ ____ | | -// | | \__ \ | __ \_/ __ \| | -// | |___ / __ \| \_\ \ ___/| |__ -// |_______ (____ /___ /\___ >____/ -// \/ \/ \/ \/ - -// Label represents a label of repository for issues. -type Label struct { - Id int64 - RepoId int64 `xorm:"INDEX"` - Name string - Color string `xorm:"VARCHAR(7)"` - NumIssues int - NumClosedIssues int - NumOpenIssues int `xorm:"-"` -} - -// CalOpenIssues calculates the open issues of label. -func (m *Label) CalOpenIssues() { - m.NumOpenIssues = m.NumIssues - m.NumClosedIssues -} - -// NewLabel creates new label of repository. -func NewLabel(l *Label) error { - _, err := orm.Insert(l) - return err -} - -// GetLabels returns a list of labels of given repository ID. -func GetLabels(repoId int64) ([]*Label, error) { - labels := make([]*Label, 0, 10) - err := orm.Where("repo_id=?", repoId).Find(&labels) - return labels, err -} - // _________ __ // \_ ___ \ ____ _____ _____ ____ _____/ |_ // / \ \/ / _ \ / \ / \_/ __ \ / \ __\ diff --git a/routers/dashboard.go b/routers/dashboard.go index 78533127f..6387089e5 100644 --- a/routers/dashboard.go +++ b/routers/dashboard.go @@ -32,9 +32,8 @@ func Home(ctx *middleware.Context) { } for _, repo := range repos { - repo.Owner, err = models.GetUserById(repo.OwnerId) - if err != nil { - ctx.Handle(500, "dashboard.Home(GetUserById)", err) + if err = repo.GetOwner(); err != nil { + ctx.Handle(500, "dashboard.Home(GetOwner)", err) return } } @@ -43,12 +42,6 @@ func Home(ctx *middleware.Context) { ctx.HTML(200, "home") } -func Help(ctx *middleware.Context) { - ctx.Data["PageIsHelp"] = true - ctx.Data["Title"] = "Help" - ctx.HTML(200, "help") -} - func NotFound(ctx *middleware.Context) { ctx.Data["PageIsNotFound"] = true ctx.Data["Title"] = "Page Not Found" diff --git a/routers/install.go b/routers/install.go index 53ce90d5b..da00b90e3 100644 --- a/routers/install.go +++ b/routers/install.go @@ -25,7 +25,6 @@ import ( "github.com/gogits/gogs/modules/social" ) -// Check run mode(Default of martini is Dev). func checkRunMode() { switch base.Cfg.MustValue("", "RUN_MODE") { case "prod": diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 8852dd08f..0c0b7348f 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -197,7 +197,7 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C PosterId: ctx.User.Id, MilestoneId: form.MilestoneId, AssigneeId: form.AssigneeId, - Labels: form.Labels, + LabelIds: form.Labels, Content: form.Content, } if err := models.NewIssue(issue); err != nil { @@ -269,6 +269,17 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index)) } +func checkLabels(labels, allLabels []*models.Label) { + for _, l := range labels { + for _, l2 := range allLabels { + if l.Id == l2.Id { + l2.IsChecked = true + break + } + } + } +} + func ViewIssue(ctx *middleware.Context, params martini.Params) { idx, _ := base.StrTo(params["index"]).Int64() if idx == 0 { @@ -286,6 +297,19 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { return } + // Get labels. + if err = issue.GetLabels(); err != nil { + ctx.Handle(500, "issue.ViewIssue(GetLabels)", err) + return + } + labels, err := models.GetLabels(ctx.Repo.Repository.Id) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetLabels.2)", err) + return + } + checkLabels(issue.Labels, labels) + ctx.Data["Labels"] = labels + // Get assigned milestone. if issue.MilestoneId > 0 { ctx.Data["Milestone"], err = models.GetMilestoneById(issue.MilestoneId) @@ -364,13 +388,13 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { } func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { - idx, err := base.StrTo(params["index"]).Int() - if err != nil { + idx, _ := base.StrTo(params["index"]).Int64() + if idx <= 0 { ctx.Error(404) return } - issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(idx)) + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.UpdateIssue", err) @@ -381,14 +405,14 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat } if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner { - ctx.Handle(404, "issue.UpdateIssue", nil) + ctx.Error(403) return } issue.Name = form.IssueName issue.MilestoneId = form.MilestoneId issue.AssigneeId = form.AssigneeId - issue.Labels = form.Labels + issue.LabelIds = form.Labels issue.Content = form.Content // try get content from text, ignore conflict with preview ajax if form.Content == "" { @@ -406,6 +430,55 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat }) } +func UpdateIssueLabel(ctx *middleware.Context, params martini.Params) { + if !ctx.Repo.IsOwner { + ctx.Error(403) + return + } + + idx, _ := base.StrTo(params["index"]).Int64() + if idx <= 0 { + ctx.Error(404) + return + } + + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) + if err != nil { + if err == models.ErrIssueNotExist { + ctx.Handle(404, "issue.UpdateIssueLabel", err) + } else { + ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err) + } + return + } + + isAttach := ctx.Query("action") == "attach" + labelStrId := ctx.Query("id") + isHad := strings.Contains(issue.LabelIds, "$"+labelStrId+"|") + isNeedUpdate := false + if isAttach { + if !isHad { + issue.LabelIds += "$" + labelStrId + "|" + isNeedUpdate = true + } + } else { + if isHad { + issue.LabelIds = strings.Replace(issue.LabelIds, "$"+labelStrId+"|", "", -1) + isNeedUpdate = true + } + } + + if isNeedUpdate { + if err = models.UpdateIssue(issue); err != nil { + ctx.Handle(500, "issue.UpdateIssueLabel(UpdateIssue)", err) + return + } + } + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + func UpdateIssueMilestone(ctx *middleware.Context) { if !ctx.Repo.IsOwner { ctx.Error(403) @@ -622,8 +695,37 @@ func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) { ctx.Redirect(ctx.Repo.RepoLink + "/issues") } -func UpdateLabel(ctx *middleware.Context, params martini.Params) { +func UpdateLabel(ctx *middleware.Context, params martini.Params, form auth.CreateLabelForm) { + id, _ := base.StrTo(ctx.Query("id")).Int64() + if id == 0 { + ctx.Error(404) + return + } + l := &models.Label{ + Id: id, + Name: form.Title, + Color: form.Color, + } + if err := models.UpdateLabel(l); err != nil { + ctx.Handle(500, "issue.UpdateLabel(UpdateLabel)", err) + return + } + ctx.Redirect(ctx.Repo.RepoLink + "/issues") +} + +func DeleteLabel(ctx *middleware.Context) { + strIds := strings.Split(ctx.Query("remove"), ",") + for _, strId := range strIds { + if err := models.DeleteLabel(ctx.Repo.Repository.Id, strId); err != nil { + ctx.Handle(500, "issue.DeleteLabel(DeleteLabel)", err) + return + } + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) } func Milestones(ctx *middleware.Context) { diff --git a/templates/help.tmpl b/templates/help.tmpl deleted file mode 100644 index e835c2981..000000000 --- a/templates/help.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -{{template "base/head" .}} -{{template "base/navbar" .}} -
-