From 5c4bc3c848fb4bd46ad5ceeacd82cdfa8f2b5635 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Sat, 26 Jul 2014 02:28:04 -0400 Subject: [PATCH] Huge updates!!!!! Be careful to merge!!!! --- cmd/web.go | 152 +- conf/locale/locale_en-US.ini | 2 + conf/locale/locale_zh-CN.ini | 2 + gogs.go | 2 +- models/release.go | 2 +- modules/base/template.go | 1 - modules/git/commit.go | 8 + modules/git/repo_commit.go | 57 + modules/git/repo_tag.go | 8 + modules/git/tree_blob.go | 13 + modules/git/utils.go | 21 + public/{ng => }/css/github.min.css | 0 public/img/avatar_default.jpg | Bin 6951 -> 6238 bytes public/img/favicon.bak.png | Bin 15949 -> 12737 bytes public/img/gogs-lg.png | Bin 97926 -> 97903 bytes public/ng/css/font-awesome.min.css | 4 - public/ng/css/gogs.css | 6 +- routers/org/members.go | 6 +- routers/org/org.go | 36 +- routers/org/teams.go | 26 +- routers/repo/commit.go | 375 ++-- routers/repo/download.go | 65 +- routers/repo/issue.go | 2226 ++++++++++++----------- routers/repo/release.go | 382 ++-- routers/repo/repo.go | 184 +- routers/repo/setting.go | 620 +++---- routers/user/home.go | 23 +- routers/user/setting.go | 8 + templates/.VERSION | 2 +- templates/admin/config.tmpl | 6 +- templates/ng/base/head.tmpl | 4 +- templates/repo/commits.tmpl | 2 +- templates/repo/diff.tmpl | 2 +- templates/repo/issue/list.tmpl | 2 +- templates/repo/issue/view.tmpl | 8 +- templates/status/401.tmpl | 6 +- templates/status/404.tmpl | 1 + templates/user/dashboard/dashboard.tmpl | 49 +- templates/user/dashboard/repo_list.tmpl | 12 + templates/user/issues.tmpl | 2 +- templates/user/profile.tmpl | 6 +- templates/user/settings/nav.tmpl | 1 + templates/user/settings/orgs.tmpl | 18 + 43 files changed, 2225 insertions(+), 2125 deletions(-) rename public/{ng => }/css/github.min.css (100%) delete mode 100644 public/ng/css/font-awesome.min.css create mode 100644 templates/user/dashboard/repo_list.tmpl create mode 100644 templates/user/settings/orgs.tmpl diff --git a/cmd/web.go b/cmd/web.go index 9329b61426..744614f6b7 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -30,7 +30,7 @@ import ( "github.com/gogits/gogs/routers/admin" "github.com/gogits/gogs/routers/api/v1" "github.com/gogits/gogs/routers/dev" - // "github.com/gogits/gogs/routers/org" + "github.com/gogits/gogs/routers/org" "github.com/gogits/gogs/routers/repo" "github.com/gogits/gogs/routers/user" ) @@ -101,8 +101,8 @@ func runWeb(*cli.Context) { // Routers. m.Get("/", ignSignIn, routers.Home) - // m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install) - // m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost) + m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install) + m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost) m.Group("", func(r *macaron.Router) { r.Get("/issues", user.Issues) r.Get("/pulls", user.Pulls) @@ -151,6 +151,7 @@ func runWeb(*cli.Context) { r.Get("/ssh", user.SettingsSSHKeys) r.Post("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost) r.Get("/social", user.SettingsSocial) + r.Get("/orgs", user.SettingsOrgs) r.Route("/delete", "GET,POST", user.SettingsDelete) }) }, reqSignIn) @@ -173,8 +174,8 @@ func runWeb(*cli.Context) { m.Group("/repo", func(r *macaron.Router) { r.Get("/create", repo.Create) r.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost) - // r.Get("/migrate", repo.Migrate) - // r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost) + r.Get("/migrate", repo.Migrate) + r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost) }, reqSignIn) adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) @@ -210,91 +211,92 @@ func runWeb(*cli.Context) { dev.RegisterDebugRoutes(m) } - // reqTrueOwner := middleware.RequireTrueOwner() + reqTrueOwner := middleware.RequireTrueOwner() - // m.Group("/org", func(r *macaron.Router) { - // r.Get("/create", org.New) - // r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.NewPost) - // r.Get("/:org", org.Home) - // r.Get("/:org/dashboard", org.Dashboard) - // r.Get("/:org/members", org.Members) + // Organization routers. + m.Group("/org", func(r *macaron.Router) { + r.Get("/create", org.New) + r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.NewPost) + r.Get("/:org", org.Home) + r.Get("/:org/dashboard", org.Dashboard) + r.Get("/:org/members", org.Members) - // r.Get("/:org/teams", org.Teams) - // r.Get("/:org/teams/new", org.NewTeam) - // r.Post("/:org/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost) - // r.Get("/:org/teams/:team/edit", org.EditTeam) + r.Get("/:org/teams", org.Teams) + r.Get("/:org/teams/new", org.NewTeam) + r.Post("/:org/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost) + r.Get("/:org/teams/:team/edit", org.EditTeam) - // r.Get("/:org/team/:team", org.SingleTeam) + r.Get("/:org/team/:team", org.SingleTeam) - // r.Get("/:org/settings", org.Settings) - // r.Post("/:org/settings", bindIgnErr(auth.OrgSettingForm{}), org.SettingsPost) - // r.Post("/:org/settings/delete", org.DeletePost) - // }, reqSignIn) + r.Get("/:org/settings", org.Settings) + r.Post("/:org/settings", bindIgnErr(auth.OrgSettingForm{}), org.SettingsPost) + r.Post("/:org/settings/delete", org.DeletePost) + }, reqSignIn) - // m.Group("/:username/:reponame", func(r *macaron.Router) { - // r.Get("/settings", repo.Setting) - // r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost) + m.Group("/:username/:reponame", func(r *macaron.Router) { + r.Get("/settings", repo.Setting) + r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost) - // m.Group("/settings", func(r *macaron.Router) { - // r.Get("/collaboration", repo.Collaboration) - // r.Post("/collaboration", repo.CollaborationPost) - // r.Get("/hooks", repo.WebHooks) - // r.Get("/hooks/add", repo.WebHooksAdd) - // r.Post("/hooks/add", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksAddPost) - // r.Get("/hooks/:id", repo.WebHooksEdit) - // r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) - // }) - // }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) + m.Group("/settings", func(r *macaron.Router) { + r.Get("/collaboration", repo.Collaboration) + r.Post("/collaboration", repo.CollaborationPost) + r.Get("/hooks", repo.WebHooks) + r.Get("/hooks/add", repo.WebHooksAdd) + r.Post("/hooks/add", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksAddPost) + r.Get("/hooks/:id", repo.WebHooksEdit) + r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) + }) + }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) - // m.Group("/:username/:reponame", func(r *macaron.Router) { - // r.Get("/action/:action", repo.Action) + m.Group("/:username/:reponame", func(r *macaron.Router) { + // r.Get("/action/:action", repo.Action) - // m.Group("/issues", func(r *macaron.Router) { - // r.Get("/new", repo.CreateIssue) - // r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) - // r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) - // r.Post("/:index/label", repo.UpdateIssueLabel) - // r.Post("/:index/milestone", repo.UpdateIssueMilestone) - // r.Post("/:index/assignee", repo.UpdateAssignee) - // r.Get("/:index/attachment/:id", repo.IssueGetAttachment) - // 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) - // r.Get("/milestones/:index/edit", repo.UpdateMilestone) - // r.Post("/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost) - // r.Get("/milestones/:index/:action", repo.UpdateMilestone) - // }) + m.Group("/issues", func(r *macaron.Router) { + r.Get("/new", repo.CreateIssue) + r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) + r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) + r.Post("/:index/label", repo.UpdateIssueLabel) + r.Post("/:index/milestone", repo.UpdateIssueMilestone) + r.Post("/:index/assignee", repo.UpdateAssignee) + r.Get("/:index/attachment/:id", repo.IssueGetAttachment) + 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) + r.Get("/milestones/:index/edit", repo.UpdateMilestone) + r.Post("/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost) + r.Get("/milestones/:index/:action", repo.UpdateMilestone) + }) - // r.Post("/comment/:action", repo.Comment) - // r.Get("/releases/new", repo.NewRelease) - // r.Get("/releases/edit/:tagname", repo.EditRelease) - // }, reqSignIn, middleware.RepoAssignment(true)) + r.Post("/comment/:action", repo.Comment) + r.Get("/releases/new", repo.NewRelease) + r.Get("/releases/edit/:tagname", repo.EditRelease) + }, reqSignIn, middleware.RepoAssignment(true)) - // m.Group("/:username/:reponame", func(r *macaron.Router) { - // r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) - // r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) - // }, reqSignIn, middleware.RepoAssignment(true, true)) + m.Group("/:username/:reponame", func(r *macaron.Router) { + r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) + r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) + }, reqSignIn, middleware.RepoAssignment(true, true)) - // m.Group("/:username/:reponame", func(r *macaron.Router) { - // r.Get("/issues", repo.Issues) - // r.Get("/issues/:index", repo.ViewIssue) - // r.Get("/pulls", repo.Pulls) - // r.Get("/branches", repo.Branches) - // }, ignSignIn, middleware.RepoAssignment(true)) + m.Group("/:username/:reponame", func(r *macaron.Router) { + r.Get("/issues", repo.Issues) + r.Get("/issues/:index", repo.ViewIssue) + r.Get("/pulls", repo.Pulls) + r.Get("/branches", repo.Branches) + }, ignSignIn, middleware.RepoAssignment(true)) m.Group("/:username/:reponame", func(r *macaron.Router) { r.Get("/src/:branchname", repo.Home) r.Get("/src/:branchname/*", repo.Home) - r.Get("/raw/:branchname/**", repo.SingleDownload) - // r.Get("/commits/:branchname", repo.Commits) - // r.Get("/commits/:branchname/search", repo.SearchCommits) - // r.Get("/commits/:branchname/**", repo.FileHistory) - // r.Get("/commit/:branchname", repo.Diff) - // r.Get("/commit/:branchname/**", repo.Diff) - // r.Get("/releases", repo.Releases) + r.Get("/raw/:branchname/*", repo.SingleDownload) + r.Get("/commits/:branchname", repo.Commits) + r.Get("/commits/:branchname/search", repo.SearchCommits) + r.Get("/commits/:branchname/*", repo.FileHistory) + r.Get("/commit/:branchname", repo.Diff) + r.Get("/commit/:branchname/*", repo.Diff) + r.Get("/releases", repo.Releases) r.Get("/archive/*.*", repo.Download) }, ignSignIn, middleware.RepoAssignment(true, true)) diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index b8832167ae..c673ca9f49 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -89,6 +89,7 @@ profile = Profile password = Password ssh_keys = SSH Keys social = Social Accounts +orgs = Organizations delete = Delete Accoount public_profile = Public Profile @@ -118,6 +119,7 @@ add_on = Added on last_used = Last used on no_activity = No recent activity +manage_orgs = Manage Organizations manage_social = Manage Associated Social Accounts delete_account = Delete Your Account diff --git a/conf/locale/locale_zh-CN.ini b/conf/locale/locale_zh-CN.ini index 30e683071e..ff542a374c 100644 --- a/conf/locale/locale_zh-CN.ini +++ b/conf/locale/locale_zh-CN.ini @@ -89,6 +89,7 @@ profile = 个人信息 password = 修改密码 ssh_keys = 管理 SSH 密钥 social = 社交帐号绑定 +orgs = 管理组织 delete = 删除帐户 public_profile = 公开信息 @@ -118,6 +119,7 @@ add_on = 增加于 last_used = 上次使用在 no_activity = 没有最近活动 +manage_orgs = 管理我的组织 manage_social = 管理关联社交帐户 delete_account = 删除当前帐户 diff --git a/gogs.go b/gogs.go index e432d6bede..53f96e90f9 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/setting" ) -const APP_VER = "0.4.7.0725 Alpha" +const APP_VER = "0.4.7.0726 Alpha" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/models/release.go b/models/release.go index 3e1a78118c..012b6cc5c4 100644 --- a/models/release.go +++ b/models/release.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/gogits/git" + "github.com/gogits/gogs/modules/git" ) var ( diff --git a/modules/base/template.go b/modules/base/template.go index 7589fdaafb..b9968bae65 100644 --- a/modules/base/template.go +++ b/modules/base/template.go @@ -106,7 +106,6 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ "CreateCaptcha": func() string { return "" }, } -// TODO: Legacy type Actioner interface { GetOpType() int GetActUserName() string diff --git a/modules/git/commit.go b/modules/git/commit.go index f61f2927b9..52348fefed 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -73,6 +73,14 @@ func (c *Commit) CommitsCount() (int, error) { return c.repo.commitsCount(c.Id) } +func (c *Commit) SearchCommits(keyword string) (*list.List, error) { + return c.repo.searchCommits(c.Id, keyword) +} + +func (c *Commit) CommitsByRange(page int) (*list.List, error) { + return c.repo.commitsByRange(c.Id, page) +} + func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) { return c.repo.getCommitOfRelPath(c.Id, relPath) } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index b1ea5a29a5..0e39963e68 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -32,7 +32,18 @@ func (repo *Repository) GetCommitOfBranch(branchName string) (*Commit, error) { if err != nil { return nil, err } + return repo.GetCommit(commitId) +} +func (repo *Repository) GetCommitIdOfTag(tagName string) (string, error) { + return repo.getCommitIdOfRef("refs/tags/" + tagName) +} + +func (repo *Repository) GetCommitOfTag(tagName string) (*Commit, error) { + commitId, err := repo.GetCommitIdOfTag(tagName) + if err != nil { + return nil, err + } return repo.GetCommit(commitId) } @@ -212,6 +223,32 @@ func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *li return nil } +func (repo *Repository) CommitsCount(commitId string) (int, error) { + id, err := NewIdFromString(commitId) + if err != nil { + return 0, err + } + return repo.commitsCount(id) +} + +func (repo *Repository) FileCommitsCount(branch, file string) (int, error) { + stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", + branch, "--", file) + if err != nil { + return 0, errors.New(stderr) + } + return com.StrTo(strings.TrimSpace(stdout)).Int() +} + +func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*list.List, error) { + stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", branch, + "--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat, "--", file) + if err != nil { + return nil, errors.New(string(stderr)) + } + return parsePrettyFormatLog(repo, stdout) +} + func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) { l := list.New() lock := new(sync.Mutex) @@ -219,6 +256,26 @@ func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) { return l, err } +func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) { + stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(), "-100", + "-i", "--grep="+keyword, prettyLogFormat) + if err != nil { + return nil, err + } else if len(stderr) > 0 { + return nil, errors.New(string(stderr)) + } + return parsePrettyFormatLog(repo, stdout) +} + +func (repo *Repository) commitsByRange(id sha1, page int) (*list.List, error) { + stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(), + "--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat) + if err != nil { + return nil, errors.New(string(stderr)) + } + return parsePrettyFormatLog(repo, stdout) +} + func (repo *Repository) getCommitOfRelPath(id sha1, relPath string) (*Commit, error) { stdout, _, err := com.ExecCmdDir(repo.Path, "git", "log", "-1", prettyLogFormat, id.String(), "--", relPath) if err != nil { diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index cefbe783ab..21818f3e6b 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -30,6 +30,14 @@ func (repo *Repository) GetTags() ([]string, error) { return tags[:len(tags)-1], nil } +func (repo *Repository) CreateTag(tagName, idStr string) error { + _, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", tagName, idStr) + if err != nil { + return errors.New(stderr) + } + return nil +} + func (repo *Repository) getTag(id sha1) (*Tag, error) { if repo.tagCache != nil { if t, ok := repo.tagCache[id]; ok { diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go index debc722bc9..f996aba376 100644 --- a/modules/git/tree_blob.go +++ b/modules/git/tree_blob.go @@ -44,3 +44,16 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { } return nil, fmt.Errorf("GetTreeEntryByPath: %v", ErrNotExist) } + +func (t *Tree) GetBlobByPath(rpath string) (*Blob, error) { + entry, err := t.GetTreeEntryByPath(rpath) + if err != nil { + return nil, err + } + + if !entry.IsDir() { + return entry.Blob(), nil + } + + return nil, ErrNotExist +} diff --git a/modules/git/utils.go b/modules/git/utils.go index 3c0c60f235..26eef23191 100644 --- a/modules/git/utils.go +++ b/modules/git/utils.go @@ -5,12 +5,33 @@ package git import ( + "bytes" + "container/list" "path/filepath" "strings" ) const prettyLogFormat = `--pretty=format:%H` +func parsePrettyFormatLog(repo *Repository, logByts []byte) (*list.List, error) { + l := list.New() + if len(logByts) == 0 { + return l, nil + } + + parts := bytes.Split(logByts, []byte{'\n'}) + + for _, commitId := range parts { + commit, err := repo.GetCommit(string(commitId)) + if err != nil { + return nil, err + } + l.PushBack(commit) + } + + return l, nil +} + func RefEndName(refStr string) string { index := strings.LastIndex(refStr, "/") if index != -1 { diff --git a/public/ng/css/github.min.css b/public/css/github.min.css similarity index 100% rename from public/ng/css/github.min.css rename to public/css/github.min.css diff --git a/public/img/avatar_default.jpg b/public/img/avatar_default.jpg index 728ec5af6c197ae10062f55331348756f9e52fc6..f97aaaf0417f72cf38893ed48d1554d8dc22f043 100644 GIT binary patch delta 6115 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 byJ3b?2Uv-V3rMS2pg~7BXN!wV{QsK(TfB=w delta 6833 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 z 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ⅆ?8~{FuPC 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 diff --git a/public/ng/css/font-awesome.min.css b/public/ng/css/font-awesome.min.css deleted file mode 100644 index 3d920fc87c..0000000000 --- a/public/ng/css/font-awesome.min.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} \ No newline at end of file diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index 7afba6b628..96cf77a639 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -373,7 +373,7 @@ img.avatar-30 { display: inline-block; text-decoration: none; -webkit-font-smoothing: antialiased; - margin-right: 8px; + margin-left: 30px; } .markdown a span.octicon-link { opacity: 0; @@ -1058,6 +1058,9 @@ The register and sign-in page style } #repo-bare-start pre { margin: 0 40px; + padding: 6px 10px; + border: 1px solid #ddd; + background: #f8f8f8; } .repo-bare #repo-bare-start h2 { margin-top: 30px; @@ -1073,6 +1076,7 @@ The register and sign-in page style margin-right: 200px; } .repo-bare #repo-clone-help { + clear: both; width: 100%; } .repo-bare #repo-clone-url { diff --git a/routers/org/members.go b/routers/org/members.go index c6f49b14bd..ac278d4e6d 100644 --- a/routers/org/members.go +++ b/routers/org/members.go @@ -5,12 +5,10 @@ package org import ( - "github.com/go-martini/martini" - "github.com/gogits/gogs/modules/middleware" ) -func Members(ctx *middleware.Context, params martini.Params) { - ctx.Data["Title"] = "Organization " + params["org"] + " Members" +func Members(ctx *middleware.Context) { + ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Members" ctx.HTML(200, "org/members") } diff --git a/routers/org/org.go b/routers/org/org.go index 4c8854974d..74527d6008 100644 --- a/routers/org/org.go +++ b/routers/org/org.go @@ -5,9 +5,7 @@ package org import ( - "github.com/go-martini/martini" - - "github.com/gogits/gogs-ng/models" + "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" @@ -21,10 +19,10 @@ const ( SETTINGS base.TplName = "org/settings" ) -func Home(ctx *middleware.Context, params martini.Params) { - ctx.Data["Title"] = "Organization " + params["org"] +func Home(ctx *middleware.Context) { + ctx.Data["Title"] = "Organization " + ctx.Params(":org") - org, err := models.GetUserByName(params["org"]) + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.Home(GetUserByName)", err) @@ -99,12 +97,12 @@ func NewPost(ctx *middleware.Context, form auth.CreateOrgForm) { ctx.Redirect("/org/" + form.OrgName + "/dashboard") } -func Dashboard(ctx *middleware.Context, params martini.Params) { +func Dashboard(ctx *middleware.Context) { ctx.Data["Title"] = "Dashboard" ctx.Data["PageIsUserDashboard"] = true ctx.Data["PageIsOrgDashboard"] = true - org, err := models.GetUserByName(params["org"]) + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.Dashboard(GetUserByName)", err) @@ -114,11 +112,11 @@ func Dashboard(ctx *middleware.Context, params martini.Params) { return } - // if err := ctx.User.GetOrganizations(); err != nil { - // ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) - // return - // } - // ctx.Data["Orgs"] = ctx.User.Orgs + if err := ctx.User.GetOrganizations(); err != nil { + ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) + return + } + ctx.Data["Orgs"] = ctx.User.Orgs ctx.Data["ContextUser"] = org ctx.Data["MyRepos"], err = models.GetRepositories(org.Id, true) @@ -137,10 +135,10 @@ func Dashboard(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, user.DASHBOARD) } -func Settings(ctx *middleware.Context, params martini.Params) { +func Settings(ctx *middleware.Context) { ctx.Data["Title"] = "Settings" - org, err := models.GetUserByName(params["org"]) + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.Settings(GetUserByName)", err) @@ -154,10 +152,10 @@ func Settings(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, SETTINGS) } -func SettingsPost(ctx *middleware.Context, params martini.Params, form auth.OrgSettingForm) { +func SettingsPost(ctx *middleware.Context, form auth.OrgSettingForm) { ctx.Data["Title"] = "Settings" - org, err := models.GetUserByName(params["org"]) + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.SettingsPost(GetUserByName)", err) @@ -187,10 +185,10 @@ func SettingsPost(ctx *middleware.Context, params martini.Params, form auth.OrgS ctx.Redirect("/org/" + org.Name + "/settings") } -func DeletePost(ctx *middleware.Context, params martini.Params) { +func DeletePost(ctx *middleware.Context) { ctx.Data["Title"] = "Settings" - org, err := models.GetUserByName(params["org"]) + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.DeletePost(GetUserByName)", err) diff --git a/routers/org/teams.go b/routers/org/teams.go index 0b17f46368..d494ddc04d 100644 --- a/routers/org/teams.go +++ b/routers/org/teams.go @@ -5,8 +5,6 @@ package org import ( - "github.com/go-martini/martini" - "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/base" @@ -19,10 +17,10 @@ const ( TEAM_NEW base.TplName = "org/team_new" ) -func Teams(ctx *middleware.Context, params martini.Params) { - ctx.Data["Title"] = "Organization " + params["org"] + " Teams" +func Teams(ctx *middleware.Context) { + ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Teams" - org, err := models.GetUserByName(params["org"]) + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.Teams(GetUserByName)", err) @@ -48,8 +46,8 @@ func Teams(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, TEAMS) } -func NewTeam(ctx *middleware.Context, params martini.Params) { - org, err := models.GetUserByName(params["org"]) +func NewTeam(ctx *middleware.Context) { + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.NewTeam(GetUserByName)", err) @@ -69,8 +67,8 @@ func NewTeam(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, TEAM_NEW) } -func NewTeamPost(ctx *middleware.Context, params martini.Params, form auth.CreateTeamForm) { - org, err := models.GetUserByName(params["org"]) +func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) { + org, err := models.GetUserByName(ctx.Params(":org")) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "org.NewTeamPost(GetUserByName)", err) @@ -125,12 +123,12 @@ func NewTeamPost(ctx *middleware.Context, params martini.Params, form auth.Creat ctx.Redirect("/org/" + org.LowerName + "/teams/" + t.LowerName) } -func EditTeam(ctx *middleware.Context, params martini.Params) { - ctx.Data["Title"] = "Organization " + params["org"] + " Edit Team" +func EditTeam(ctx *middleware.Context) { + ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Edit Team" ctx.HTML(200, "org/edit_team") } -func SingleTeam(ctx *middleware.Context,params martini.Params){ - ctx.Data["Title"] = "single-team"+params["org"] - ctx.HTML(200,"org/team") +func SingleTeam(ctx *middleware.Context) { + ctx.Data["Title"] = "single-team" + ctx.Params(":org") + ctx.HTML(200, "org/team") } diff --git a/routers/repo/commit.go b/routers/repo/commit.go index 71b483823b..6320123b40 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -4,224 +4,221 @@ package repo -// import ( -// "path" +import ( + "path" -// "github.com/Unknwon/com" -// "github.com/go-martini/martini" + "github.com/Unknwon/com" -// "github.com/gogits/gogs/models" -// "github.com/gogits/gogs/modules/base" -// "github.com/gogits/gogs/modules/middleware" -// ) + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/middleware" +) -// const ( -// COMMITS base.TplName = "repo/commits" -// DIFF base.TplName = "repo/diff" -// ) +const ( + COMMITS base.TplName = "repo/commits" + DIFF base.TplName = "repo/diff" +) -// func Commits(ctx *middleware.Context, params martini.Params) { -// ctx.Data["IsRepoToolbarCommits"] = true +func Commits(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarCommits"] = true -// userName := ctx.Repo.Owner.Name -// repoName := ctx.Repo.Repository.Name + userName := ctx.Repo.Owner.Name + repoName := ctx.Repo.Repository.Name -// brs, err := ctx.Repo.GitRepo.GetBranches() -// if err != nil { -// ctx.Handle(500, "repo.Commits(GetBranches)", err) -// return -// } else if len(brs) == 0 { -// ctx.Handle(404, "repo.Commits(GetBranches)", nil) -// return -// } + brs, err := ctx.Repo.GitRepo.GetBranches() + if err != nil { + ctx.Handle(500, "GetBranches", err) + return + } else if len(brs) == 0 { + ctx.Handle(404, "GetBranches", nil) + return + } -// commitsCount, err := ctx.Repo.Commit.CommitsCount() -// if err != nil { -// ctx.Handle(500, "repo.Commits(GetCommitsCount)", err) -// return -// } + commitsCount, err := ctx.Repo.Commit.CommitsCount() + if err != nil { + ctx.Handle(500, "GetCommitsCount", err) + return + } -// // Calculate and validate page number. -// page, _ := com.StrTo(ctx.Query("p")).Int() -// if page < 1 { -// page = 1 -// } -// lastPage := page - 1 -// if lastPage < 0 { -// lastPage = 0 -// } -// nextPage := page + 1 -// if nextPage*50 > commitsCount { -// nextPage = 0 -// } + // Calculate and validate page number. + page, _ := com.StrTo(ctx.Query("p")).Int() + if page < 1 { + page = 1 + } + lastPage := page - 1 + if lastPage < 0 { + lastPage = 0 + } + nextPage := page + 1 + if nextPage*50 > commitsCount { + nextPage = 0 + } -// // Both `git log branchName` and `git log commitId` work. -// // ctx.Data["Commits"], err = ctx.Repo.Commit.CommitsByRange(page) -// // if err != nil { -// // ctx.Handle(500, "repo.Commits(CommitsByRange)", err) -// // return -// // } + // Both `git log branchName` and `git log commitId` work. + ctx.Data["Commits"], err = ctx.Repo.Commit.CommitsByRange(page) + if err != nil { + ctx.Handle(500, "CommitsByRange", err) + return + } -// ctx.Data["Username"] = userName -// ctx.Data["Reponame"] = repoName -// ctx.Data["CommitCount"] = commitsCount -// ctx.Data["LastPageNum"] = lastPage -// ctx.Data["NextPageNum"] = nextPage -// ctx.HTML(200, COMMITS) -// } + ctx.Data["Username"] = userName + ctx.Data["Reponame"] = repoName + ctx.Data["CommitCount"] = commitsCount + ctx.Data["LastPageNum"] = lastPage + ctx.Data["NextPageNum"] = nextPage + ctx.HTML(200, COMMITS) +} -// func SearchCommits(ctx *middleware.Context, params martini.Params) { -// ctx.Data["IsSearchPage"] = true -// ctx.Data["IsRepoToolbarCommits"] = true +func SearchCommits(ctx *middleware.Context) { + ctx.Data["IsSearchPage"] = true + ctx.Data["IsRepoToolbarCommits"] = true -// keyword := ctx.Query("q") -// if len(keyword) == 0 { -// ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName) -// return -// } + keyword := ctx.Query("q") + if len(keyword) == 0 { + ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName) + return + } -// userName := params["username"] -// repoName := params["reponame"] + userName := ctx.Params(":username") + repoName := ctx.Params(":reponame") -// brs, err := ctx.Repo.GitRepo.GetBranches() -// if err != nil { -// ctx.Handle(500, "repo.SearchCommits(GetBranches)", err) -// return -// } else if len(brs) == 0 { -// ctx.Handle(404, "repo.SearchCommits(GetBranches)", nil) -// return -// } + brs, err := ctx.Repo.GitRepo.GetBranches() + if err != nil { + ctx.Handle(500, "GetBranches", err) + return + } else if len(brs) == 0 { + ctx.Handle(404, "GetBranches", nil) + return + } -// // commits, err := ctx.Repo.Commit.SearchCommits(keyword) -// // if err != nil { -// // ctx.Handle(500, "repo.SearchCommits(SearchCommits)", err) -// // return -// // } + commits, err := ctx.Repo.Commit.SearchCommits(keyword) + if err != nil { + ctx.Handle(500, "repo.SearchCommits(SearchCommits)", err) + return + } -// ctx.Data["Keyword"] = keyword -// ctx.Data["Username"] = userName -// ctx.Data["Reponame"] = repoName -// // ctx.Data["CommitCount"] = commits.Len() -// // ctx.Data["Commits"] = commits -// ctx.HTML(200, COMMITS) -// } + ctx.Data["Keyword"] = keyword + ctx.Data["Username"] = userName + ctx.Data["Reponame"] = repoName + ctx.Data["CommitCount"] = commits.Len() + ctx.Data["Commits"] = commits + ctx.HTML(200, COMMITS) +} -// func Diff(ctx *middleware.Context, params martini.Params) { -// ctx.Data["IsRepoToolbarCommits"] = true +func Diff(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarCommits"] = true -// userName := ctx.Repo.Owner.Name -// repoName := ctx.Repo.Repository.Name -// commitId := ctx.Repo.CommitId + userName := ctx.Repo.Owner.Name + repoName := ctx.Repo.Repository.Name + commitId := ctx.Repo.CommitId -// commit := ctx.Repo.Commit + commit := ctx.Repo.Commit -// diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId) -// if err != nil { -// ctx.Handle(404, "repo.Diff(GetDiff)", err) -// return -// } + diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId) + if err != nil { + ctx.Handle(404, "GetDiff", err) + return + } -// isImageFile := func(name string) bool { -// // blob, err := ctx.Repo.Commit.GetBlobByPath(name) -// // if err != nil { -// // return false -// // } + isImageFile := func(name string) bool { + blob, err := ctx.Repo.Commit.GetBlobByPath(name) + if err != nil { + return false + } -// // dataRc, err := blob.Data() -// // if err != nil { -// // return false -// // } -// // buf := make([]byte, 1024) -// // n, _ := dataRc.Read(buf) -// // if n > 0 { -// // buf = buf[:n] -// // } -// // dataRc.Close() -// // _, isImage := base.IsImageFile(buf) -// // return isImage -// return false -// } + dataRc, err := blob.Data() + if err != nil { + return false + } + buf := make([]byte, 1024) + n, _ := dataRc.Read(buf) + if n > 0 { + buf = buf[:n] + } + _, isImage := base.IsImageFile(buf) + return isImage + } -// parents := make([]string, commit.ParentCount()) -// for i := 0; i < commit.ParentCount(); i++ { -// sha, err := commit.ParentId(i) -// parents[i] = sha.String() -// if err != nil { -// ctx.Handle(404, "repo.Diff", err) -// return -// } -// } + parents := make([]string, commit.ParentCount()) + for i := 0; i < commit.ParentCount(); i++ { + sha, err := commit.ParentId(i) + parents[i] = sha.String() + if err != nil { + ctx.Handle(404, "repo.Diff", err) + return + } + } -// ctx.Data["Username"] = userName -// ctx.Data["Reponame"] = repoName -// ctx.Data["IsImageFile"] = isImageFile -// ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitId) -// ctx.Data["Commit"] = commit -// ctx.Data["Diff"] = diff -// ctx.Data["Parents"] = parents -// ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 -// ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId) -// ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", commitId) -// ctx.HTML(200, DIFF) -// } + ctx.Data["Username"] = userName + ctx.Data["Reponame"] = repoName + ctx.Data["IsImageFile"] = isImageFile + ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitId) + ctx.Data["Commit"] = commit + ctx.Data["Diff"] = diff + ctx.Data["Parents"] = parents + ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 + ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId) + ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", commitId) + ctx.HTML(200, DIFF) +} -// func FileHistory(ctx *middleware.Context, params martini.Params) { -// ctx.Data["IsRepoToolbarCommits"] = true +func FileHistory(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarCommits"] = true -// fileName := params["_1"] -// if len(fileName) == 0 { -// Commits(ctx, params) -// return -// } + fileName := ctx.Params("*") + if len(fileName) == 0 { + Commits(ctx) + return + } -// userName := ctx.Repo.Owner.Name -// repoName := ctx.Repo.Repository.Name -// branchName := params["branchname"] + userName := ctx.Repo.Owner.Name + repoName := ctx.Repo.Repository.Name + branchName := ctx.Params(":branchname") -// brs, err := ctx.Repo.GitRepo.GetBranches() -// if err != nil { -// ctx.Handle(500, "repo.FileHistory", err) -// return -// } else if len(brs) == 0 { -// ctx.Handle(404, "repo.FileHistory", nil) -// return -// } + brs, err := ctx.Repo.GitRepo.GetBranches() + if err != nil { + ctx.Handle(500, "GetBranches", err) + return + } else if len(brs) == 0 { + ctx.Handle(404, "GetBranches", nil) + return + } -// // commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName) -// // if err != nil { -// // ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err) -// // return -// // } else if commitsCount == 0 { -// // ctx.Handle(404, "repo.FileHistory", nil) -// // return -// // } + commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName) + if err != nil { + ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err) + return + } else if commitsCount == 0 { + ctx.Handle(404, "repo.FileHistory", nil) + return + } -// // Calculate and validate page number. -// // page, _ := base.StrTo(ctx.Query("p")).Int() -// // if page < 1 { -// // page = 1 -// // } -// // lastPage := page - 1 -// // if lastPage < 0 { -// // lastPage = 0 -// // } -// // nextPage := page + 1 -// // if nextPage*50 > commitsCount { -// // nextPage = 0 -// // } + // Calculate and validate page number. + page := com.StrTo(ctx.Query("p")).MustInt() + if page < 1 { + page = 1 + } + lastPage := page - 1 + if lastPage < 0 { + lastPage = 0 + } + nextPage := page + 1 + if nextPage*50 > commitsCount { + nextPage = 0 + } -// // ctx.Data["Commits"], err = ctx.Repo.GitRepo.CommitsByFileAndRange( -// // branchName, fileName, page) -// // if err != nil { -// // ctx.Handle(500, "repo.FileHistory(CommitsByRange)", err) -// // return -// // } + ctx.Data["Commits"], err = ctx.Repo.GitRepo.CommitsByFileAndRange( + branchName, fileName, page) + if err != nil { + ctx.Handle(500, "repo.FileHistory(CommitsByRange)", err) + return + } -// ctx.Data["Username"] = userName -// ctx.Data["Reponame"] = repoName -// ctx.Data["FileName"] = fileName -// // ctx.Data["CommitCount"] = commitsCount -// // ctx.Data["LastPageNum"] = lastPage -// // ctx.Data["NextPageNum"] = nextPage -// ctx.HTML(200, COMMITS) -// } + ctx.Data["Username"] = userName + ctx.Data["Reponame"] = repoName + ctx.Data["FileName"] = fileName + ctx.Data["CommitCount"] = commitsCount + ctx.Data["LastPageNum"] = lastPage + ctx.Data["NextPageNum"] = nextPage + ctx.HTML(200, COMMITS) +} diff --git a/routers/repo/download.go b/routers/repo/download.go index 42bce2b1ff..abb9b06292 100644 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -5,50 +5,41 @@ package repo import ( - // "io" - // "os" - // "path/filepath" + "io" + "path" - // "github.com/Unknwon/com" - - // "github.com/gogits/git" - - // "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/middleware" ) func SingleDownload(ctx *middleware.Context) { - // treename := params["_1"] + treename := ctx.Params("*") - // blob, err := ctx.Repo.Commit.GetBlobByPath(treename) - // if err != nil { - // ctx.Handle(500, "repo.SingleDownload(GetBlobByPath)", err) - // return - // } + blob, err := ctx.Repo.Commit.GetBlobByPath(treename) + if err != nil { + ctx.Handle(500, "GetBlobByPath", err) + return + } - // dataRc, err := blob.Data() - // if err != nil { - // ctx.Handle(500, "repo.SingleDownload(Data)", err) - // return - // } + dataRc, err := blob.Data() + if err != nil { + ctx.Handle(500, "repo.SingleDownload(Data)", err) + return + } - // buf := make([]byte, 1024) - // n, _ := dataRc.Read(buf) - // if n > 0 { - // buf = buf[:n] - // } + buf := make([]byte, 1024) + n, _ := dataRc.Read(buf) + if n > 0 { + buf = buf[:n] + } - // defer func() { - // dataRc.Close() - // }() - - // contentType, isTextFile := base.IsTextFile(buf) - // _, isImageFile := base.IsImageFile(buf) - // ctx.Res.Header().Set("Content-Type", contentType) - // if !isTextFile && !isImageFile { - // ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(treename)) - // ctx.Res.Header().Set("Content-Transfer-Encoding", "binary") - // } - // ctx.Res.Write(buf) - // io.Copy(ctx.Res, dataRc) + contentType, isTextFile := base.IsTextFile(buf) + _, isImageFile := base.IsImageFile(buf) + ctx.Resp.Header().Set("Content-Type", contentType) + if !isTextFile && !isImageFile { + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+path.Base(treename)) + ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary") + } + ctx.Resp.Write(buf) + io.Copy(ctx.Resp, dataRc) } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 005596bdeb..ab9b2b646e 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -4,1114 +4,1118 @@ package repo -// import ( -// "errors" -// // "fmt" -// // "io" -// // "io/ioutil" -// // "mime" -// "net/url" -// // "strings" -// // "time" - -// "github.com/Unknwon/com" -// "github.com/go-martini/martini" - -// // "github.com/gogits/gogs-ng/models" -// "github.com/gogits/gogs/modules/auth" -// "github.com/gogits/gogs/modules/base" -// // "github.com/gogits/gogs/modules/log" -// // "github.com/gogits/gogs/modules/mailer" -// "github.com/gogits/gogs/modules/middleware" -// // "github.com/gogits/gogs/modules/setting" -// ) - -// const ( -// ISSUES base.TplName = "repo/issue/list" -// ISSUE_CREATE base.TplName = "repo/issue/create" -// ISSUE_VIEW base.TplName = "repo/issue/view" - -// MILESTONE base.TplName = "repo/issue/milestone" -// MILESTONE_NEW base.TplName = "repo/issue/milestone_new" -// MILESTONE_EDIT base.TplName = "repo/issue/milestone_edit" -// ) - -// var ( -// ErrFileTypeForbidden = errors.New("File type is not allowed") -// ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded") -// ) - -// func Issues(ctx *middleware.Context) { -// ctx.Data["Title"] = "Issues" -// ctx.Data["IsRepoToolbarIssues"] = true -// ctx.Data["IsRepoToolbarIssuesList"] = true - -// viewType := ctx.Query("type") -// types := []string{"assigned", "created_by", "mentioned"} -// if !com.IsSliceContainsStr(types, viewType) { -// viewType = "all" -// } - -// isShowClosed := ctx.Query("state") == "closed" - -// if viewType != "all" && !ctx.IsSigned { -// ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) -// ctx.Redirect("/user/login") -// return -// } - -// // var assigneeId, posterId int64 -// // var filterMode int -// // switch viewType { -// // case "assigned": -// // assigneeId = ctx.User.Id -// // filterMode = models.FM_ASSIGN -// // case "created_by": -// // posterId = ctx.User.Id -// // filterMode = models.FM_CREATE -// // case "mentioned": -// // filterMode = models.FM_MENTION -// // } - -// // var mid int64 -// // midx, _ := com.StrTo(ctx.Query("milestone")).Int64() -// // if midx > 0 { -// // mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, midx) -// // if err != nil { -// // ctx.Handle(500, "issue.Issues(GetMilestoneByIndex): %v", err) -// // return -// // } -// // mid = mile.Id -// // } - -// // selectLabels := ctx.Query("labels") -// // labels, err := models.GetLabels(ctx.Repo.Repository.Id) -// // if err != nil { -// // ctx.Handle(500, "issue.Issues(GetLabels): %v", err) -// // return -// // } -// // for _, l := range labels { -// // l.CalOpenIssues() -// // } -// // ctx.Data["Labels"] = labels - -// page, _ := com.StrTo(ctx.Query("page")).Int() - -// // Get issues. -// // issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page, -// // isShowClosed, selectLabels, ctx.Query("sortType")) -// // if err != nil { -// // ctx.Handle(500, "issue.Issues(GetIssues): %v", err) -// // return -// // } - -// // Get issue-user pairs. -// // pairs, err := models.GetIssueUserPairs(ctx.Repo.Repository.Id, posterId, isShowClosed) -// // if err != nil { -// // ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err) -// // return -// // } - -// // Get posters. -// // for i := range issues { -// // if err = issues[i].GetLabels(); err != nil { -// // ctx.Handle(500, "issue.Issues(GetLabels)", fmt.Errorf("[#%d]%v", issues[i].Id, err)) -// // return -// // } - -// // idx := models.PairsContains(pairs, issues[i].Id) - -// // if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) { -// // continue -// // } - -// // if idx > -1 { -// // issues[i].IsRead = pairs[idx].IsRead -// // } else { -// // issues[i].IsRead = true -// // } - -// // if err = issues[i].GetPoster(); err != nil { -// // ctx.Handle(500, "issue.Issues(GetPoster)", fmt.Errorf("[#%d]%v", issues[i].Id, err)) -// // return -// // } -// // } - -// // var uid int64 = -1 -// // if ctx.User != nil { -// // uid = ctx.User.Id -// // } -// // issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode) -// // ctx.Data["IssueStats"] = issueStats -// // ctx.Data["SelectLabels"], _ = com.StrTo(selectLabels).Int64() -// // ctx.Data["ViewType"] = viewType -// // ctx.Data["Issues"] = issues -// // ctx.Data["IsShowClosed"] = isShowClosed -// // if isShowClosed { -// // ctx.Data["State"] = "closed" -// // ctx.Data["ShowCount"] = issueStats.ClosedCount -// // } else { -// // ctx.Data["ShowCount"] = issueStats.OpenCount -// // } -// // ctx.HTML(200, ISSUES) -// } - -// func CreateIssue(ctx *middleware.Context, params martini.Params) { -// // ctx.Data["Title"] = "Create issue" -// // ctx.Data["IsRepoToolbarIssues"] = true -// // ctx.Data["IsRepoToolbarIssuesList"] = false -// // ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled - -// // var err error -// // // Get all milestones. -// // ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false) -// // if err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err) -// // return -// // } -// // ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true) -// // if err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err) -// // return -// // } - -// // us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) -// // if err != nil { -// // ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) -// // return -// // } - -// // ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes -// // ctx.Data["Collaborators"] = us - -// // ctx.HTML(200, ISSUE_CREATE) -// } - -// func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { -// // send := func(status int, data interface{}, err error) { -// // if err != nil { -// // log.Error(4, "issue.CreateIssuePost(?): %s", err.Error()) - -// // ctx.JSON(status, map[string]interface{}{ -// // "ok": false, -// // "status": status, -// // "error": err.Error(), -// // }) -// // } else { -// // ctx.JSON(status, map[string]interface{}{ -// // "ok": true, -// // "status": status, -// // "data": data, -// // }) -// // } -// // } - -// // var err error -// // // Get all milestones. -// // _, err = models.GetMilestones(ctx.Repo.Repository.Id, false) -// // if err != nil { -// // send(500, nil, err) -// // return -// // } -// // _, err = models.GetMilestones(ctx.Repo.Repository.Id, true) -// // if err != nil { -// // send(500, nil, err) -// // return -// // } - -// // _, err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) -// // if err != nil { -// // send(500, nil, err) -// // return -// // } - -// // if ctx.HasError() { -// // send(400, nil, errors.New(ctx.Flash.ErrorMsg)) -// // return -// // } - -// // // Only collaborators can assign. -// // if !ctx.Repo.IsOwner { -// // form.AssigneeId = 0 -// // } -// // issue := &models.Issue{ -// // RepoId: ctx.Repo.Repository.Id, -// // Index: int64(ctx.Repo.Repository.NumIssues) + 1, -// // Name: form.IssueName, -// // PosterId: ctx.User.Id, -// // MilestoneId: form.MilestoneId, -// // AssigneeId: form.AssigneeId, -// // LabelIds: form.Labels, -// // Content: form.Content, -// // } -// // if err := models.NewIssue(issue); err != nil { -// // send(500, nil, err) -// // return -// // } else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id, -// // ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil { -// // send(500, nil, err) -// // return -// // } - -// // if setting.AttachmentEnabled { -// // uploadFiles(ctx, issue.Id, 0) -// // } - -// // // Update mentions. -// // ms := base.MentionPattern.FindAllString(issue.Content, -1) -// // if len(ms) > 0 { -// // for i := range ms { -// // ms[i] = ms[i][1:] -// // } - -// // if err := models.UpdateMentions(ms, issue.Id); err != nil { -// // send(500, nil, err) -// // return -// // } -// // } - -// // act := &models.Action{ -// // ActUserId: ctx.User.Id, -// // ActUserName: ctx.User.Name, -// // ActEmail: ctx.User.Email, -// // OpType: models.OP_CREATE_ISSUE, -// // Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name), -// // RepoId: ctx.Repo.Repository.Id, -// // RepoUserName: ctx.Repo.Owner.Name, -// // RepoName: ctx.Repo.Repository.Name, -// // RefName: ctx.Repo.BranchName, -// // IsPrivate: ctx.Repo.Repository.IsPrivate, -// // } -// // // Notify watchers. -// // if err := models.NotifyWatchers(act); err != nil { -// // send(500, nil, err) -// // return -// // } - -// // // Mail watchers and mentions. -// // if setting.Service.EnableNotifyMail { -// // tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) -// // if err != nil { -// // send(500, nil, err) -// // return -// // } - -// // tos = append(tos, ctx.User.LowerName) -// // newTos := make([]string, 0, len(ms)) -// // for _, m := range ms { -// // if com.IsSliceContainsStr(tos, m) { -// // continue -// // } - -// // newTos = append(newTos, m) -// // } -// // if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, -// // ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { -// // send(500, nil, err) -// // return -// // } -// // } -// // log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id) - -// // send(200, fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index), nil) -// } - -// // 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) { -// // ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled - -// // idx, _ := base.StrTo(params["index"]).Int64() -// // if idx == 0 { -// // ctx.Handle(404, "issue.ViewIssue", nil) -// // return -// // } - -// // issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) -// // if err != nil { -// // if err == models.ErrIssueNotExist { -// // ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err) -// // } else { -// // ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err) -// // } -// // 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) -// // if err != nil { -// // if err == models.ErrMilestoneNotExist { -// // log.Warn("issue.ViewIssue(GetMilestoneById): %v", err) -// // } else { -// // ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err) -// // return -// // } -// // } -// // } - -// // // Get all milestones. -// // ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false) -// // if err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err) -// // return -// // } -// // ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true) -// // if err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err) -// // return -// // } - -// // // Get all collaborators. -// // ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) -// // if err != nil { -// // ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) -// // return -// // } - -// // if ctx.IsSigned { -// // // Update issue-user. -// // if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil { -// // ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err) -// // return -// // } -// // } - -// // // Get poster and Assignee. -// // if err = issue.GetPoster(); err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err) -// // return -// // } else if err = issue.GetAssignee(); err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err) -// // return -// // } -// // issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) - -// // // Get comments. -// // comments, err := models.GetIssueComments(issue.Id) -// // if err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err) -// // return -// // } - -// // // Get posters. -// // for i := range comments { -// // u, err := models.GetUserById(comments[i].PosterId) -// // if err != nil { -// // ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err) -// // return -// // } -// // comments[i].Poster = u - -// // if comments[i].Type == models.COMMENT { -// // comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) -// // } -// // } - -// // ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes - -// // ctx.Data["Title"] = issue.Name -// // ctx.Data["Issue"] = issue -// // ctx.Data["Comments"] = comments -// // ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id) -// // ctx.Data["IsRepoToolbarIssues"] = true -// // ctx.Data["IsRepoToolbarIssuesList"] = false -// // ctx.HTML(200, ISSUE_VIEW) -// // } - -// // func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { -// // 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.UpdateIssue", err) -// // } else { -// // ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err) -// // } -// // return -// // } - -// // if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner { -// // ctx.Error(403) -// // return -// // } - -// // issue.Name = form.IssueName -// // issue.MilestoneId = form.MilestoneId -// // issue.AssigneeId = form.AssigneeId -// // issue.LabelIds = form.Labels -// // issue.Content = form.Content -// // // try get content from text, ignore conflict with preview ajax -// // if form.Content == "" { -// // issue.Content = ctx.Query("text") -// // } -// // if err = models.UpdateIssue(issue); err != nil { -// // ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err) -// // return -// // } - -// // ctx.JSON(200, map[string]interface{}{ -// // "ok": true, -// // "title": issue.Name, -// // "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)), -// // }) -// // } - -// // 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(GetIssueByIndex)", err) -// // } else { -// // ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err) -// // } -// // return -// // } - -// // isAttach := ctx.Query("action") == "attach" -// // labelStrId := ctx.Query("id") -// // labelId, _ := base.StrTo(labelStrId).Int64() -// // label, err := models.GetLabelById(labelId) -// // if err != nil { -// // if err == models.ErrLabelNotExist { -// // ctx.Handle(404, "issue.UpdateIssueLabel(GetLabelById)", err) -// // } else { -// // ctx.Handle(500, "issue.UpdateIssueLabel(GetLabelById)", err) -// // } -// // return -// // } - -// // 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 -// // } - -// // if isAttach { -// // label.NumIssues++ -// // if issue.IsClosed { -// // label.NumClosedIssues++ -// // } -// // } else { -// // label.NumIssues-- -// // if issue.IsClosed { -// // label.NumClosedIssues-- -// // } -// // } -// // if err = models.UpdateLabel(label); err != nil { -// // ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err) -// // return -// // } -// // } -// // ctx.JSON(200, map[string]interface{}{ -// // "ok": true, -// // }) -// // } - -// // func UpdateIssueMilestone(ctx *middleware.Context) { -// // if !ctx.Repo.IsOwner { -// // ctx.Error(403) -// // return -// // } - -// // issueId, err := base.StrTo(ctx.Query("issue")).Int64() -// // if err != nil { -// // ctx.Error(404) -// // return -// // } - -// // issue, err := models.GetIssueById(issueId) -// // if err != nil { -// // if err == models.ErrIssueNotExist { -// // ctx.Handle(404, "issue.UpdateIssueMilestone(GetIssueById)", err) -// // } else { -// // ctx.Handle(500, "issue.UpdateIssueMilestone(GetIssueById)", err) -// // } -// // return -// // } - -// // oldMid := issue.MilestoneId -// // mid, _ := base.StrTo(ctx.Query("milestone")).Int64() -// // if oldMid == mid { -// // ctx.JSON(200, map[string]interface{}{ -// // "ok": true, -// // }) -// // return -// // } - -// // // Not check for invalid milestone id and give responsibility to owners. -// // issue.MilestoneId = mid -// // if err = models.ChangeMilestoneAssign(oldMid, mid, issue); err != nil { -// // ctx.Handle(500, "issue.UpdateIssueMilestone(ChangeMilestoneAssign)", err) -// // return -// // } else if err = models.UpdateIssue(issue); err != nil { -// // ctx.Handle(500, "issue.UpdateIssueMilestone(UpdateIssue)", err) -// // return -// // } - -// // ctx.JSON(200, map[string]interface{}{ -// // "ok": true, -// // }) -// // } - -// // func UpdateAssignee(ctx *middleware.Context) { -// // if !ctx.Repo.IsOwner { -// // ctx.Error(403) -// // return -// // } - -// // issueId, err := base.StrTo(ctx.Query("issue")).Int64() -// // if err != nil { -// // ctx.Error(404) -// // return -// // } - -// // issue, err := models.GetIssueById(issueId) -// // if err != nil { -// // if err == models.ErrIssueNotExist { -// // ctx.Handle(404, "issue.UpdateAssignee(GetIssueById)", err) -// // } else { -// // ctx.Handle(500, "issue.UpdateAssignee(GetIssueById)", err) -// // } -// // return -// // } - -// // aid, _ := base.StrTo(ctx.Query("assigneeid")).Int64() -// // // Not check for invalid assignne id and give responsibility to owners. -// // issue.AssigneeId = aid -// // if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil { -// // ctx.Handle(500, "issue.UpdateAssignee(UpdateIssueUserPairByAssignee): %v", err) -// // return -// // } else if err = models.UpdateIssue(issue); err != nil { -// // ctx.Handle(500, "issue.UpdateAssignee(UpdateIssue)", err) -// // return -// // } - -// // ctx.JSON(200, map[string]interface{}{ -// // "ok": true, -// // }) -// // } - -// // func uploadFiles(ctx *middleware.Context, issueId, commentId int64) { -// // if !setting.AttachmentEnabled { -// // return -// // } - -// // allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|") -// // attachments := ctx.Req.MultipartForm.File["attachments"] - -// // if len(attachments) > setting.AttachmentMaxFiles { -// // ctx.Handle(400, "issue.Comment", ErrTooManyFiles) -// // return -// // } - -// // for _, header := range attachments { -// // file, err := header.Open() - -// // if err != nil { -// // ctx.Handle(500, "issue.Comment(header.Open)", err) -// // return -// // } - -// // defer file.Close() - -// // allowed := false -// // fileType := mime.TypeByExtension(header.Filename) - -// // for _, t := range allowedTypes { -// // t := strings.Trim(t, " ") - -// // if t == "*/*" || t == fileType { -// // allowed = true -// // break -// // } -// // } - -// // if !allowed { -// // ctx.Handle(400, "issue.Comment", ErrFileTypeForbidden) -// // return -// // } - -// // out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_") - -// // if err != nil { -// // ctx.Handle(500, "issue.Comment(ioutil.TempFile)", err) -// // return -// // } - -// // defer out.Close() - -// // _, err = io.Copy(out, file) - -// // if err != nil { -// // ctx.Handle(500, "issue.Comment(io.Copy)", err) -// // return -// // } - -// // _, err = models.CreateAttachment(issueId, commentId, header.Filename, out.Name()) - -// // if err != nil { -// // ctx.Handle(500, "issue.Comment(io.Copy)", err) -// // return -// // } -// // } -// // } - -// // func Comment(ctx *middleware.Context, params martini.Params) { -// // send := func(status int, data interface{}, err error) { -// // if err != nil { -// // log.Error("issue.Comment(?): %s", err.Error()) - -// // ctx.JSON(status, map[string]interface{}{ -// // "ok": false, -// // "status": status, -// // "error": err.Error(), -// // }) -// // } else { -// // ctx.JSON(status, map[string]interface{}{ -// // "ok": true, -// // "status": status, -// // "data": data, -// // }) -// // } -// // } - -// // index, err := base.StrTo(ctx.Query("issueIndex")).Int64() -// // if err != nil { -// // send(404, nil, err) -// // return -// // } - -// // issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) -// // if err != nil { -// // if err == models.ErrIssueNotExist { -// // send(404, nil, err) -// // } else { -// // send(200, nil, err) -// // } - -// // return -// // } - -// // // Check if issue owner changes the status of issue. -// // var newStatus string -// // if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id { -// // newStatus = ctx.Query("change_status") -// // } -// // if len(newStatus) > 0 { -// // if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) || -// // (strings.Contains(newStatus, "Close") && !issue.IsClosed) { -// // issue.IsClosed = !issue.IsClosed -// // if err = models.UpdateIssue(issue); err != nil { -// // send(500, nil, err) -// // return -// // } else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil { -// // send(500, nil, err) -// // return -// // } - -// // // Change open/closed issue counter for the associated milestone -// // if issue.MilestoneId > 0 { -// // if err = models.ChangeMilestoneIssueStats(issue); err != nil { -// // send(500, nil, err) -// // } -// // } - -// // cmtType := models.CLOSE -// // if !issue.IsClosed { -// // cmtType = models.REOPEN -// // } - -// // if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil { -// // send(200, nil, err) -// // return -// // } -// // log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed) -// // } -// // } - -// // var comment *models.Comment - -// // var ms []string -// // content := ctx.Query("content") -// // // Fix #321. Allow empty comments, as long as we have attachments. -// // if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 { -// // switch params["action"] { -// // case "new": -// // if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.COMMENT, content, nil); err != nil { -// // send(500, nil, err) -// // return -// // } - -// // // Update mentions. -// // ms = base.MentionPattern.FindAllString(issue.Content, -1) -// // if len(ms) > 0 { -// // for i := range ms { -// // ms[i] = ms[i][1:] -// // } - -// // if err := models.UpdateMentions(ms, issue.Id); err != nil { -// // send(500, nil, err) -// // return -// // } -// // } - -// // log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id) -// // default: -// // ctx.Handle(404, "issue.Comment", err) -// // return -// // } -// // } - -// // if comment != nil { -// // uploadFiles(ctx, issue.Id, comment.Id) -// // } - -// // // Notify watchers. -// // act := &models.Action{ -// // ActUserId: ctx.User.Id, -// // ActUserName: ctx.User.LowerName, -// // ActEmail: ctx.User.Email, -// // OpType: models.OP_COMMENT_ISSUE, -// // Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]), -// // RepoId: ctx.Repo.Repository.Id, -// // RepoUserName: ctx.Repo.Owner.LowerName, -// // RepoName: ctx.Repo.Repository.LowerName, -// // } -// // if err = models.NotifyWatchers(act); err != nil { -// // send(500, nil, err) -// // return -// // } - -// // // Mail watchers and mentions. -// // if setting.Service.EnableNotifyMail { -// // issue.Content = content -// // tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) -// // if err != nil { -// // send(500, nil, err) -// // return -// // } - -// // tos = append(tos, ctx.User.LowerName) -// // newTos := make([]string, 0, len(ms)) -// // for _, m := range ms { -// // if com.IsSliceContainsStr(tos, m) { -// // continue -// // } - -// // newTos = append(newTos, m) -// // } -// // if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, -// // ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { -// // send(500, nil, err) -// // return -// // } -// // } - -// // send(200, fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index), nil) -// // } - -// // func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) { -// // if ctx.HasError() { -// // Issues(ctx) -// // return -// // } - -// // l := &models.Label{ -// // RepoId: ctx.Repo.Repository.Id, -// // Name: form.Title, -// // Color: form.Color, -// // } -// // if err := models.NewLabel(l); err != nil { -// // ctx.Handle(500, "issue.NewLabel(NewLabel)", err) -// // return -// // } -// // ctx.Redirect(ctx.Repo.RepoLink + "/issues") -// // } - -// // 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) { -// // removes := ctx.Query("remove") -// // if len(strings.TrimSpace(removes)) == 0 { -// // ctx.JSON(200, map[string]interface{}{ -// // "ok": true, -// // }) -// // return -// // } - -// // strIds := strings.Split(removes, ",") -// // 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) { -// // ctx.Data["Title"] = "Milestones" -// // ctx.Data["IsRepoToolbarIssues"] = true -// // ctx.Data["IsRepoToolbarIssuesList"] = true - -// // isShowClosed := ctx.Query("state") == "closed" - -// // miles, err := models.GetMilestones(ctx.Repo.Repository.Id, isShowClosed) -// // if err != nil { -// // ctx.Handle(500, "issue.Milestones(GetMilestones)", err) -// // return -// // } -// // for _, m := range miles { -// // m.RenderedContent = string(base.RenderSpecialLink([]byte(m.Content), ctx.Repo.RepoLink)) -// // m.CalOpenIssues() -// // } -// // ctx.Data["Milestones"] = miles - -// // if isShowClosed { -// // ctx.Data["State"] = "closed" -// // } else { -// // ctx.Data["State"] = "open" -// // } -// // ctx.HTML(200, MILESTONE) -// // } - -// // func NewMilestone(ctx *middleware.Context) { -// // ctx.Data["Title"] = "New Milestone" -// // ctx.Data["IsRepoToolbarIssues"] = true -// // ctx.Data["IsRepoToolbarIssuesList"] = true -// // ctx.HTML(200, MILESTONE_NEW) -// // } - -// // func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) { -// // ctx.Data["Title"] = "New Milestone" -// // ctx.Data["IsRepoToolbarIssues"] = true -// // ctx.Data["IsRepoToolbarIssuesList"] = true - -// // if ctx.HasError() { -// // ctx.HTML(200, MILESTONE_NEW) -// // return -// // } - -// // var deadline time.Time -// // var err error -// // if len(form.Deadline) == 0 { -// // form.Deadline = "12/31/9999" -// // } -// // deadline, err = time.Parse("01/02/2006", form.Deadline) -// // if err != nil { -// // ctx.Handle(500, "issue.NewMilestonePost(time.Parse)", err) -// // return -// // } - -// // mile := &models.Milestone{ -// // RepoId: ctx.Repo.Repository.Id, -// // Index: int64(ctx.Repo.Repository.NumMilestones) + 1, -// // Name: form.Title, -// // Content: form.Content, -// // Deadline: deadline, -// // } -// // if err = models.NewMilestone(mile); err != nil { -// // ctx.Handle(500, "issue.NewMilestonePost(NewMilestone)", err) -// // return -// // } - -// // ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones") -// // } - -// // func UpdateMilestone(ctx *middleware.Context, params martini.Params) { -// // ctx.Data["Title"] = "Update Milestone" -// // ctx.Data["IsRepoToolbarIssues"] = true -// // ctx.Data["IsRepoToolbarIssuesList"] = true - -// // idx, _ := base.StrTo(params["index"]).Int64() -// // if idx == 0 { -// // ctx.Handle(404, "issue.UpdateMilestone", nil) -// // return -// // } - -// // mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx) -// // if err != nil { -// // if err == models.ErrMilestoneNotExist { -// // ctx.Handle(404, "issue.UpdateMilestone(GetMilestoneByIndex)", err) -// // } else { -// // ctx.Handle(500, "issue.UpdateMilestone(GetMilestoneByIndex)", err) -// // } -// // return -// // } - -// // action := params["action"] -// // if len(action) > 0 { -// // switch action { -// // case "open": -// // if mile.IsClosed { -// // if err = models.ChangeMilestoneStatus(mile, false); err != nil { -// // ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err) -// // return -// // } -// // } -// // case "close": -// // if !mile.IsClosed { -// // mile.ClosedDate = time.Now() -// // if err = models.ChangeMilestoneStatus(mile, true); err != nil { -// // ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err) -// // return -// // } -// // } -// // case "delete": -// // if err = models.DeleteMilestone(mile); err != nil { -// // ctx.Handle(500, "issue.UpdateMilestone(DeleteMilestone)", err) -// // return -// // } -// // } -// // ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones") -// // return -// // } - -// // mile.DeadlineString = mile.Deadline.UTC().Format("01/02/2006") -// // if mile.DeadlineString == "12/31/9999" { -// // mile.DeadlineString = "" -// // } -// // ctx.Data["Milestone"] = mile - -// // ctx.HTML(200, MILESTONE_EDIT) -// // } - -// // func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form auth.CreateMilestoneForm) { -// // ctx.Data["Title"] = "Update Milestone" -// // ctx.Data["IsRepoToolbarIssues"] = true -// // ctx.Data["IsRepoToolbarIssuesList"] = true - -// // idx, _ := base.StrTo(params["index"]).Int64() -// // if idx == 0 { -// // ctx.Handle(404, "issue.UpdateMilestonePost", nil) -// // return -// // } - -// // mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx) -// // if err != nil { -// // if err == models.ErrMilestoneNotExist { -// // ctx.Handle(404, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err) -// // } else { -// // ctx.Handle(500, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err) -// // } -// // return -// // } - -// // if ctx.HasError() { -// // ctx.HTML(200, MILESTONE_EDIT) -// // return -// // } - -// // var deadline time.Time -// // if len(form.Deadline) == 0 { -// // form.Deadline = "12/31/9999" -// // } -// // deadline, err = time.Parse("01/02/2006", form.Deadline) -// // if err != nil { -// // ctx.Handle(500, "issue.UpdateMilestonePost(time.Parse)", err) -// // return -// // } - -// // mile.Name = form.Title -// // mile.Content = form.Content -// // mile.Deadline = deadline -// // if err = models.UpdateMilestone(mile); err != nil { -// // ctx.Handle(500, "issue.UpdateMilestonePost(UpdateMilestone)", err) -// // return -// // } - -// // ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones") -// // } - -// // func IssueGetAttachment(ctx *middleware.Context, params martini.Params) { -// // id, err := base.StrTo(params["id"]).Int64() - -// // if err != nil { -// // ctx.Handle(400, "issue.IssueGetAttachment(base.StrTo.Int64)", err) -// // return -// // } - -// // attachment, err := models.GetAttachmentById(id) - -// // if err != nil { -// // ctx.Handle(404, "issue.IssueGetAttachment(models.GetAttachmentById)", err) -// // return -// // } - -// // // Fix #312. Attachments with , in their name are not handled correctly by Google Chrome. -// // // We must put the name in " manually. -// // ctx.ServeFile(attachment.Path, "\""+attachment.Name+"\"") -// // } +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/Unknwon/com" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/log" + "github.com/gogits/gogs/modules/mailer" + "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/modules/setting" +) + +const ( + ISSUES base.TplName = "repo/issue/list" + ISSUE_CREATE base.TplName = "repo/issue/create" + ISSUE_VIEW base.TplName = "repo/issue/view" + + MILESTONE base.TplName = "repo/issue/milestone" + MILESTONE_NEW base.TplName = "repo/issue/milestone_new" + MILESTONE_EDIT base.TplName = "repo/issue/milestone_edit" +) + +var ( + ErrFileTypeForbidden = errors.New("File type is not allowed") + ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded") +) + +func Issues(ctx *middleware.Context) { + ctx.Data["Title"] = "Issues" + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = true + + viewType := ctx.Query("type") + types := []string{"assigned", "created_by", "mentioned"} + if !com.IsSliceContainsStr(types, viewType) { + viewType = "all" + } + + isShowClosed := ctx.Query("state") == "closed" + + if viewType != "all" && !ctx.IsSigned { + ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) + ctx.Redirect("/user/login") + return + } + + var assigneeId, posterId int64 + var filterMode int + switch viewType { + case "assigned": + assigneeId = ctx.User.Id + filterMode = models.FM_ASSIGN + case "created_by": + posterId = ctx.User.Id + filterMode = models.FM_CREATE + case "mentioned": + filterMode = models.FM_MENTION + } + + var mid int64 + midx, _ := com.StrTo(ctx.Query("milestone")).Int64() + if midx > 0 { + mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, midx) + if err != nil { + ctx.Handle(500, "issue.Issues(GetMilestoneByIndex): %v", err) + return + } + mid = mile.Id + } + + selectLabels := ctx.Query("labels") + labels, err := models.GetLabels(ctx.Repo.Repository.Id) + if err != nil { + ctx.Handle(500, "issue.Issues(GetLabels): %v", err) + return + } + for _, l := range labels { + l.CalOpenIssues() + } + ctx.Data["Labels"] = labels + + page, _ := com.StrTo(ctx.Query("page")).Int() + + // Get issues. + issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page, + isShowClosed, selectLabels, ctx.Query("sortType")) + if err != nil { + ctx.Handle(500, "issue.Issues(GetIssues): %v", err) + return + } + + // Get issue-user pairs. + pairs, err := models.GetIssueUserPairs(ctx.Repo.Repository.Id, posterId, isShowClosed) + if err != nil { + ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err) + return + } + + // Get posters. + for i := range issues { + if err = issues[i].GetLabels(); err != nil { + ctx.Handle(500, "GetLabels", fmt.Errorf("[#%d]%v", issues[i].Id, err)) + return + } + + idx := models.PairsContains(pairs, issues[i].Id) + + if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) { + continue + } + + if idx > -1 { + issues[i].IsRead = pairs[idx].IsRead + } else { + issues[i].IsRead = true + } + + if err = issues[i].GetPoster(); err != nil { + ctx.Handle(500, "issue.Issues(GetPoster)", fmt.Errorf("[#%d]%v", issues[i].Id, err)) + return + } + } + + var uid int64 = -1 + if ctx.User != nil { + uid = ctx.User.Id + } + issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode) + ctx.Data["IssueStats"] = issueStats + ctx.Data["SelectLabels"], _ = com.StrTo(selectLabels).Int64() + ctx.Data["ViewType"] = viewType + ctx.Data["Issues"] = issues + ctx.Data["IsShowClosed"] = isShowClosed + if isShowClosed { + ctx.Data["State"] = "closed" + ctx.Data["ShowCount"] = issueStats.ClosedCount + } else { + ctx.Data["ShowCount"] = issueStats.OpenCount + } + ctx.HTML(200, ISSUES) +} + +func CreateIssue(ctx *middleware.Context) { + ctx.Data["Title"] = "Create issue" + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = false + ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled + + var err error + // Get all milestones. + ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err) + return + } + ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err) + return + } + + us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) + if err != nil { + ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) + return + } + + ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes + ctx.Data["Collaborators"] = us + + ctx.HTML(200, ISSUE_CREATE) +} + +func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { + send := func(status int, data interface{}, err error) { + if err != nil { + log.Error(4, "issue.CreateIssuePost(?): %s", err) + + ctx.JSON(status, map[string]interface{}{ + "ok": false, + "status": status, + "error": err.Error(), + }) + } else { + ctx.JSON(status, map[string]interface{}{ + "ok": true, + "status": status, + "data": data, + }) + } + } + + var err error + // Get all milestones. + _, err = models.GetMilestones(ctx.Repo.Repository.Id, false) + if err != nil { + send(500, nil, err) + return + } + _, err = models.GetMilestones(ctx.Repo.Repository.Id, true) + if err != nil { + send(500, nil, err) + return + } + + _, err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) + if err != nil { + send(500, nil, err) + return + } + + if ctx.HasError() { + send(400, nil, errors.New(ctx.Flash.ErrorMsg)) + return + } + + // Only collaborators can assign. + if !ctx.Repo.IsOwner { + form.AssigneeId = 0 + } + issue := &models.Issue{ + RepoId: ctx.Repo.Repository.Id, + Index: int64(ctx.Repo.Repository.NumIssues) + 1, + Name: form.IssueName, + PosterId: ctx.User.Id, + MilestoneId: form.MilestoneId, + AssigneeId: form.AssigneeId, + LabelIds: form.Labels, + Content: form.Content, + } + if err := models.NewIssue(issue); err != nil { + send(500, nil, err) + return + } else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id, + ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil { + send(500, nil, err) + return + } + + if setting.AttachmentEnabled { + uploadFiles(ctx, issue.Id, 0) + } + + // Update mentions. + ms := base.MentionPattern.FindAllString(issue.Content, -1) + if len(ms) > 0 { + for i := range ms { + ms[i] = ms[i][1:] + } + + if err := models.UpdateMentions(ms, issue.Id); err != nil { + send(500, nil, err) + return + } + } + + act := &models.Action{ + ActUserId: ctx.User.Id, + ActUserName: ctx.User.Name, + ActEmail: ctx.User.Email, + OpType: models.CREATE_ISSUE, + Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name), + RepoId: ctx.Repo.Repository.Id, + RepoUserName: ctx.Repo.Owner.Name, + RepoName: ctx.Repo.Repository.Name, + RefName: ctx.Repo.BranchName, + IsPrivate: ctx.Repo.Repository.IsPrivate, + } + // Notify watchers. + if err := models.NotifyWatchers(act); err != nil { + send(500, nil, err) + return + } + + // Mail watchers and mentions. + if setting.Service.EnableNotifyMail { + tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) + if err != nil { + send(500, nil, err) + return + } + + tos = append(tos, ctx.User.LowerName) + newTos := make([]string, 0, len(ms)) + for _, m := range ms { + if com.IsSliceContainsStr(tos, m) { + continue + } + + newTos = append(newTos, m) + } + if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, + ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { + send(500, nil, err) + return + } + } + log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id) + + send(200, fmt.Sprintf("/%s/%s/issues/%d", ctx.Params(":username"), ctx.Params(":reponame"), issue.Index), nil) +} + +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) { + ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled + + idx := com.StrTo(ctx.Params(":index")).MustInt64() + if idx == 0 { + ctx.Handle(404, "issue.ViewIssue", nil) + return + } + + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) + if err != nil { + if err == models.ErrIssueNotExist { + ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err) + } else { + ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err) + } + 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) + if err != nil { + if err == models.ErrMilestoneNotExist { + log.Warn("issue.ViewIssue(GetMilestoneById): %v", err) + } else { + ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err) + return + } + } + } + + // Get all milestones. + ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err) + return + } + ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err) + return + } + + // Get all collaborators. + ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) + if err != nil { + ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) + return + } + + if ctx.IsSigned { + // Update issue-user. + if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil { + ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err) + return + } + } + + // Get poster and Assignee. + if err = issue.GetPoster(); err != nil { + ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err) + return + } else if err = issue.GetAssignee(); err != nil { + ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err) + return + } + issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) + + // Get comments. + comments, err := models.GetIssueComments(issue.Id) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err) + return + } + + // Get posters. + for i := range comments { + u, err := models.GetUserById(comments[i].PosterId) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err) + return + } + comments[i].Poster = u + + if comments[i].Type == models.COMMENT { + comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) + } + } + + ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes + + ctx.Data["Title"] = issue.Name + ctx.Data["Issue"] = issue + ctx.Data["Comments"] = comments + ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id) + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = false + ctx.HTML(200, ISSUE_VIEW) +} + +func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) { + idx := com.StrTo(ctx.Params(":index")).MustInt64() + 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.UpdateIssue", err) + } else { + ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err) + } + return + } + + if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner { + ctx.Error(403) + return + } + + issue.Name = form.IssueName + issue.MilestoneId = form.MilestoneId + issue.AssigneeId = form.AssigneeId + issue.LabelIds = form.Labels + issue.Content = form.Content + // try get content from text, ignore conflict with preview ajax + if form.Content == "" { + issue.Content = ctx.Query("text") + } + if err = models.UpdateIssue(issue); err != nil { + ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + "title": issue.Name, + "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)), + }) +} + +func UpdateIssueLabel(ctx *middleware.Context) { + if !ctx.Repo.IsOwner { + ctx.Error(403) + return + } + + idx := com.StrTo(ctx.Params(":index")).MustInt64() + 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(GetIssueByIndex)", err) + } else { + ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err) + } + return + } + + isAttach := ctx.Query("action") == "attach" + labelStrId := ctx.Query("id") + labelId := com.StrTo(labelStrId).MustInt64() + label, err := models.GetLabelById(labelId) + if err != nil { + if err == models.ErrLabelNotExist { + ctx.Handle(404, "issue.UpdateIssueLabel(GetLabelById)", err) + } else { + ctx.Handle(500, "issue.UpdateIssueLabel(GetLabelById)", err) + } + return + } + + 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 + } + + if isAttach { + label.NumIssues++ + if issue.IsClosed { + label.NumClosedIssues++ + } + } else { + label.NumIssues-- + if issue.IsClosed { + label.NumClosedIssues-- + } + } + if err = models.UpdateLabel(label); err != nil { + ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err) + return + } + } + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + +func UpdateIssueMilestone(ctx *middleware.Context) { + if !ctx.Repo.IsOwner { + ctx.Error(403) + return + } + + issueId := com.StrTo(ctx.Params(":issue")).MustInt64() + if issueId == 0 { + ctx.Error(404) + return + } + + issue, err := models.GetIssueById(issueId) + if err != nil { + if err == models.ErrIssueNotExist { + ctx.Handle(404, "issue.UpdateIssueMilestone(GetIssueById)", err) + } else { + ctx.Handle(500, "issue.UpdateIssueMilestone(GetIssueById)", err) + } + return + } + + oldMid := issue.MilestoneId + mid := com.StrTo(ctx.Params(":milestone")).MustInt64() + if oldMid == mid { + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) + return + } + + // Not check for invalid milestone id and give responsibility to owners. + issue.MilestoneId = mid + if err = models.ChangeMilestoneAssign(oldMid, mid, issue); err != nil { + ctx.Handle(500, "issue.UpdateIssueMilestone(ChangeMilestoneAssign)", err) + return + } else if err = models.UpdateIssue(issue); err != nil { + ctx.Handle(500, "issue.UpdateIssueMilestone(UpdateIssue)", err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + +func UpdateAssignee(ctx *middleware.Context) { + if !ctx.Repo.IsOwner { + ctx.Error(403) + return + } + + issueId := com.StrTo(ctx.Params(":index")).MustInt64() + if issueId == 0 { + ctx.Error(404) + return + } + + issue, err := models.GetIssueById(issueId) + if err != nil { + if err == models.ErrIssueNotExist { + ctx.Handle(404, "GetIssueById", err) + } else { + ctx.Handle(500, "GetIssueById", err) + } + return + } + + aid := com.StrTo(ctx.Params(":assigneeid")).MustInt64() + // Not check for invalid assignne id and give responsibility to owners. + issue.AssigneeId = aid + if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil { + ctx.Handle(500, "UpdateIssueUserPairByAssignee: %v", err) + return + } else if err = models.UpdateIssue(issue); err != nil { + ctx.Handle(500, "UpdateIssue", err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + +func uploadFiles(ctx *middleware.Context, issueId, commentId int64) { + if !setting.AttachmentEnabled { + return + } + + allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|") + attachments := ctx.Req.MultipartForm.File["attachments"] + + if len(attachments) > setting.AttachmentMaxFiles { + ctx.Handle(400, "issue.Comment", ErrTooManyFiles) + return + } + + for _, header := range attachments { + file, err := header.Open() + + if err != nil { + ctx.Handle(500, "issue.Comment(header.Open)", err) + return + } + + defer file.Close() + + buf := make([]byte, 1024) + n, _ := file.Read(buf) + if n > 0 { + buf = buf[:n] + } + fileType := http.DetectContentType(buf) + fmt.Println(fileType) + + allowed := false + + for _, t := range allowedTypes { + t := strings.Trim(t, " ") + + if t == "*/*" || t == fileType { + allowed = true + break + } + } + + if !allowed { + ctx.Handle(400, "issue.Comment", ErrFileTypeForbidden) + return + } + + out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_") + + if err != nil { + ctx.Handle(500, "ioutil.TempFile", err) + return + } + + defer out.Close() + + out.Write(buf) + _, err = io.Copy(out, file) + if err != nil { + ctx.Handle(500, "io.Copy", err) + return + } + + _, err = models.CreateAttachment(issueId, commentId, header.Filename, out.Name()) + if err != nil { + ctx.Handle(500, "CreateAttachment", err) + return + } + } +} + +func Comment(ctx *middleware.Context) { + send := func(status int, data interface{}, err error) { + if err != nil { + log.Error(4, "issue.Comment(?): %s", err) + + ctx.JSON(status, map[string]interface{}{ + "ok": false, + "status": status, + "error": err.Error(), + }) + } else { + ctx.JSON(status, map[string]interface{}{ + "ok": true, + "status": status, + "data": data, + }) + } + } + + index := com.StrTo(ctx.Query("issueIndex")).MustInt64() + if index == 0 { + ctx.Error(404) + return + } + + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) + if err != nil { + if err == models.ErrIssueNotExist { + send(404, nil, err) + } else { + send(200, nil, err) + } + + return + } + + // Check if issue owner changes the status of issue. + var newStatus string + if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id { + newStatus = ctx.Query("change_status") + } + if len(newStatus) > 0 { + if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) || + (strings.Contains(newStatus, "Close") && !issue.IsClosed) { + issue.IsClosed = !issue.IsClosed + if err = models.UpdateIssue(issue); err != nil { + send(500, nil, err) + return + } else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil { + send(500, nil, err) + return + } + + // Change open/closed issue counter for the associated milestone + if issue.MilestoneId > 0 { + if err = models.ChangeMilestoneIssueStats(issue); err != nil { + send(500, nil, err) + } + } + + cmtType := models.CLOSE + if !issue.IsClosed { + cmtType = models.REOPEN + } + + if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil { + send(200, nil, err) + return + } + log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed) + } + } + + var comment *models.Comment + + var ms []string + content := ctx.Query("content") + // Fix #321. Allow empty comments, as long as we have attachments. + if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 { + switch ctx.Params(":action") { + case "new": + if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.COMMENT, content, nil); err != nil { + send(500, nil, err) + return + } + + // Update mentions. + ms = base.MentionPattern.FindAllString(issue.Content, -1) + if len(ms) > 0 { + for i := range ms { + ms[i] = ms[i][1:] + } + + if err := models.UpdateMentions(ms, issue.Id); err != nil { + send(500, nil, err) + return + } + } + + log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id) + default: + ctx.Handle(404, "issue.Comment", err) + return + } + } + + if comment != nil { + uploadFiles(ctx, issue.Id, comment.Id) + } + + // Notify watchers. + act := &models.Action{ + ActUserId: ctx.User.Id, + ActUserName: ctx.User.LowerName, + ActEmail: ctx.User.Email, + OpType: models.COMMENT_ISSUE, + Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]), + RepoId: ctx.Repo.Repository.Id, + RepoUserName: ctx.Repo.Owner.LowerName, + RepoName: ctx.Repo.Repository.LowerName, + } + if err = models.NotifyWatchers(act); err != nil { + send(500, nil, err) + return + } + + // Mail watchers and mentions. + if setting.Service.EnableNotifyMail { + issue.Content = content + tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) + if err != nil { + send(500, nil, err) + return + } + + tos = append(tos, ctx.User.LowerName) + newTos := make([]string, 0, len(ms)) + for _, m := range ms { + if com.IsSliceContainsStr(tos, m) { + continue + } + + newTos = append(newTos, m) + } + if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, + ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { + send(500, nil, err) + return + } + } + + send(200, fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index), nil) +} + +func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) { + if ctx.HasError() { + Issues(ctx) + return + } + + l := &models.Label{ + RepoId: ctx.Repo.Repository.Id, + Name: form.Title, + Color: form.Color, + } + if err := models.NewLabel(l); err != nil { + ctx.Handle(500, "issue.NewLabel(NewLabel)", err) + return + } + ctx.Redirect(ctx.Repo.RepoLink + "/issues") +} + +func UpdateLabel(ctx *middleware.Context, form auth.CreateLabelForm) { + id := com.StrTo(ctx.Query("id")).MustInt64() + 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) { + removes := ctx.Query("remove") + if len(strings.TrimSpace(removes)) == 0 { + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) + return + } + + strIds := strings.Split(removes, ",") + 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) { + ctx.Data["Title"] = "Milestones" + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = true + + isShowClosed := ctx.Query("state") == "closed" + + miles, err := models.GetMilestones(ctx.Repo.Repository.Id, isShowClosed) + if err != nil { + ctx.Handle(500, "issue.Milestones(GetMilestones)", err) + return + } + for _, m := range miles { + m.RenderedContent = string(base.RenderSpecialLink([]byte(m.Content), ctx.Repo.RepoLink)) + m.CalOpenIssues() + } + ctx.Data["Milestones"] = miles + + if isShowClosed { + ctx.Data["State"] = "closed" + } else { + ctx.Data["State"] = "open" + } + ctx.HTML(200, MILESTONE) +} + +func NewMilestone(ctx *middleware.Context) { + ctx.Data["Title"] = "New Milestone" + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = true + ctx.HTML(200, MILESTONE_NEW) +} + +func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) { + ctx.Data["Title"] = "New Milestone" + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = true + + if ctx.HasError() { + ctx.HTML(200, MILESTONE_NEW) + return + } + + var deadline time.Time + var err error + if len(form.Deadline) == 0 { + form.Deadline = "12/31/9999" + } + deadline, err = time.Parse("01/02/2006", form.Deadline) + if err != nil { + ctx.Handle(500, "issue.NewMilestonePost(time.Parse)", err) + return + } + + mile := &models.Milestone{ + RepoId: ctx.Repo.Repository.Id, + Index: int64(ctx.Repo.Repository.NumMilestones) + 1, + Name: form.Title, + Content: form.Content, + Deadline: deadline, + } + if err = models.NewMilestone(mile); err != nil { + ctx.Handle(500, "issue.NewMilestonePost(NewMilestone)", err) + return + } + + ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones") +} + +func UpdateMilestone(ctx *middleware.Context) { + ctx.Data["Title"] = "Update Milestone" + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = true + + idx := com.StrTo(ctx.Params(":index")).MustInt64() + if idx == 0 { + ctx.Handle(404, "issue.UpdateMilestone", nil) + return + } + + mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx) + if err != nil { + if err == models.ErrMilestoneNotExist { + ctx.Handle(404, "issue.UpdateMilestone(GetMilestoneByIndex)", err) + } else { + ctx.Handle(500, "issue.UpdateMilestone(GetMilestoneByIndex)", err) + } + return + } + + action := ctx.Params(":action") + if len(action) > 0 { + switch action { + case "open": + if mile.IsClosed { + if err = models.ChangeMilestoneStatus(mile, false); err != nil { + ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err) + return + } + } + case "close": + if !mile.IsClosed { + mile.ClosedDate = time.Now() + if err = models.ChangeMilestoneStatus(mile, true); err != nil { + ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err) + return + } + } + case "delete": + if err = models.DeleteMilestone(mile); err != nil { + ctx.Handle(500, "issue.UpdateMilestone(DeleteMilestone)", err) + return + } + } + ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones") + return + } + + mile.DeadlineString = mile.Deadline.UTC().Format("01/02/2006") + if mile.DeadlineString == "12/31/9999" { + mile.DeadlineString = "" + } + ctx.Data["Milestone"] = mile + + ctx.HTML(200, MILESTONE_EDIT) +} + +func UpdateMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) { + ctx.Data["Title"] = "Update Milestone" + ctx.Data["IsRepoToolbarIssues"] = true + ctx.Data["IsRepoToolbarIssuesList"] = true + + idx := com.StrTo(ctx.Params(":index")).MustInt64() + if idx == 0 { + ctx.Handle(404, "issue.UpdateMilestonePost", nil) + return + } + + mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx) + if err != nil { + if err == models.ErrMilestoneNotExist { + ctx.Handle(404, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err) + } else { + ctx.Handle(500, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err) + } + return + } + + if ctx.HasError() { + ctx.HTML(200, MILESTONE_EDIT) + return + } + + var deadline time.Time + if len(form.Deadline) == 0 { + form.Deadline = "12/31/9999" + } + deadline, err = time.Parse("01/02/2006", form.Deadline) + if err != nil { + ctx.Handle(500, "issue.UpdateMilestonePost(time.Parse)", err) + return + } + + mile.Name = form.Title + mile.Content = form.Content + mile.Deadline = deadline + if err = models.UpdateMilestone(mile); err != nil { + ctx.Handle(500, "issue.UpdateMilestonePost(UpdateMilestone)", err) + return + } + + ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones") +} + +func IssueGetAttachment(ctx *middleware.Context) { + id := com.StrTo(ctx.Params(":id")).MustInt64() + if id == 0 { + ctx.Error(404) + return + } + + attachment, err := models.GetAttachmentById(id) + + if err != nil { + ctx.Handle(404, "issue.IssueGetAttachment(models.GetAttachmentById)", err) + return + } + + // Fix #312. Attachments with , in their name are not handled correctly by Google Chrome. + // We must put the name in " manually. + ctx.ServeFile(attachment.Path, "\""+attachment.Name+"\"") +} diff --git a/routers/repo/release.go b/routers/repo/release.go index 509796fb19..addeb1ce5f 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -5,13 +5,11 @@ package repo import ( - // "github.com/go-martini/martini" - - // "github.com/gogits/gogs/models" - // "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/base" - // "github.com/gogits/gogs/modules/log" - // "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/modules/log" + "github.com/gogits/gogs/modules/middleware" ) const ( @@ -20,215 +18,215 @@ const ( RELEASE_EDIT base.TplName = "repo/release/edit" ) -// func Releases(ctx *middleware.Context) { -// ctx.Data["Title"] = "Releases" -// ctx.Data["IsRepoToolbarReleases"] = true -// ctx.Data["IsRepoReleaseNew"] = false -// rawTags, err := ctx.Repo.GitRepo.GetTags() -// if err != nil { -// ctx.Handle(500, "release.Releases(GetTags)", err) -// return -// } +func Releases(ctx *middleware.Context) { + ctx.Data["Title"] = "Releases" + ctx.Data["IsRepoToolbarReleases"] = true + ctx.Data["IsRepoReleaseNew"] = false + rawTags, err := ctx.Repo.GitRepo.GetTags() + if err != nil { + ctx.Handle(500, "release.Releases(GetTags)", err) + return + } -// rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id) -// if err != nil { -// ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err) -// return -// } + rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id) + if err != nil { + ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err) + return + } -// commitsCount, err := ctx.Repo.Commit.CommitsCount() -// if err != nil { -// ctx.Handle(500, "release.Releases(CommitsCount)", err) -// return -// } + commitsCount, err := ctx.Repo.Commit.CommitsCount() + if err != nil { + ctx.Handle(500, "release.Releases(CommitsCount)", err) + return + } -// // Temproray cache commits count of used branches to speed up. -// countCache := make(map[string]int) + // Temproray cache commits count of used branches to speed up. + countCache := make(map[string]int) -// tags := make([]*models.Release, len(rawTags)) -// for i, rawTag := range rawTags { -// for _, rel := range rels { -// if rel.IsDraft && !ctx.Repo.IsOwner { -// continue -// } -// if rel.TagName == rawTag { -// rel.Publisher, err = models.GetUserById(rel.PublisherId) -// if err != nil { -// ctx.Handle(500, "release.Releases(GetUserById)", err) -// return -// } -// // Get corresponding target if it's not the current branch. -// if ctx.Repo.BranchName != rel.Target { -// // Get count if not exists. -// if _, ok := countCache[rel.Target]; !ok { -// commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rel.TagName) -// if err != nil { -// ctx.Handle(500, "release.Releases(GetCommitOfTag)", err) -// return -// } -// countCache[rel.Target], err = commit.CommitsCount() -// if err != nil { -// ctx.Handle(500, "release.Releases(CommitsCount2)", err) -// return -// } -// } -// rel.NumCommitsBehind = countCache[rel.Target] - rel.NumCommits -// } else { -// rel.NumCommitsBehind = commitsCount - rel.NumCommits -// } + tags := make([]*models.Release, len(rawTags)) + for i, rawTag := range rawTags { + for _, rel := range rels { + if rel.IsDraft && !ctx.Repo.IsOwner { + continue + } + if rel.TagName == rawTag { + rel.Publisher, err = models.GetUserById(rel.PublisherId) + if err != nil { + ctx.Handle(500, "GetUserById", err) + return + } + // Get corresponding target if it's not the current branch. + if ctx.Repo.BranchName != rel.Target { + // Get count if not exists. + if _, ok := countCache[rel.Target]; !ok { + commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rel.TagName) + if err != nil { + ctx.Handle(500, "GetCommitOfTag", err) + return + } + countCache[rel.Target], err = commit.CommitsCount() + if err != nil { + ctx.Handle(500, "CommitsCount2", err) + return + } + } + rel.NumCommitsBehind = countCache[rel.Target] - rel.NumCommits + } else { + rel.NumCommitsBehind = commitsCount - rel.NumCommits + } -// rel.Note = base.RenderMarkdownString(rel.Note, ctx.Repo.RepoLink) -// tags[i] = rel -// break -// } -// } + rel.Note = base.RenderMarkdownString(rel.Note, ctx.Repo.RepoLink) + tags[i] = rel + break + } + } -// if tags[i] == nil { -// commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag) -// if err != nil { -// ctx.Handle(500, "release.Releases(GetCommitOfTag2)", err) -// return -// } + if tags[i] == nil { + commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag) + if err != nil { + ctx.Handle(500, "GetCommitOfTag2", err) + return + } -// tags[i] = &models.Release{ -// Title: rawTag, -// TagName: rawTag, -// Sha1: commit.Id.String(), -// } + tags[i] = &models.Release{ + Title: rawTag, + TagName: rawTag, + Sha1: commit.Id.String(), + } -// tags[i].NumCommits, err = ctx.Repo.GitRepo.CommitsCount(commit.Id.String()) -// if err != nil { -// ctx.Handle(500, "release.Releases(CommitsCount)", err) -// return -// } -// tags[i].NumCommitsBehind = commitsCount - tags[i].NumCommits -// } -// } -// models.SortReleases(tags) -// ctx.Data["Releases"] = tags -// ctx.HTML(200, RELEASES) -// } + tags[i].NumCommits, err = ctx.Repo.GitRepo.CommitsCount(commit.Id.String()) + if err != nil { + ctx.Handle(500, "CommitsCount", err) + return + } + tags[i].NumCommitsBehind = commitsCount - tags[i].NumCommits + } + } + models.SortReleases(tags) + ctx.Data["Releases"] = tags + ctx.HTML(200, RELEASES) +} -// func NewRelease(ctx *middleware.Context) { -// if !ctx.Repo.IsOwner { -// ctx.Handle(403, "release.ReleasesNew", nil) -// return -// } +func NewRelease(ctx *middleware.Context) { + if !ctx.Repo.IsOwner { + ctx.Handle(403, "release.ReleasesNew", nil) + return + } -// ctx.Data["Title"] = "New Release" -// ctx.Data["IsRepoToolbarReleases"] = true -// ctx.Data["IsRepoReleaseNew"] = true -// ctx.HTML(200, RELEASE_NEW) -// } + ctx.Data["Title"] = "New Release" + ctx.Data["IsRepoToolbarReleases"] = true + ctx.Data["IsRepoReleaseNew"] = true + ctx.HTML(200, RELEASE_NEW) +} -// func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { -// if !ctx.Repo.IsOwner { -// ctx.Handle(403, "release.ReleasesNew", nil) -// return -// } +func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { + if !ctx.Repo.IsOwner { + ctx.Handle(403, "release.ReleasesNew", nil) + return + } -// ctx.Data["Title"] = "New Release" -// ctx.Data["IsRepoToolbarReleases"] = true -// ctx.Data["IsRepoReleaseNew"] = true + ctx.Data["Title"] = "New Release" + ctx.Data["IsRepoToolbarReleases"] = true + ctx.Data["IsRepoReleaseNew"] = true -// if ctx.HasError() { -// ctx.HTML(200, RELEASE_NEW) -// return -// } + if ctx.HasError() { + ctx.HTML(200, RELEASE_NEW) + return + } -// commitsCount, err := ctx.Repo.Commit.CommitsCount() -// if err != nil { -// ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err) -// return -// } + commitsCount, err := ctx.Repo.Commit.CommitsCount() + if err != nil { + ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err) + return + } -// if !ctx.Repo.GitRepo.IsBranchExist(form.Target) { -// ctx.RenderWithErr("Target branch does not exist", "release/new", &form) -// return -// } + if !ctx.Repo.GitRepo.IsBranchExist(form.Target) { + ctx.RenderWithErr("Target branch does not exist", "release/new", &form) + return + } -// rel := &models.Release{ -// RepoId: ctx.Repo.Repository.Id, -// PublisherId: ctx.User.Id, -// Title: form.Title, -// TagName: form.TagName, -// Target: form.Target, -// Sha1: ctx.Repo.Commit.Id.String(), -// NumCommits: commitsCount, -// Note: form.Content, -// IsDraft: len(form.Draft) > 0, -// IsPrerelease: form.Prerelease, -// } + rel := &models.Release{ + RepoId: ctx.Repo.Repository.Id, + PublisherId: ctx.User.Id, + Title: form.Title, + TagName: form.TagName, + Target: form.Target, + Sha1: ctx.Repo.Commit.Id.String(), + NumCommits: commitsCount, + Note: form.Content, + IsDraft: len(form.Draft) > 0, + IsPrerelease: form.Prerelease, + } -// if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil { -// if err == models.ErrReleaseAlreadyExist { -// ctx.RenderWithErr("Release with this tag name has already existed", "release/new", &form) -// } else { -// ctx.Handle(500, "release.ReleasesNewPost(IsReleaseExist)", err) -// } -// return -// } -// log.Trace("%s Release created: %s/%s:%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.Name, form.TagName) + if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil { + if err == models.ErrReleaseAlreadyExist { + ctx.RenderWithErr("Release with this tag name has already existed", "release/new", &form) + } else { + ctx.Handle(500, "release.ReleasesNewPost(IsReleaseExist)", err) + } + return + } + log.Trace("%s Release created: %s/%s:%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.Name, form.TagName) -// ctx.Redirect(ctx.Repo.RepoLink + "/releases") -// } + ctx.Redirect(ctx.Repo.RepoLink + "/releases") +} -// func EditRelease(ctx *middleware.Context, params martini.Params) { -// if !ctx.Repo.IsOwner { -// ctx.Handle(403, "release.ReleasesEdit", nil) -// return -// } +func EditRelease(ctx *middleware.Context) { + if !ctx.Repo.IsOwner { + ctx.Handle(403, "release.ReleasesEdit", nil) + return + } -// tagName := params["tagname"] -// rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName) -// if err != nil { -// if err == models.ErrReleaseNotExist { -// ctx.Handle(404, "release.ReleasesEdit(GetRelease)", err) -// } else { -// ctx.Handle(500, "release.ReleasesEdit(GetRelease)", err) -// } -// return -// } -// ctx.Data["Release"] = rel + tagName := ctx.Params(":tagname") + rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName) + if err != nil { + if err == models.ErrReleaseNotExist { + ctx.Handle(404, "release.ReleasesEdit(GetRelease)", err) + } else { + ctx.Handle(500, "release.ReleasesEdit(GetRelease)", err) + } + return + } + ctx.Data["Release"] = rel -// ctx.Data["Title"] = "Edit Release" -// ctx.Data["IsRepoToolbarReleases"] = true -// ctx.HTML(200, RELEASE_EDIT) -// } + ctx.Data["Title"] = "Edit Release" + ctx.Data["IsRepoToolbarReleases"] = true + ctx.HTML(200, RELEASE_EDIT) +} -// func EditReleasePost(ctx *middleware.Context, params martini.Params, form auth.EditReleaseForm) { -// if !ctx.Repo.IsOwner { -// ctx.Handle(403, "release.EditReleasePost", nil) -// return -// } +func EditReleasePost(ctx *middleware.Context, form auth.EditReleaseForm) { + if !ctx.Repo.IsOwner { + ctx.Handle(403, "release.EditReleasePost", nil) + return + } -// tagName := params["tagname"] -// rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName) -// if err != nil { -// if err == models.ErrReleaseNotExist { -// ctx.Handle(404, "release.EditReleasePost(GetRelease)", err) -// } else { -// ctx.Handle(500, "release.EditReleasePost(GetRelease)", err) -// } -// return -// } -// ctx.Data["Release"] = rel + tagName := ctx.Params(":tagname") + rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName) + if err != nil { + if err == models.ErrReleaseNotExist { + ctx.Handle(404, "release.EditReleasePost(GetRelease)", err) + } else { + ctx.Handle(500, "release.EditReleasePost(GetRelease)", err) + } + return + } + ctx.Data["Release"] = rel -// if ctx.HasError() { -// ctx.HTML(200, RELEASE_EDIT) -// return -// } + if ctx.HasError() { + ctx.HTML(200, RELEASE_EDIT) + return + } -// ctx.Data["Title"] = "Edit Release" -// ctx.Data["IsRepoToolbarReleases"] = true + ctx.Data["Title"] = "Edit Release" + ctx.Data["IsRepoToolbarReleases"] = true -// rel.Title = form.Title -// rel.Note = form.Content -// rel.IsDraft = len(form.Draft) > 0 -// rel.IsPrerelease = form.Prerelease -// if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil { -// ctx.Handle(500, "release.EditReleasePost(UpdateRelease)", err) -// return -// } -// ctx.Redirect(ctx.Repo.RepoLink + "/releases") -// } + rel.Title = form.Title + rel.Note = form.Content + rel.IsDraft = len(form.Draft) > 0 + rel.IsPrerelease = form.Prerelease + if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil { + ctx.Handle(500, "release.EditReleasePost(UpdateRelease)", err) + return + } + ctx.Redirect(ctx.Repo.RepoLink + "/releases") +} diff --git a/routers/repo/repo.go b/routers/repo/repo.go index d1e196a0bd..8de1e0936e 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -5,8 +5,10 @@ package repo import ( + "fmt" "os" "path" + "strings" "github.com/Unknwon/com" @@ -34,22 +36,22 @@ func Create(ctx *middleware.Context) { ctx.Data["Licenses"] = models.Licenses ctxUser := ctx.User - // orgId := com.StrTo(ctx.Query("org")).MustInt64() - // if orgId > 0 { - // org, err := models.GetUserById(orgId) - // if err != nil && err != models.ErrUserNotExist { - // ctx.Handle(500, "home.Dashboard(GetUserById)", err) - // return - // } - // ctxUser = org - // } + orgId := com.StrTo(ctx.Query("org")).MustInt64() + if orgId > 0 { + org, err := models.GetUserById(orgId) + if err != nil && err != models.ErrUserNotExist { + ctx.Handle(500, "home.Dashboard(GetUserById)", err) + return + } + ctxUser = org + } ctx.Data["ContextUser"] = ctxUser - // if err := ctx.User.GetOrganizations(); err != nil { - // ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) - // return - // } - // ctx.Data["AllUsers"] = append([]*models.User{ctx.User}, ctx.User.Orgs...) + if err := ctx.User.GetOrganizations(); err != nil { + ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) + return + } + ctx.Data["AllUsers"] = append([]*models.User{ctx.User}, ctx.User.Orgs...) ctx.HTML(200, CREATE) } @@ -62,22 +64,22 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) { ctx.Data["Licenses"] = models.Licenses ctxUser := ctx.User - // orgId := com.StrTo(ctx.Query("org")).MustInt64() - // if orgId > 0 { - // org, err := models.GetUserById(orgId) - // if err != nil && err != models.ErrUserNotExist { - // ctx.Handle(500, "home.Dashboard(GetUserById)", err) - // return - // } - // ctxUser = org - // } + orgId := com.StrTo(ctx.Query("org")).MustInt64() + if orgId > 0 { + org, err := models.GetUserById(orgId) + if err != nil && err != models.ErrUserNotExist { + ctx.Handle(500, "home.Dashboard(GetUserById)", err) + return + } + ctxUser = org + } ctx.Data["ContextUser"] = ctxUser - // if err := ctx.User.GetOrganizations(); err != nil { - // ctx.Handle(500, "home.CreatePost(GetOrganizations)", err) - // return - // } - // ctx.Data["Orgs"] = ctx.User.Orgs + if err := ctx.User.GetOrganizations(); err != nil { + ctx.Handle(500, "home.CreatePost(GetOrganizations)", err) + return + } + ctx.Data["Orgs"] = ctx.User.Orgs if ctx.HasError() { ctx.HTML(200, CREATE) @@ -127,78 +129,78 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) { ctx.Handle(500, "CreateRepository", err) } -// func Migrate(ctx *middleware.Context) { -// ctx.Data["Title"] = "Migrate repository" -// ctx.Data["PageIsNewRepo"] = true +func Migrate(ctx *middleware.Context) { + ctx.Data["Title"] = "Migrate repository" + ctx.Data["PageIsNewRepo"] = true -// if err := ctx.User.GetOrganizations(); err != nil { -// ctx.Handle(500, "home.Migrate(GetOrganizations)", err) -// return -// } -// ctx.Data["Orgs"] = ctx.User.Orgs + if err := ctx.User.GetOrganizations(); err != nil { + ctx.Handle(500, "home.Migrate(GetOrganizations)", err) + return + } + ctx.Data["Orgs"] = ctx.User.Orgs -// ctx.HTML(200, MIGRATE) -// } + ctx.HTML(200, MIGRATE) +} -// func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) { -// ctx.Data["Title"] = "Migrate repository" -// ctx.Data["PageIsNewRepo"] = true +func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) { + ctx.Data["Title"] = "Migrate repository" + ctx.Data["PageIsNewRepo"] = true -// if err := ctx.User.GetOrganizations(); err != nil { -// ctx.Handle(500, "home.MigratePost(GetOrganizations)", err) -// return -// } -// ctx.Data["Orgs"] = ctx.User.Orgs + if err := ctx.User.GetOrganizations(); err != nil { + ctx.Handle(500, "home.MigratePost(GetOrganizations)", err) + return + } + ctx.Data["Orgs"] = ctx.User.Orgs -// if ctx.HasError() { -// ctx.HTML(200, MIGRATE) -// return -// } + if ctx.HasError() { + ctx.HTML(200, MIGRATE) + return + } -// u := ctx.User -// // Not equal means current user is an organization. -// if u.Id != form.Uid { -// var err error -// u, err = models.GetUserById(form.Uid) -// if err != nil { -// if err == models.ErrUserNotExist { -// ctx.Handle(404, "home.MigratePost(GetUserById)", err) -// } else { -// ctx.Handle(500, "home.MigratePost(GetUserById)", err) -// } -// return -// } -// } + u := ctx.User + // Not equal means current user is an organization. + if u.Id != form.Uid { + var err error + u, err = models.GetUserById(form.Uid) + if err != nil { + if err == models.ErrUserNotExist { + ctx.Handle(404, "home.MigratePost(GetUserById)", err) + } else { + ctx.Handle(500, "home.MigratePost(GetUserById)", err) + } + return + } + } -// authStr := strings.Replace(fmt.Sprintf("://%s:%s", -// form.AuthUserName, form.AuthPasswd), "@", "%40", -1) -// url := strings.Replace(form.Url, "://", authStr+"@", 1) -// repo, err := models.MigrateRepository(u, form.RepoName, form.Description, form.Private, -// form.Mirror, url) -// if err == nil { -// log.Trace("%s Repository migrated: %s/%s", ctx.Req.RequestURI, u.LowerName, form.RepoName) -// ctx.Redirect("/" + u.Name + "/" + form.RepoName) -// return -// } else if err == models.ErrRepoAlreadyExist { -// ctx.RenderWithErr("Repository name has already been used", MIGRATE, &form) -// return -// } else if err == models.ErrRepoNameIllegal { -// ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), MIGRATE, &form) -// return -// } + authStr := strings.Replace(fmt.Sprintf("://%s:%s", + form.AuthUserName, form.AuthPasswd), "@", "%40", -1) + url := strings.Replace(form.Url, "://", authStr+"@", 1) + repo, err := models.MigrateRepository(u, form.RepoName, form.Description, form.Private, + form.Mirror, url) + if err == nil { + log.Trace("%s Repository migrated: %s/%s", ctx.Req.RequestURI, u.LowerName, form.RepoName) + ctx.Redirect("/" + u.Name + "/" + form.RepoName) + return + } else if err == models.ErrRepoAlreadyExist { + ctx.RenderWithErr("Repository name has already been used", MIGRATE, &form) + return + } else if err == models.ErrRepoNameIllegal { + ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), MIGRATE, &form) + return + } -// if repo != nil { -// if errDelete := models.DeleteRepository(u.Id, repo.Id, u.Name); errDelete != nil { -// log.Error("repo.MigratePost(DeleteRepository): %v", errDelete) -// } -// } + if repo != nil { + if errDelete := models.DeleteRepository(u.Id, repo.Id, u.Name); errDelete != nil { + log.Error(4, "DeleteRepository: %v", errDelete) + } + } -// if strings.Contains(err.Error(), "Authentication failed") { -// ctx.RenderWithErr(err.Error(), MIGRATE, &form) -// return -// } -// ctx.Handle(500, "repo.Migrate(MigrateRepository)", err) -// } + if strings.Contains(err.Error(), "Authentication failed") { + ctx.RenderWithErr(err.Error(), MIGRATE, &form) + return + } + ctx.Handle(500, "MigrateRepository", err) +} // func Action(ctx *middleware.Context, params martini.Params) { // var err error diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 48f2e219ed..26f391346e 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -4,362 +4,362 @@ package repo -// import ( -// "fmt" -// "strings" -// "time" +import ( + "fmt" + "strings" + "time" -// "github.com/go-martini/martini" + "github.com/Unknwon/com" -// "github.com/gogits/gogs-ng/models" -// "github.com/gogits/gogs/modules/auth" -// "github.com/gogits/gogs/modules/base" -// "github.com/gogits/gogs/modules/log" -// "github.com/gogits/gogs/modules/mailer" -// "github.com/gogits/gogs/modules/middleware" -// "github.com/gogits/gogs/modules/setting" -// ) + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/log" + "github.com/gogits/gogs/modules/mailer" + "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/modules/setting" +) -// const ( -// SETTING base.TplName = "repo/setting" -// COLLABORATION base.TplName = "repo/collaboration" +const ( + SETTING base.TplName = "repo/setting" + COLLABORATION base.TplName = "repo/collaboration" -// HOOKS base.TplName = "repo/hooks" -// HOOK_ADD base.TplName = "repo/hook_add" -// HOOK_EDIT base.TplName = "repo/hook_edit" -// ) + HOOKS base.TplName = "repo/hooks" + HOOK_ADD base.TplName = "repo/hook_add" + HOOK_EDIT base.TplName = "repo/hook_edit" +) -// func Setting(ctx *middleware.Context) { -// ctx.Data["IsRepoToolbarSetting"] = true -// ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - settings" -// ctx.HTML(200, SETTING) -// } +func Setting(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarSetting"] = true + ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - settings" + ctx.HTML(200, SETTING) +} -// func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) { -// ctx.Data["IsRepoToolbarSetting"] = true +func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) { + ctx.Data["IsRepoToolbarSetting"] = true -// switch ctx.Query("action") { -// case "update": -// if ctx.HasError() { -// ctx.HTML(200, SETTING) -// return -// } + switch ctx.Query("action") { + case "update": + if ctx.HasError() { + ctx.HTML(200, SETTING) + return + } -// newRepoName := form.RepoName -// // Check if repository name has been changed. -// if ctx.Repo.Repository.Name != newRepoName { -// isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName) -// if err != nil { -// ctx.Handle(500, "setting.SettingPost(update: check existence)", err) -// return -// } else if isExist { -// ctx.RenderWithErr("Repository name has been taken in your repositories.", SETTING, nil) -// return -// } else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil { -// ctx.Handle(500, "setting.SettingPost(change repository name)", err) -// return -// } -// log.Trace("%s Repository name changed: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newRepoName) + newRepoName := form.RepoName + // Check if repository name has been changed. + if ctx.Repo.Repository.Name != newRepoName { + isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName) + if err != nil { + ctx.Handle(500, "setting.SettingPost(update: check existence)", err) + return + } else if isExist { + ctx.RenderWithErr("Repository name has been taken in your repositories.", SETTING, nil) + return + } else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil { + ctx.Handle(500, "setting.SettingPost(change repository name)", err) + return + } + log.Trace("%s Repository name changed: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newRepoName) -// ctx.Repo.Repository.Name = newRepoName -// } + ctx.Repo.Repository.Name = newRepoName + } -// br := form.Branch + br := form.Branch -// if ctx.Repo.GitRepo.IsBranchExist(br) { -// ctx.Repo.Repository.DefaultBranch = br -// } -// ctx.Repo.Repository.Description = form.Description -// ctx.Repo.Repository.Website = form.Website -// ctx.Repo.Repository.IsPrivate = form.Private -// ctx.Repo.Repository.IsGoget = form.GoGet -// if err := models.UpdateRepository(ctx.Repo.Repository); err != nil { -// ctx.Handle(404, "setting.SettingPost(update)", err) -// return -// } -// log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) + if ctx.Repo.GitRepo.IsBranchExist(br) { + ctx.Repo.Repository.DefaultBranch = br + } + ctx.Repo.Repository.Description = form.Description + ctx.Repo.Repository.Website = form.Website + ctx.Repo.Repository.IsPrivate = form.Private + ctx.Repo.Repository.IsGoget = form.GoGet + if err := models.UpdateRepository(ctx.Repo.Repository); err != nil { + ctx.Handle(404, "UpdateRepository", err) + return + } + log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) -// if ctx.Repo.Repository.IsMirror { -// if form.Interval > 0 { -// ctx.Repo.Mirror.Interval = form.Interval -// ctx.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(form.Interval) * time.Hour) -// if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil { -// log.Error("setting.SettingPost(UpdateMirror): %v", err) -// } -// } -// } + if ctx.Repo.Repository.IsMirror { + if form.Interval > 0 { + ctx.Repo.Mirror.Interval = form.Interval + ctx.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(form.Interval) * time.Hour) + if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil { + log.Error(4, "UpdateMirror: %v", err) + } + } + } -// ctx.Flash.Success("Repository options has been successfully updated.") -// ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)) -// case "transfer": -// if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") { -// ctx.RenderWithErr("Please make sure you entered repository name is correct.", SETTING, nil) -// return -// } else if ctx.Repo.Repository.IsMirror { -// ctx.Error(404) -// return -// } + ctx.Flash.Success("Repository options has been successfully updated.") + ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)) + case "transfer": + if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") { + ctx.RenderWithErr("Please make sure you entered repository name is correct.", SETTING, nil) + return + } else if ctx.Repo.Repository.IsMirror { + ctx.Error(404) + return + } -// newOwner := ctx.Query("owner") -// // Check if new owner exists. -// isExist, err := models.IsUserExist(newOwner) -// if err != nil { -// ctx.Handle(500, "setting.SettingPost(transfer: check existence)", err) -// return -// } else if !isExist { -// ctx.RenderWithErr("Please make sure you entered owner name is correct.", SETTING, nil) -// return -// } else if err = models.TransferOwnership(ctx.Repo.Owner, newOwner, ctx.Repo.Repository); err != nil { -// ctx.Handle(500, "setting.SettingPost(transfer repository)", err) -// return -// } -// log.Trace("%s Repository transfered: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newOwner) + newOwner := ctx.Query("owner") + // Check if new owner exists. + isExist, err := models.IsUserExist(newOwner) + if err != nil { + ctx.Handle(500, "setting.SettingPost(transfer: check existence)", err) + return + } else if !isExist { + ctx.RenderWithErr("Please make sure you entered owner name is correct.", SETTING, nil) + return + } else if err = models.TransferOwnership(ctx.Repo.Owner, newOwner, ctx.Repo.Repository); err != nil { + ctx.Handle(500, "setting.SettingPost(transfer repository)", err) + return + } + log.Trace("%s Repository transfered: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newOwner) -// ctx.Redirect("/") -// case "delete": -// if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") { -// ctx.RenderWithErr("Please make sure you entered repository name is correct.", SETTING, nil) -// return -// } + ctx.Redirect("/") + case "delete": + if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") { + ctx.RenderWithErr("Please make sure you entered repository name is correct.", SETTING, nil) + return + } -// if ctx.Repo.Owner.IsOrganization() && -// !ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) { -// ctx.Error(403) -// return -// } + if ctx.Repo.Owner.IsOrganization() && + !ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) { + ctx.Error(403) + return + } -// if err := models.DeleteRepository(ctx.Repo.Owner.Id, ctx.Repo.Repository.Id, ctx.Repo.Owner.Name); err != nil { -// ctx.Handle(500, "setting.Delete(DeleteRepository)", err) -// return -// } -// log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName) + if err := models.DeleteRepository(ctx.Repo.Owner.Id, ctx.Repo.Repository.Id, ctx.Repo.Owner.Name); err != nil { + ctx.Handle(500, "setting.Delete(DeleteRepository)", err) + return + } + log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName) -// if ctx.Repo.Owner.IsOrganization() { -// ctx.Redirect("/org/" + ctx.Repo.Owner.Name + "/dashboard") -// } else { -// ctx.Redirect("/") -// } -// } -// } + if ctx.Repo.Owner.IsOrganization() { + ctx.Redirect("/org/" + ctx.Repo.Owner.Name + "/dashboard") + } else { + ctx.Redirect("/") + } + } +} -// func Collaboration(ctx *middleware.Context) { -// repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/") -// ctx.Data["IsRepoToolbarCollaboration"] = true -// ctx.Data["Title"] = repoLink + " - collaboration" +func Collaboration(ctx *middleware.Context) { + repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/") + ctx.Data["IsRepoToolbarCollaboration"] = true + ctx.Data["Title"] = repoLink + " - collaboration" -// // Delete collaborator. -// remove := strings.ToLower(ctx.Query("remove")) -// if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { -// if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil { -// ctx.Handle(500, "setting.Collaboration(DeleteAccess)", err) -// return -// } -// ctx.Flash.Success("Collaborator has been removed.") -// ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") -// return -// } + // Delete collaborator. + remove := strings.ToLower(ctx.Query("remove")) + if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { + if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil { + ctx.Handle(500, "setting.Collaboration(DeleteAccess)", err) + return + } + ctx.Flash.Success("Collaborator has been removed.") + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } -// names, err := models.GetCollaboratorNames(repoLink) -// if err != nil { -// ctx.Handle(500, "setting.Collaboration(GetCollaborators)", err) -// return -// } + names, err := models.GetCollaboratorNames(repoLink) + if err != nil { + ctx.Handle(500, "setting.Collaboration(GetCollaborators)", err) + return + } -// us := make([]*models.User, len(names)) -// for i, name := range names { -// us[i], err = models.GetUserByName(name) -// if err != nil { -// ctx.Handle(500, "setting.Collaboration(GetUserByName)", err) -// return -// } -// } + us := make([]*models.User, len(names)) + for i, name := range names { + us[i], err = models.GetUserByName(name) + if err != nil { + ctx.Handle(500, "setting.Collaboration(GetUserByName)", err) + return + } + } -// ctx.Data["Collaborators"] = us -// ctx.HTML(200, COLLABORATION) -// } + ctx.Data["Collaborators"] = us + ctx.HTML(200, COLLABORATION) +} -// func CollaborationPost(ctx *middleware.Context) { -// repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/") -// name := strings.ToLower(ctx.Query("collaborator")) -// if len(name) == 0 || ctx.Repo.Owner.LowerName == name { -// ctx.Redirect(ctx.Req.RequestURI) -// return -// } -// has, err := models.HasAccess(name, repoLink, models.WRITABLE) -// if err != nil { -// ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err) -// return -// } else if has { -// ctx.Redirect(ctx.Req.RequestURI) -// return -// } +func CollaborationPost(ctx *middleware.Context) { + repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/") + name := strings.ToLower(ctx.Query("collaborator")) + if len(name) == 0 || ctx.Repo.Owner.LowerName == name { + ctx.Redirect(ctx.Req.RequestURI) + return + } + has, err := models.HasAccess(name, repoLink, models.WRITABLE) + if err != nil { + ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err) + return + } else if has { + ctx.Redirect(ctx.Req.RequestURI) + return + } -// u, err := models.GetUserByName(name) -// if err != nil { -// if err == models.ErrUserNotExist { -// ctx.Flash.Error("Given user does not exist.") -// ctx.Redirect(ctx.Req.RequestURI) -// } else { -// ctx.Handle(500, "setting.CollaborationPost(GetUserByName)", err) -// } -// return -// } + u, err := models.GetUserByName(name) + if err != nil { + if err == models.ErrUserNotExist { + ctx.Flash.Error("Given user does not exist.") + ctx.Redirect(ctx.Req.RequestURI) + } else { + ctx.Handle(500, "setting.CollaborationPost(GetUserByName)", err) + } + return + } -// if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink, -// Mode: models.WRITABLE}); err != nil { -// ctx.Handle(500, "setting.CollaborationPost(AddAccess)", err) -// return -// } + if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink, + Mode: models.WRITABLE}); err != nil { + ctx.Handle(500, "setting.CollaborationPost(AddAccess)", err) + return + } -// if setting.Service.EnableNotifyMail { -// if err = mailer.SendCollaboratorMail(ctx.Render, u, ctx.User, ctx.Repo.Repository); err != nil { -// ctx.Handle(500, "setting.CollaborationPost(SendCollaboratorMail)", err) -// return -// } -// } + if setting.Service.EnableNotifyMail { + if err = mailer.SendCollaboratorMail(ctx.Render, u, ctx.User, ctx.Repo.Repository); err != nil { + ctx.Handle(500, "setting.CollaborationPost(SendCollaboratorMail)", err) + return + } + } -// ctx.Flash.Success("New collaborator has been added.") -// ctx.Redirect(ctx.Req.RequestURI) -// } + ctx.Flash.Success("New collaborator has been added.") + ctx.Redirect(ctx.Req.RequestURI) +} -// func WebHooks(ctx *middleware.Context) { -// ctx.Data["IsRepoToolbarWebHooks"] = true -// ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhooks" +func WebHooks(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarWebHooks"] = true + ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhooks" -// // Delete webhook. -// remove, _ := base.StrTo(ctx.Query("remove")).Int64() -// if remove > 0 { -// if err := models.DeleteWebhook(remove); err != nil { -// ctx.Handle(500, "setting.WebHooks(DeleteWebhook)", err) -// return -// } -// ctx.Flash.Success("Webhook has been removed.") -// ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks") -// return -// } + // Delete webhook. + remove := com.StrTo(ctx.Query("remove")).MustInt64() + if remove > 0 { + if err := models.DeleteWebhook(remove); err != nil { + ctx.Handle(500, "setting.WebHooks(DeleteWebhook)", err) + return + } + ctx.Flash.Success("Webhook has been removed.") + ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks") + return + } -// ws, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.Id) -// if err != nil { -// ctx.Handle(500, "setting.WebHooks(GetWebhooksByRepoId)", err) -// return -// } + ws, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.Id) + if err != nil { + ctx.Handle(500, "setting.WebHooks(GetWebhooksByRepoId)", err) + return + } -// ctx.Data["Webhooks"] = ws -// ctx.HTML(200, HOOKS) -// } + ctx.Data["Webhooks"] = ws + ctx.HTML(200, HOOKS) +} -// func WebHooksAdd(ctx *middleware.Context) { -// ctx.Data["IsRepoToolbarWebHooks"] = true -// ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook" -// ctx.HTML(200, HOOK_ADD) -// } +func WebHooksAdd(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarWebHooks"] = true + ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook" + ctx.HTML(200, HOOK_ADD) +} -// func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) { -// ctx.Data["IsRepoToolbarWebHooks"] = true -// ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook" +func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) { + ctx.Data["IsRepoToolbarWebHooks"] = true + ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook" -// if ctx.HasError() { -// ctx.HTML(200, HOOK_ADD) -// return -// } + if ctx.HasError() { + ctx.HTML(200, HOOK_ADD) + return + } -// ct := models.JSON -// if form.ContentType == "2" { -// ct = models.FORM -// } + ct := models.JSON + if form.ContentType == "2" { + ct = models.FORM + } -// w := &models.Webhook{ -// RepoId: ctx.Repo.Repository.Id, -// Url: form.Url, -// ContentType: ct, -// Secret: form.Secret, -// HookEvent: &models.HookEvent{ -// PushOnly: form.PushOnly, -// }, -// IsActive: form.Active, -// } -// if err := w.UpdateEvent(); err != nil { -// ctx.Handle(500, "setting.WebHooksAddPost(UpdateEvent)", err) -// return -// } else if err := models.CreateWebhook(w); err != nil { -// ctx.Handle(500, "setting.WebHooksAddPost(CreateWebhook)", err) -// return -// } + w := &models.Webhook{ + RepoId: ctx.Repo.Repository.Id, + Url: form.Url, + ContentType: ct, + Secret: form.Secret, + HookEvent: &models.HookEvent{ + PushOnly: form.PushOnly, + }, + IsActive: form.Active, + } + if err := w.UpdateEvent(); err != nil { + ctx.Handle(500, "setting.WebHooksAddPost(UpdateEvent)", err) + return + } else if err := models.CreateWebhook(w); err != nil { + ctx.Handle(500, "setting.WebHooksAddPost(CreateWebhook)", err) + return + } -// ctx.Flash.Success("New webhook has been added.") -// ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks") -// } + ctx.Flash.Success("New webhook has been added.") + ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks") +} -// func WebHooksEdit(ctx *middleware.Context, params martini.Params) { -// ctx.Data["IsRepoToolbarWebHooks"] = true -// ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook" +func WebHooksEdit(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarWebHooks"] = true + ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook" -// hookId, _ := base.StrTo(params["id"]).Int64() -// if hookId == 0 { -// ctx.Handle(404, "setting.WebHooksEdit", nil) -// return -// } + hookId := com.StrTo(ctx.Params(":id")).MustInt64() + if hookId == 0 { + ctx.Handle(404, "setting.WebHooksEdit", nil) + return + } -// w, err := models.GetWebhookById(hookId) -// if err != nil { -// if err == models.ErrWebhookNotExist { -// ctx.Handle(404, "setting.WebHooksEdit(GetWebhookById)", nil) -// } else { -// ctx.Handle(500, "setting.WebHooksEdit(GetWebhookById)", err) -// } -// return -// } + w, err := models.GetWebhookById(hookId) + if err != nil { + if err == models.ErrWebhookNotExist { + ctx.Handle(404, "setting.WebHooksEdit(GetWebhookById)", nil) + } else { + ctx.Handle(500, "setting.WebHooksEdit(GetWebhookById)", err) + } + return + } -// w.GetEvent() -// ctx.Data["Webhook"] = w -// ctx.HTML(200, HOOK_EDIT) -// } + w.GetEvent() + ctx.Data["Webhook"] = w + ctx.HTML(200, HOOK_EDIT) +} -// func WebHooksEditPost(ctx *middleware.Context, params martini.Params, form auth.NewWebhookForm) { -// ctx.Data["IsRepoToolbarWebHooks"] = true -// ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook" +func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) { + ctx.Data["IsRepoToolbarWebHooks"] = true + ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook" -// hookId, _ := base.StrTo(params["id"]).Int64() -// if hookId == 0 { -// ctx.Handle(404, "setting.WebHooksEditPost", nil) -// return -// } + hookId := com.StrTo(ctx.Params(":id")).MustInt64() + if hookId == 0 { + ctx.Handle(404, "setting.WebHooksEditPost", nil) + return + } -// w, err := models.GetWebhookById(hookId) -// if err != nil { -// if err == models.ErrWebhookNotExist { -// ctx.Handle(404, "setting.WebHooksEditPost(GetWebhookById)", nil) -// } else { -// ctx.Handle(500, "setting.WebHooksEditPost(GetWebhookById)", err) -// } -// return -// } + w, err := models.GetWebhookById(hookId) + if err != nil { + if err == models.ErrWebhookNotExist { + ctx.Handle(404, "GetWebhookById", nil) + } else { + ctx.Handle(500, "GetWebhookById", err) + } + return + } -// if ctx.HasError() { -// ctx.HTML(200, HOOK_EDIT) -// return -// } + if ctx.HasError() { + ctx.HTML(200, HOOK_EDIT) + return + } -// ct := models.JSON -// if form.ContentType == "2" { -// ct = models.FORM -// } + ct := models.JSON + if form.ContentType == "2" { + ct = models.FORM + } -// w.Url = form.Url -// w.ContentType = ct -// w.Secret = form.Secret -// w.HookEvent = &models.HookEvent{ -// PushOnly: form.PushOnly, -// } -// w.IsActive = form.Active -// if err := w.UpdateEvent(); err != nil { -// ctx.Handle(500, "setting.WebHooksEditPost(UpdateEvent)", err) -// return -// } else if err := models.UpdateWebhook(w); err != nil { -// ctx.Handle(500, "setting.WebHooksEditPost(WebHooksEditPost)", err) -// return -// } + w.Url = form.Url + w.ContentType = ct + w.Secret = form.Secret + w.HookEvent = &models.HookEvent{ + PushOnly: form.PushOnly, + } + w.IsActive = form.Active + if err := w.UpdateEvent(); err != nil { + ctx.Handle(500, "UpdateEvent", err) + return + } else if err := models.UpdateWebhook(w); err != nil { + ctx.Handle(500, "WebHooksEditPost", err) + return + } -// ctx.Flash.Success("Webhook has been updated.") -// ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId)) -// } + ctx.Flash.Success("Webhook has been updated.") + ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId)) +} diff --git a/routers/user/home.go b/routers/user/home.go index 16e88a9427..b5a789ae0e 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -28,11 +28,11 @@ func Dashboard(ctx *middleware.Context) { ctx.Data["PageIsDashboard"] = true ctx.Data["PageIsNews"] = true - // if err := ctx.User.GetOrganizations(); err != nil { - // ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) - // return - // } - // ctx.Data["Orgs"] = ctx.User.Orgs + if err := ctx.User.GetOrganizations(); err != nil { + ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) + return + } + ctx.Data["Orgs"] = ctx.User.Orgs ctx.Data["ContextUser"] = ctx.User repos, err := models.GetRepositories(ctx.User.Id, true) @@ -40,13 +40,16 @@ func Dashboard(ctx *middleware.Context) { ctx.Handle(500, "GetRepositories", err) return } + for _, repo := range repos { + repo.Owner = ctx.User + } ctx.Data["Repos"] = repos - // ctx.Data["CollaborativeRepos"], err = models.GetCollaborativeRepos(ctx.User.Name) - // if err != nil { - // ctx.Handle(500, "home.Dashboard(GetCollaborativeRepos)", err) - // return - // } + ctx.Data["CollaborativeRepos"], err = models.GetCollaborativeRepos(ctx.User.Name) + if err != nil { + ctx.Handle(500, "GetCollaborativeRepos", err) + return + } actions, err := models.GetFeeds(ctx.User.Id, 0, true) if err != nil { diff --git a/routers/user/setting.go b/routers/user/setting.go index e4d6ff9ce7..761052144f 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -19,6 +19,7 @@ const ( SETTINGS_PASSWORD base.TplName = "user/settings/password" SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys" SETTINGS_SOCIAL base.TplName = "user/settings/social" + SETTINGS_ORGS base.TplName = "user/settings/orgs" SETTINGS_DELETE base.TplName = "user/settings/delete" NOTIFICATION base.TplName = "user/notification" SECURITY base.TplName = "user/security" @@ -232,6 +233,13 @@ func SettingsSocial(ctx *middleware.Context) { ctx.HTML(200, SETTINGS_SOCIAL) } +func SettingsOrgs(ctx *middleware.Context) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsUserSettings"] = true + ctx.Data["PageIsSettingsOrgs"] = true + ctx.HTML(200, SETTINGS_ORGS) +} + func SettingsDelete(ctx *middleware.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsUserSettings"] = true diff --git a/templates/.VERSION b/templates/.VERSION index 036f90911f..a4f4593d76 100644 --- a/templates/.VERSION +++ b/templates/.VERSION @@ -1 +1 @@ -0.4.7.0725 Alpha \ No newline at end of file +0.4.7.0726 Alpha \ No newline at end of file diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 10a53b5397..34e710bf62 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -176,11 +176,11 @@
Enable Set Cookie
GC Interval Time
-
{{.SessionConfig.GcIntervalTime}} seconds
+
{{.SessionConfig.Gclifetime}} seconds
Session Life Time
-
{{.SessionConfig.SessionLifeTime}} seconds
+
{{.SessionConfig.Maxlifetime}} seconds
HTTPS Only
-
+
Cookie Life Time
{{.SessionConfig.CookieLifeTime}} seconds
Session ID Hash Function
diff --git a/templates/ng/base/head.tmpl b/templates/ng/base/head.tmpl index 0cd7686ffb..bab914bfe7 100644 --- a/templates/ng/base/head.tmpl +++ b/templates/ng/base/head.tmpl @@ -12,10 +12,10 @@ - + - + diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl index 385f9d5bae..420e973a50 100644 --- a/templates/repo/commits.tmpl +++ b/templates/repo/commits.tmpl @@ -34,7 +34,7 @@ {{.Author.Name}} {{SubStr .Id.String 0 10}} {{.Summary}} - {{TimeSince .Author.When}} + {{TimeSince .Author.When $.Lang}} {{end}} diff --git a/templates/repo/diff.tmpl b/templates/repo/diff.tmpl index c85caa21ec..6adea04593 100644 --- a/templates/repo/diff.tmpl +++ b/templates/repo/diff.tmpl @@ -20,7 +20,7 @@

{{.Commit.Author.Name}} - {{TimeSince .Commit.Author.When}} + {{TimeSince .Commit.Author.When $.Lang}}

diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index ffbdcc8379..099e41b2dd 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -86,7 +86,7 @@

{{.Poster.Name}} - {{TimeSince .Created}} + {{TimeSince .Created $.Lang}} {{.NumComments}}

diff --git a/templates/repo/issue/view.tmpl b/templates/repo/issue/view.tmpl index aec50ca62e..c12de7dd7e 100644 --- a/templates/repo/issue/view.tmpl +++ b/templates/repo/issue/view.tmpl @@ -18,7 +18,7 @@ {{end}} {{if .Issue.IsClosed}}Closed{{else}}Open{{end}} {{.Issue.Poster.Name}} opened this issue - {{TimeSince .Issue.Created}} · {{.Issue.NumComments}} comments + {{TimeSince .Issue.Created $.Lang}} · {{.Issue.NumComments}} comments

@@ -66,7 +66,7 @@
- {{.Poster.Name}} commented {{TimeSince .Created}} + {{.Poster.Name}} commented {{TimeSince .Created $.Lang}} Owner @@ -95,14 +95,14 @@
- {{.Poster.Name}} Reopened this issue {{TimeSince .Created}} + {{.Poster.Name}} Reopened this issue {{TimeSince .Created $.Lang}}
{{else if eq .Type 2}}
- {{.Poster.Name}} Closed this issue {{TimeSince .Created}} + {{.Poster.Name}} Closed this issue {{TimeSince .Created $.Lang}}
{{else if eq .Type 4}} diff --git a/templates/status/401.tmpl b/templates/status/401.tmpl index 6e24302fef..2c38d90fb5 100644 --- a/templates/status/401.tmpl +++ b/templates/status/401.tmpl @@ -1,6 +1,6 @@ -{{template "base/head" .}} -{{template "base/header" .}} +{{template "ng/base/head" .}} +{{template "ng/base/header" .}}
401 Unauthorized: {{.ErrorMsg}}
-{{template "base/footer" .}} \ No newline at end of file +{{template "ng/base/footer" .}} \ No newline at end of file diff --git a/templates/status/404.tmpl b/templates/status/404.tmpl index 7062fb122e..2d04b55917 100644 --- a/templates/status/404.tmpl +++ b/templates/status/404.tmpl @@ -6,5 +6,6 @@

Application Version: {{AppVer}}

If you think this is an error, please open an issue on GitHub.

+

We're currently working on 0.5 beta version, many pages may be missing at this time. Sorry for confusion!

{{template "ng/base/footer" .}} diff --git a/templates/user/dashboard/dashboard.tmpl b/templates/user/dashboard/dashboard.tmpl index e9027230a5..209a495b58 100644 --- a/templates/user/dashboard/dashboard.tmpl +++ b/templates/user/dashboard/dashboard.tmpl @@ -70,15 +70,7 @@
@@ -87,42 +79,9 @@
diff --git a/templates/user/dashboard/repo_list.tmpl b/templates/user/dashboard/repo_list.tmpl new file mode 100644 index 0000000000..e3d35e8ecc --- /dev/null +++ b/templates/user/dashboard/repo_list.tmpl @@ -0,0 +1,12 @@ +
  • + + + + + {{.Name}} + + + {{.NumStars}} + + +
  • \ No newline at end of file diff --git a/templates/user/issues.tmpl b/templates/user/issues.tmpl index c4ad64a4cf..93e798aa30 100644 --- a/templates/user/issues.tmpl +++ b/templates/user/issues.tmpl @@ -41,7 +41,7 @@

    {{.Poster.Name}} - {{TimeSince .Created}} + {{TimeSince .Created $.Lang}} {{.NumComments}}

    diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 0c9ada0130..4f80f1659d 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -50,8 +50,8 @@
      {{range .Feeds}}
    • - -
      {{TimeSince .Created}}
      {{ActionDesc . | str2html}}
      + +
      {{TimeSince .Created $.Lang}}
      {{ActionDesc . | str2html}}
    • {{else}} @@ -69,7 +69,7 @@ {{.Name}}{{if .IsPrivate}} Private{{end}}

      {{.Description}}

      -
      Last updated {{.Updated|TimeSince}}
      +
      Last updated {{TimeSince .Updated $.Lang}}
      {{end}}
    diff --git a/templates/user/settings/nav.tmpl b/templates/user/settings/nav.tmpl index d6d20dee96..cae120527c 100644 --- a/templates/user/settings/nav.tmpl +++ b/templates/user/settings/nav.tmpl @@ -6,6 +6,7 @@
  • {{.i18n.Tr "settings.password"}}
  • {{.i18n.Tr "settings.ssh_keys"}}
  • {{.i18n.Tr "settings.social"}}
  • +
  • {{.i18n.Tr "settings.orgs"}}
  • {{.i18n.Tr "settings.delete"}}
  • diff --git a/templates/user/settings/orgs.tmpl b/templates/user/settings/orgs.tmpl new file mode 100644 index 0000000000..fb9096c3da --- /dev/null +++ b/templates/user/settings/orgs.tmpl @@ -0,0 +1,18 @@ +{{template "ng/base/head" .}} +{{template "ng/base/header" .}} +
    +
    + {{template "user/settings/nav" .}} +
    +
    + {{template "ng/base/alert" .}} +
    +
    +

    {{.i18n.Tr "settings.manage_orgs"}}

    +
    +
    +
    +
    +
    +
    +{{template "ng/base/footer" .}} \ No newline at end of file