From 4400d2fdd933204044aeb18ce7d8613c53aa87c0 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Sun, 11 Jun 2017 00:34:14 -0400 Subject: Refactoring: rename package routers -> routes --- cmd/hook.go | 2 +- cmd/serv.go | 2 +- cmd/web.go | 30 +- gogs.go | 2 +- routers/admin/admin.go | 258 ------- routers/admin/auths.go | 265 ------- routers/admin/notice.go | 72 -- routers/admin/orgs.go | 31 - routers/admin/repos.go | 87 --- routers/admin/users.go | 262 ------- routers/api/v1/admin/org.go | 44 -- routers/api/v1/admin/org_repo.go | 50 -- routers/api/v1/admin/org_team.go | 60 -- routers/api/v1/admin/repo.go | 23 - routers/api/v1/admin/user.go | 160 ----- routers/api/v1/api.go | 356 ---------- routers/api/v1/convert/convert.go | 127 ---- routers/api/v1/convert/utils.go | 19 - routers/api/v1/misc/markdown.go | 42 -- routers/api/v1/org/org.go | 66 -- routers/api/v1/org/team.go | 26 - routers/api/v1/repo/branch.go | 55 -- routers/api/v1/repo/collaborators.go | 94 --- routers/api/v1/repo/file.go | 72 -- routers/api/v1/repo/hook.go | 186 ----- routers/api/v1/repo/issue.go | 201 ------ routers/api/v1/repo/issue_comment.go | 128 ---- routers/api/v1/repo/issue_label.go | 169 ----- routers/api/v1/repo/key.go | 114 --- routers/api/v1/repo/label.go | 110 --- routers/api/v1/repo/milestone.go | 103 --- routers/api/v1/repo/repo.go | 380 ---------- routers/api/v1/user/app.go | 40 -- routers/api/v1/user/email.go | 82 --- routers/api/v1/user/follower.go | 120 ---- routers/api/v1/user/key.go | 120 ---- routers/api/v1/user/user.go | 75 -- routers/dev/template.go | 24 - routers/home.go | 163 ----- routers/install.go | 392 ----------- routers/org/members.go | 123 ---- routers/org/org.go | 56 -- routers/org/setting.go | 168 ----- routers/org/teams.go | 271 -------- routers/repo/branch.go | 142 ---- routers/repo/commit.go | 239 ------- routers/repo/download.go | 60 -- routers/repo/editor.go | 571 --------------- routers/repo/http.go | 447 ------------ routers/repo/issue.go | 1263 ---------------------------------- routers/repo/pull.go | 763 -------------------- routers/repo/release.go | 332 --------- routers/repo/repo.go | 335 --------- routers/repo/setting.go | 631 ----------------- routers/repo/view.go | 367 ---------- routers/repo/webhook.go | 558 --------------- routers/repo/wiki.go | 274 -------- routers/user/auth.go | 534 -------------- routers/user/home.go | 424 ------------ routers/user/profile.go | 169 ----- routers/user/setting.go | 664 ------------------ routes/admin/admin.go | 258 +++++++ routes/admin/auths.go | 265 +++++++ routes/admin/notice.go | 72 ++ routes/admin/orgs.go | 31 + routes/admin/repos.go | 87 +++ routes/admin/users.go | 262 +++++++ routes/api/v1/admin/org.go | 44 ++ routes/api/v1/admin/org_repo.go | 50 ++ routes/api/v1/admin/org_team.go | 60 ++ routes/api/v1/admin/repo.go | 23 + routes/api/v1/admin/user.go | 160 +++++ routes/api/v1/api.go | 356 ++++++++++ routes/api/v1/convert/convert.go | 127 ++++ routes/api/v1/convert/utils.go | 19 + routes/api/v1/misc/markdown.go | 42 ++ routes/api/v1/org/org.go | 66 ++ routes/api/v1/org/team.go | 26 + routes/api/v1/repo/branch.go | 55 ++ routes/api/v1/repo/collaborators.go | 94 +++ routes/api/v1/repo/file.go | 72 ++ routes/api/v1/repo/hook.go | 186 +++++ routes/api/v1/repo/issue.go | 201 ++++++ routes/api/v1/repo/issue_comment.go | 128 ++++ routes/api/v1/repo/issue_label.go | 169 +++++ routes/api/v1/repo/key.go | 114 +++ routes/api/v1/repo/label.go | 110 +++ routes/api/v1/repo/milestone.go | 103 +++ routes/api/v1/repo/repo.go | 380 ++++++++++ routes/api/v1/user/app.go | 40 ++ routes/api/v1/user/email.go | 82 +++ routes/api/v1/user/follower.go | 120 ++++ routes/api/v1/user/key.go | 120 ++++ routes/api/v1/user/user.go | 75 ++ routes/dev/template.go | 24 + routes/home.go | 163 +++++ routes/install.go | 392 +++++++++++ routes/org/members.go | 123 ++++ routes/org/org.go | 56 ++ routes/org/setting.go | 168 +++++ routes/org/teams.go | 271 ++++++++ routes/repo/branch.go | 142 ++++ routes/repo/commit.go | 239 +++++++ routes/repo/download.go | 60 ++ routes/repo/editor.go | 571 +++++++++++++++ routes/repo/http.go | 447 ++++++++++++ routes/repo/issue.go | 1263 ++++++++++++++++++++++++++++++++++ routes/repo/pull.go | 763 ++++++++++++++++++++ routes/repo/release.go | 332 +++++++++ routes/repo/repo.go | 335 +++++++++ routes/repo/setting.go | 631 +++++++++++++++++ routes/repo/view.go | 367 ++++++++++ routes/repo/webhook.go | 558 +++++++++++++++ routes/repo/wiki.go | 274 ++++++++ routes/user/auth.go | 534 ++++++++++++++ routes/user/home.go | 424 ++++++++++++ routes/user/profile.go | 169 +++++ routes/user/setting.go | 664 ++++++++++++++++++ templates/.VERSION | 2 +- 119 files changed, 12986 insertions(+), 12986 deletions(-) delete mode 100644 routers/admin/admin.go delete mode 100644 routers/admin/auths.go delete mode 100644 routers/admin/notice.go delete mode 100644 routers/admin/orgs.go delete mode 100644 routers/admin/repos.go delete mode 100644 routers/admin/users.go delete mode 100644 routers/api/v1/admin/org.go delete mode 100644 routers/api/v1/admin/org_repo.go delete mode 100644 routers/api/v1/admin/org_team.go delete mode 100644 routers/api/v1/admin/repo.go delete mode 100644 routers/api/v1/admin/user.go delete mode 100644 routers/api/v1/api.go delete mode 100644 routers/api/v1/convert/convert.go delete mode 100644 routers/api/v1/convert/utils.go delete mode 100644 routers/api/v1/misc/markdown.go delete mode 100644 routers/api/v1/org/org.go delete mode 100644 routers/api/v1/org/team.go delete mode 100644 routers/api/v1/repo/branch.go delete mode 100644 routers/api/v1/repo/collaborators.go delete mode 100644 routers/api/v1/repo/file.go delete mode 100644 routers/api/v1/repo/hook.go delete mode 100644 routers/api/v1/repo/issue.go delete mode 100644 routers/api/v1/repo/issue_comment.go delete mode 100644 routers/api/v1/repo/issue_label.go delete mode 100644 routers/api/v1/repo/key.go delete mode 100644 routers/api/v1/repo/label.go delete mode 100644 routers/api/v1/repo/milestone.go delete mode 100644 routers/api/v1/repo/repo.go delete mode 100644 routers/api/v1/user/app.go delete mode 100644 routers/api/v1/user/email.go delete mode 100644 routers/api/v1/user/follower.go delete mode 100644 routers/api/v1/user/key.go delete mode 100644 routers/api/v1/user/user.go delete mode 100644 routers/dev/template.go delete mode 100644 routers/home.go delete mode 100644 routers/install.go delete mode 100644 routers/org/members.go delete mode 100644 routers/org/org.go delete mode 100644 routers/org/setting.go delete mode 100644 routers/org/teams.go delete mode 100644 routers/repo/branch.go delete mode 100644 routers/repo/commit.go delete mode 100644 routers/repo/download.go delete mode 100644 routers/repo/editor.go delete mode 100644 routers/repo/http.go delete mode 100644 routers/repo/issue.go delete mode 100644 routers/repo/pull.go delete mode 100644 routers/repo/release.go delete mode 100644 routers/repo/repo.go delete mode 100644 routers/repo/setting.go delete mode 100644 routers/repo/view.go delete mode 100644 routers/repo/webhook.go delete mode 100644 routers/repo/wiki.go delete mode 100644 routers/user/auth.go delete mode 100644 routers/user/home.go delete mode 100644 routers/user/profile.go delete mode 100644 routers/user/setting.go create mode 100644 routes/admin/admin.go create mode 100644 routes/admin/auths.go create mode 100644 routes/admin/notice.go create mode 100644 routes/admin/orgs.go create mode 100644 routes/admin/repos.go create mode 100644 routes/admin/users.go create mode 100644 routes/api/v1/admin/org.go create mode 100644 routes/api/v1/admin/org_repo.go create mode 100644 routes/api/v1/admin/org_team.go create mode 100644 routes/api/v1/admin/repo.go create mode 100644 routes/api/v1/admin/user.go create mode 100644 routes/api/v1/api.go create mode 100644 routes/api/v1/convert/convert.go create mode 100644 routes/api/v1/convert/utils.go create mode 100644 routes/api/v1/misc/markdown.go create mode 100644 routes/api/v1/org/org.go create mode 100644 routes/api/v1/org/team.go create mode 100644 routes/api/v1/repo/branch.go create mode 100644 routes/api/v1/repo/collaborators.go create mode 100644 routes/api/v1/repo/file.go create mode 100644 routes/api/v1/repo/hook.go create mode 100644 routes/api/v1/repo/issue.go create mode 100644 routes/api/v1/repo/issue_comment.go create mode 100644 routes/api/v1/repo/issue_label.go create mode 100644 routes/api/v1/repo/key.go create mode 100644 routes/api/v1/repo/label.go create mode 100644 routes/api/v1/repo/milestone.go create mode 100644 routes/api/v1/repo/repo.go create mode 100644 routes/api/v1/user/app.go create mode 100644 routes/api/v1/user/email.go create mode 100644 routes/api/v1/user/follower.go create mode 100644 routes/api/v1/user/key.go create mode 100644 routes/api/v1/user/user.go create mode 100644 routes/dev/template.go create mode 100644 routes/home.go create mode 100644 routes/install.go create mode 100644 routes/org/members.go create mode 100644 routes/org/org.go create mode 100644 routes/org/setting.go create mode 100644 routes/org/teams.go create mode 100644 routes/repo/branch.go create mode 100644 routes/repo/commit.go create mode 100644 routes/repo/download.go create mode 100644 routes/repo/editor.go create mode 100644 routes/repo/http.go create mode 100644 routes/repo/issue.go create mode 100644 routes/repo/pull.go create mode 100644 routes/repo/release.go create mode 100644 routes/repo/repo.go create mode 100644 routes/repo/setting.go create mode 100644 routes/repo/view.go create mode 100644 routes/repo/webhook.go create mode 100644 routes/repo/wiki.go create mode 100644 routes/user/auth.go create mode 100644 routes/user/home.go create mode 100644 routes/user/profile.go create mode 100644 routes/user/setting.go diff --git a/cmd/hook.go b/cmd/hook.go index 602690c6..0ff4a1c6 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -23,7 +23,7 @@ import ( "github.com/gogits/gogs/models" "github.com/gogits/gogs/pkg/httplib" "github.com/gogits/gogs/pkg/setting" - http "github.com/gogits/gogs/routers/repo" + http "github.com/gogits/gogs/routes/repo" ) var ( diff --git a/cmd/serv.go b/cmd/serv.go index d89ad5bd..dd4c1217 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -19,7 +19,7 @@ import ( "github.com/gogits/gogs/models" "github.com/gogits/gogs/models/errors" "github.com/gogits/gogs/pkg/setting" - http "github.com/gogits/gogs/routers/repo" + http "github.com/gogits/gogs/routes/repo" ) const ( diff --git a/cmd/web.go b/cmd/web.go index e16e40b9..69754230 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -36,13 +36,13 @@ import ( "github.com/gogits/gogs/pkg/mailer" "github.com/gogits/gogs/pkg/setting" "github.com/gogits/gogs/pkg/template" - "github.com/gogits/gogs/routers" - "github.com/gogits/gogs/routers/admin" - apiv1 "github.com/gogits/gogs/routers/api/v1" - "github.com/gogits/gogs/routers/dev" - "github.com/gogits/gogs/routers/org" - "github.com/gogits/gogs/routers/repo" - "github.com/gogits/gogs/routers/user" + "github.com/gogits/gogs/routes" + "github.com/gogits/gogs/routes/admin" + apiv1 "github.com/gogits/gogs/routes/api/v1" + "github.com/gogits/gogs/routes/dev" + "github.com/gogits/gogs/routes/org" + "github.com/gogits/gogs/routes/repo" + "github.com/gogits/gogs/routes/user" ) var Web = cli.Command{ @@ -160,7 +160,7 @@ func runWeb(c *cli.Context) error { if c.IsSet("config") { setting.CustomConf = c.String("config") } - routers.GlobalInit() + routes.GlobalInit() checkVersion() m := newMacaron() @@ -175,17 +175,17 @@ func runWeb(c *cli.Context) error { // FIXME: not all routes need go through same middlewares. // Especially some AJAX requests, we can reduce middleware number to improve performance. // Routers. - m.Get("/", ignSignIn, routers.Home) + m.Get("/", ignSignIn, routes.Home) m.Group("/explore", func() { m.Get("", func(c *context.Context) { c.Redirect(setting.AppSubURL + "/explore/repos") }) - m.Get("/repos", routers.ExploreRepos) - m.Get("/users", routers.ExploreUsers) - m.Get("/organizations", routers.ExploreOrganizations) + m.Get("/repos", routes.ExploreRepos) + m.Get("/users", routes.ExploreUsers) + m.Get("/organizations", routes.ExploreOrganizations) }, ignSignIn) - m.Combo("/install", routers.InstallInit).Get(routers.Install). - Post(bindIgnErr(form.Install{}), routers.InstallPost) + m.Combo("/install", routes.InstallInit).Get(routes.Install). + Post(bindIgnErr(form.Install{}), routes.InstallPost) m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues) // ***** START: User ***** @@ -651,7 +651,7 @@ func runWeb(c *cli.Context) error { }) // Not found handler. - m.NotFound(routers.NotFound) + m.NotFound(routes.NotFound) // Flag for port number in case first time run conflict. if c.IsSet("port") { diff --git a/gogs.go b/gogs.go index 97d22146..8549e431 100644 --- a/gogs.go +++ b/gogs.go @@ -16,7 +16,7 @@ import ( "github.com/gogits/gogs/pkg/setting" ) -const APP_VER = "0.11.19.0609" +const APP_VER = "0.11.19.0611" func init() { setting.AppVer = APP_VER diff --git a/routers/admin/admin.go b/routers/admin/admin.go deleted file mode 100644 index 0d5eb7a6..00000000 --- a/routers/admin/admin.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - "encoding/json" - "fmt" - "runtime" - "strings" - "time" - - "github.com/Unknwon/com" - "gopkg.in/macaron.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/cron" - "github.com/gogits/gogs/pkg/mailer" - "github.com/gogits/gogs/pkg/process" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - DASHBOARD = "admin/dashboard" - CONFIG = "admin/config" - MONITOR = "admin/monitor" -) - -var ( - startTime = time.Now() -) - -var sysStatus struct { - Uptime string - NumGoroutine int - - // General statistics. - MemAllocated string // bytes allocated and still in use - MemTotal string // bytes allocated (even if freed) - MemSys string // bytes obtained from system (sum of XxxSys below) - Lookups uint64 // number of pointer lookups - MemMallocs uint64 // number of mallocs - MemFrees uint64 // number of frees - - // Main allocation heap statistics. - HeapAlloc string // bytes allocated and still in use - HeapSys string // bytes obtained from system - HeapIdle string // bytes in idle spans - HeapInuse string // bytes in non-idle span - HeapReleased string // bytes released to the OS - HeapObjects uint64 // total number of allocated objects - - // Low-level fixed-size structure allocator statistics. - // Inuse is bytes used now. - // Sys is bytes obtained from system. - StackInuse string // bootstrap stacks - StackSys string - MSpanInuse string // mspan structures - MSpanSys string - MCacheInuse string // mcache structures - MCacheSys string - BuckHashSys string // profiling bucket hash table - GCSys string // GC metadata - OtherSys string // other system allocations - - // Garbage collector statistics. - NextGC string // next run in HeapAlloc time (bytes) - LastGC string // last run in absolute time (ns) - PauseTotalNs string - PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] - NumGC uint32 -} - -func updateSystemStatus() { - sysStatus.Uptime = tool.TimeSincePro(startTime) - - m := new(runtime.MemStats) - runtime.ReadMemStats(m) - sysStatus.NumGoroutine = runtime.NumGoroutine() - - sysStatus.MemAllocated = tool.FileSize(int64(m.Alloc)) - sysStatus.MemTotal = tool.FileSize(int64(m.TotalAlloc)) - sysStatus.MemSys = tool.FileSize(int64(m.Sys)) - sysStatus.Lookups = m.Lookups - sysStatus.MemMallocs = m.Mallocs - sysStatus.MemFrees = m.Frees - - sysStatus.HeapAlloc = tool.FileSize(int64(m.HeapAlloc)) - sysStatus.HeapSys = tool.FileSize(int64(m.HeapSys)) - sysStatus.HeapIdle = tool.FileSize(int64(m.HeapIdle)) - sysStatus.HeapInuse = tool.FileSize(int64(m.HeapInuse)) - sysStatus.HeapReleased = tool.FileSize(int64(m.HeapReleased)) - sysStatus.HeapObjects = m.HeapObjects - - sysStatus.StackInuse = tool.FileSize(int64(m.StackInuse)) - sysStatus.StackSys = tool.FileSize(int64(m.StackSys)) - sysStatus.MSpanInuse = tool.FileSize(int64(m.MSpanInuse)) - sysStatus.MSpanSys = tool.FileSize(int64(m.MSpanSys)) - sysStatus.MCacheInuse = tool.FileSize(int64(m.MCacheInuse)) - sysStatus.MCacheSys = tool.FileSize(int64(m.MCacheSys)) - sysStatus.BuckHashSys = tool.FileSize(int64(m.BuckHashSys)) - sysStatus.GCSys = tool.FileSize(int64(m.GCSys)) - sysStatus.OtherSys = tool.FileSize(int64(m.OtherSys)) - - sysStatus.NextGC = tool.FileSize(int64(m.NextGC)) - sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) - sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) - sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) - sysStatus.NumGC = m.NumGC -} - -// Operation types. -type AdminOperation int - -const ( - CLEAN_INACTIVATE_USER AdminOperation = iota + 1 - CLEAN_REPO_ARCHIVES - CLEAN_MISSING_REPOS - GIT_GC_REPOS - SYNC_SSH_AUTHORIZED_KEY - SYNC_REPOSITORY_HOOKS - REINIT_MISSING_REPOSITORY -) - -func Dashboard(c *context.Context) { - c.Data["Title"] = c.Tr("admin.dashboard") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminDashboard"] = true - - // Run operation. - op, _ := com.StrTo(c.Query("op")).Int() - if op > 0 { - var err error - var success string - - switch AdminOperation(op) { - case CLEAN_INACTIVATE_USER: - success = c.Tr("admin.dashboard.delete_inactivate_accounts_success") - err = models.DeleteInactivateUsers() - case CLEAN_REPO_ARCHIVES: - success = c.Tr("admin.dashboard.delete_repo_archives_success") - err = models.DeleteRepositoryArchives() - case CLEAN_MISSING_REPOS: - success = c.Tr("admin.dashboard.delete_missing_repos_success") - err = models.DeleteMissingRepositories() - case GIT_GC_REPOS: - success = c.Tr("admin.dashboard.git_gc_repos_success") - err = models.GitGcRepos() - case SYNC_SSH_AUTHORIZED_KEY: - success = c.Tr("admin.dashboard.resync_all_sshkeys_success") - err = models.RewriteAllPublicKeys() - case SYNC_REPOSITORY_HOOKS: - success = c.Tr("admin.dashboard.resync_all_hooks_success") - err = models.SyncRepositoryHooks() - case REINIT_MISSING_REPOSITORY: - success = c.Tr("admin.dashboard.reinit_missing_repos_success") - err = models.ReinitMissingRepositories() - } - - if err != nil { - c.Flash.Error(err.Error()) - } else { - c.Flash.Success(success) - } - c.Redirect(setting.AppSubURL + "/admin") - return - } - - c.Data["Stats"] = models.GetStatistic() - // FIXME: update periodically - updateSystemStatus() - c.Data["SysStatus"] = sysStatus - c.HTML(200, DASHBOARD) -} - -func SendTestMail(c *context.Context) { - email := c.Query("email") - // Send a test email to the user's email address and redirect back to Config - if err := mailer.SendTestMail(email); err != nil { - c.Flash.Error(c.Tr("admin.config.test_mail_failed", email, err)) - } else { - c.Flash.Info(c.Tr("admin.config.test_mail_sent", email)) - } - - c.Redirect(setting.AppSubURL + "/admin/config") -} - -func Config(c *context.Context) { - c.Data["Title"] = c.Tr("admin.config") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminConfig"] = true - - c.Data["AppURL"] = setting.AppURL - c.Data["Domain"] = setting.Domain - c.Data["OfflineMode"] = setting.OfflineMode - c.Data["DisableRouterLog"] = setting.DisableRouterLog - c.Data["RunUser"] = setting.RunUser - c.Data["RunMode"] = strings.Title(macaron.Env) - c.Data["StaticRootPath"] = setting.StaticRootPath - c.Data["LogRootPath"] = setting.LogRootPath - c.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser - - c.Data["SSH"] = setting.SSH - - c.Data["RepoRootPath"] = setting.RepoRootPath - c.Data["ScriptType"] = setting.ScriptType - c.Data["Repository"] = setting.Repository - - c.Data["Service"] = setting.Service - c.Data["DbCfg"] = models.DbCfg - c.Data["Webhook"] = setting.Webhook - - c.Data["MailerEnabled"] = false - if setting.MailService != nil { - c.Data["MailerEnabled"] = true - c.Data["Mailer"] = setting.MailService - } - - c.Data["CacheAdapter"] = setting.CacheAdapter - c.Data["CacheInterval"] = setting.CacheInterval - c.Data["CacheConn"] = setting.CacheConn - - c.Data["SessionConfig"] = setting.SessionConfig - - c.Data["DisableGravatar"] = setting.DisableGravatar - c.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar - - c.Data["GitVersion"] = setting.Git.Version - c.Data["Git"] = setting.Git - - type logger struct { - Mode, Config string - } - loggers := make([]*logger, len(setting.LogModes)) - for i := range setting.LogModes { - loggers[i] = &logger{ - Mode: strings.Title(setting.LogModes[i]), - } - - result, _ := json.MarshalIndent(setting.LogConfigs[i], "", " ") - loggers[i].Config = string(result) - } - c.Data["Loggers"] = loggers - - c.HTML(200, CONFIG) -} - -func Monitor(c *context.Context) { - c.Data["Title"] = c.Tr("admin.monitor") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminMonitor"] = true - c.Data["Processes"] = process.Processes - c.Data["Entries"] = cron.ListTasks() - c.HTML(200, MONITOR) -} diff --git a/routers/admin/auths.go b/routers/admin/auths.go deleted file mode 100644 index 56a0aad6..00000000 --- a/routers/admin/auths.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - "fmt" - - "github.com/Unknwon/com" - "github.com/go-xorm/core" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/auth/ldap" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - AUTHS = "admin/auth/list" - AUTH_NEW = "admin/auth/new" - AUTH_EDIT = "admin/auth/edit" -) - -func Authentications(c *context.Context) { - c.Data["Title"] = c.Tr("admin.authentication") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminAuthentications"] = true - - var err error - c.Data["Sources"], err = models.LoginSources() - if err != nil { - c.Handle(500, "LoginSources", err) - return - } - - c.Data["Total"] = models.CountLoginSources() - c.HTML(200, AUTHS) -} - -type dropdownItem struct { - Name string - Type interface{} -} - -var ( - authSources = []dropdownItem{ - {models.LoginNames[models.LOGIN_LDAP], models.LOGIN_LDAP}, - {models.LoginNames[models.LOGIN_DLDAP], models.LOGIN_DLDAP}, - {models.LoginNames[models.LOGIN_SMTP], models.LOGIN_SMTP}, - {models.LoginNames[models.LOGIN_PAM], models.LOGIN_PAM}, - } - securityProtocols = []dropdownItem{ - {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED}, - {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_LDAPS], ldap.SECURITY_PROTOCOL_LDAPS}, - {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_START_TLS], ldap.SECURITY_PROTOCOL_START_TLS}, - } -) - -func NewAuthSource(c *context.Context) { - c.Data["Title"] = c.Tr("admin.auths.new") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminAuthentications"] = true - - c.Data["type"] = models.LOGIN_LDAP - c.Data["CurrentTypeName"] = models.LoginNames[models.LOGIN_LDAP] - c.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED] - c.Data["smtp_auth"] = "PLAIN" - c.Data["is_active"] = true - c.Data["AuthSources"] = authSources - c.Data["SecurityProtocols"] = securityProtocols - c.Data["SMTPAuths"] = models.SMTPAuths - c.HTML(200, AUTH_NEW) -} - -func parseLDAPConfig(f form.Authentication) *models.LDAPConfig { - return &models.LDAPConfig{ - Source: &ldap.Source{ - Name: f.Name, - Host: f.Host, - Port: f.Port, - SecurityProtocol: ldap.SecurityProtocol(f.SecurityProtocol), - SkipVerify: f.SkipVerify, - BindDN: f.BindDN, - UserDN: f.UserDN, - BindPassword: f.BindPassword, - UserBase: f.UserBase, - AttributeUsername: f.AttributeUsername, - AttributeName: f.AttributeName, - AttributeSurname: f.AttributeSurname, - AttributeMail: f.AttributeMail, - AttributesInBind: f.AttributesInBind, - Filter: f.Filter, - GroupEnabled: f.GroupEnabled, - GroupDN: f.GroupDN, - GroupFilter: f.GroupFilter, - GroupMemberUID: f.GroupMemberUID, - UserUID: f.UserUID, - AdminFilter: f.AdminFilter, - Enabled: true, - }, - } -} - -func parseSMTPConfig(f form.Authentication) *models.SMTPConfig { - return &models.SMTPConfig{ - Auth: f.SMTPAuth, - Host: f.SMTPHost, - Port: f.SMTPPort, - AllowedDomains: f.AllowedDomains, - TLS: f.TLS, - SkipVerify: f.SkipVerify, - } -} - -func NewAuthSourcePost(c *context.Context, f form.Authentication) { - c.Data["Title"] = c.Tr("admin.auths.new") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminAuthentications"] = true - - c.Data["CurrentTypeName"] = models.LoginNames[models.LoginType(f.Type)] - c.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SecurityProtocol(f.SecurityProtocol)] - c.Data["AuthSources"] = authSources - c.Data["SecurityProtocols"] = securityProtocols - c.Data["SMTPAuths"] = models.SMTPAuths - - hasTLS := false - var config core.Conversion - switch models.LoginType(f.Type) { - case models.LOGIN_LDAP, models.LOGIN_DLDAP: - config = parseLDAPConfig(f) - hasTLS = ldap.SecurityProtocol(f.SecurityProtocol) > ldap.SECURITY_PROTOCOL_UNENCRYPTED - case models.LOGIN_SMTP: - config = parseSMTPConfig(f) - hasTLS = true - case models.LOGIN_PAM: - config = &models.PAMConfig{ - ServiceName: f.PAMServiceName, - } - default: - c.Error(400) - return - } - c.Data["HasTLS"] = hasTLS - - if c.HasError() { - c.HTML(200, AUTH_NEW) - return - } - - if err := models.CreateLoginSource(&models.LoginSource{ - Type: models.LoginType(f.Type), - Name: f.Name, - IsActived: f.IsActive, - Cfg: config, - }); err != nil { - if models.IsErrLoginSourceAlreadyExist(err) { - c.Data["Err_Name"] = true - c.RenderWithErr(c.Tr("admin.auths.login_source_exist", err.(models.ErrLoginSourceAlreadyExist).Name), AUTH_NEW, f) - } else { - c.Handle(500, "CreateSource", err) - } - return - } - - log.Trace("Authentication created by admin(%s): %s", c.User.Name, f.Name) - - c.Flash.Success(c.Tr("admin.auths.new_success", f.Name)) - c.Redirect(setting.AppSubURL + "/admin/auths") -} - -func EditAuthSource(c *context.Context) { - c.Data["Title"] = c.Tr("admin.auths.edit") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminAuthentications"] = true - - c.Data["SecurityProtocols"] = securityProtocols - c.Data["SMTPAuths"] = models.SMTPAuths - - source, err := models.GetLoginSourceByID(c.ParamsInt64(":authid")) - if err != nil { - c.Handle(500, "GetLoginSourceByID", err) - return - } - c.Data["Source"] = source - c.Data["HasTLS"] = source.HasTLS() - - c.HTML(200, AUTH_EDIT) -} - -func EditAuthSourcePost(c *context.Context, f form.Authentication) { - c.Data["Title"] = c.Tr("admin.auths.edit") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminAuthentications"] = true - - c.Data["SMTPAuths"] = models.SMTPAuths - - source, err := models.GetLoginSourceByID(c.ParamsInt64(":authid")) - if err != nil { - c.Handle(500, "GetLoginSourceByID", err) - return - } - c.Data["Source"] = source - c.Data["HasTLS"] = source.HasTLS() - - if c.HasError() { - c.HTML(200, AUTH_EDIT) - return - } - - var config core.Conversion - switch models.LoginType(f.Type) { - case models.LOGIN_LDAP, models.LOGIN_DLDAP: - config = parseLDAPConfig(f) - case models.LOGIN_SMTP: - config = parseSMTPConfig(f) - case models.LOGIN_PAM: - config = &models.PAMConfig{ - ServiceName: f.PAMServiceName, - } - default: - c.Error(400) - return - } - - source.Name = f.Name - source.IsActived = f.IsActive - source.Cfg = config - if err := models.UpdateSource(source); err != nil { - c.Handle(500, "UpdateSource", err) - return - } - log.Trace("Authentication changed by admin(%s): %d", c.User.Name, source.ID) - - c.Flash.Success(c.Tr("admin.auths.update_success")) - c.Redirect(setting.AppSubURL + "/admin/auths/" + com.ToStr(f.ID)) -} - -func DeleteAuthSource(c *context.Context) { - source, err := models.GetLoginSourceByID(c.ParamsInt64(":authid")) - if err != nil { - c.Handle(500, "GetLoginSourceByID", err) - return - } - - if err = models.DeleteSource(source); err != nil { - if models.IsErrLoginSourceInUse(err) { - c.Flash.Error(c.Tr("admin.auths.still_in_used")) - } else { - c.Flash.Error(fmt.Sprintf("DeleteSource: %v", err)) - } - c.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/auths/" + c.Params(":authid"), - }) - return - } - log.Trace("Authentication deleted by admin(%s): %d", c.User.Name, source.ID) - - c.Flash.Success(c.Tr("admin.auths.deletion_success")) - c.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/auths", - }) -} diff --git a/routers/admin/notice.go b/routers/admin/notice.go deleted file mode 100644 index c743a1da..00000000 --- a/routers/admin/notice.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - "github.com/Unknwon/com" - "github.com/Unknwon/paginater" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - NOTICES = "admin/notice" -) - -func Notices(c *context.Context) { - c.Data["Title"] = c.Tr("admin.notices") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminNotices"] = true - - total := models.CountNotices() - page := c.QueryInt("page") - if page <= 1 { - page = 1 - } - c.Data["Page"] = paginater.New(int(total), setting.UI.Admin.NoticePagingNum, page, 5) - - notices, err := models.Notices(page, setting.UI.Admin.NoticePagingNum) - if err != nil { - c.Handle(500, "Notices", err) - return - } - c.Data["Notices"] = notices - - c.Data["Total"] = total - c.HTML(200, NOTICES) -} - -func DeleteNotices(c *context.Context) { - strs := c.QueryStrings("ids[]") - ids := make([]int64, 0, len(strs)) - for i := range strs { - id := com.StrTo(strs[i]).MustInt64() - if id > 0 { - ids = append(ids, id) - } - } - - if err := models.DeleteNoticesByIDs(ids); err != nil { - c.Flash.Error("DeleteNoticesByIDs: " + err.Error()) - c.Status(500) - } else { - c.Flash.Success(c.Tr("admin.notices.delete_success")) - c.Status(200) - } -} - -func EmptyNotices(c *context.Context) { - if err := models.DeleteNotices(0, 0); err != nil { - c.Handle(500, "DeleteNotices", err) - return - } - - log.Trace("System notices deleted by admin (%s): [start: %d]", c.User.Name, 0) - c.Flash.Success(c.Tr("admin.notices.delete_success")) - c.Redirect(setting.AppSubURL + "/admin/notices") -} diff --git a/routers/admin/orgs.go b/routers/admin/orgs.go deleted file mode 100644 index 4b995acd..00000000 --- a/routers/admin/orgs.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers" -) - -const ( - ORGS = "admin/org/list" -) - -func Organizations(c *context.Context) { - c.Data["Title"] = c.Tr("admin.organizations") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminOrganizations"] = true - - routers.RenderUserSearch(c, &routers.UserSearchOptions{ - Type: models.USER_TYPE_ORGANIZATION, - Counter: models.CountOrganizations, - Ranger: models.Organizations, - PageSize: setting.UI.Admin.OrgPagingNum, - OrderBy: "id ASC", - TplName: ORGS, - }) -} diff --git a/routers/admin/repos.go b/routers/admin/repos.go deleted file mode 100644 index b4fa2266..00000000 --- a/routers/admin/repos.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - "github.com/Unknwon/paginater" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - REPOS = "admin/repo/list" -) - -func Repos(c *context.Context) { - c.Data["Title"] = c.Tr("admin.repositories") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminRepositories"] = true - - page := c.QueryInt("page") - if page <= 0 { - page = 1 - } - - var ( - repos []*models.Repository - count int64 - err error - ) - - keyword := c.Query("q") - if len(keyword) == 0 { - repos, err = models.Repositories(page, setting.UI.Admin.RepoPagingNum) - if err != nil { - c.Handle(500, "Repositories", err) - return - } - count = models.CountRepositories(true) - } else { - repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ - Keyword: keyword, - OrderBy: "id ASC", - Private: true, - Page: page, - PageSize: setting.UI.Admin.RepoPagingNum, - }) - if err != nil { - c.Handle(500, "SearchRepositoryByName", err) - return - } - } - c.Data["Keyword"] = keyword - c.Data["Total"] = count - c.Data["Page"] = paginater.New(int(count), setting.UI.Admin.RepoPagingNum, page, 5) - - if err = models.RepositoryList(repos).LoadAttributes(); err != nil { - c.Handle(500, "LoadAttributes", err) - return - } - c.Data["Repos"] = repos - - c.HTML(200, REPOS) -} - -func DeleteRepo(c *context.Context) { - repo, err := models.GetRepositoryByID(c.QueryInt64("id")) - if err != nil { - c.Handle(500, "GetRepositoryByID", err) - return - } - - if err := models.DeleteRepository(repo.MustOwner().ID, repo.ID); err != nil { - c.Handle(500, "DeleteRepository", err) - return - } - log.Trace("Repository deleted: %s/%s", repo.MustOwner().Name, repo.Name) - - c.Flash.Success(c.Tr("repo.settings.deletion_success")) - c.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/repos?page=" + c.Query("page"), - }) -} diff --git a/routers/admin/users.go b/routers/admin/users.go deleted file mode 100644 index 237f2dc6..00000000 --- a/routers/admin/users.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - "strings" - - "github.com/Unknwon/com" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/mailer" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers" -) - -const ( - USERS = "admin/user/list" - USER_NEW = "admin/user/new" - USER_EDIT = "admin/user/edit" -) - -func Users(c *context.Context) { - c.Data["Title"] = c.Tr("admin.users") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminUsers"] = true - - routers.RenderUserSearch(c, &routers.UserSearchOptions{ - Type: models.USER_TYPE_INDIVIDUAL, - Counter: models.CountUsers, - Ranger: models.Users, - PageSize: setting.UI.Admin.UserPagingNum, - OrderBy: "id ASC", - TplName: USERS, - }) -} - -func NewUser(c *context.Context) { - c.Data["Title"] = c.Tr("admin.users.new_account") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminUsers"] = true - - c.Data["login_type"] = "0-0" - - sources, err := models.LoginSources() - if err != nil { - c.Handle(500, "LoginSources", err) - return - } - c.Data["Sources"] = sources - - c.Data["CanSendEmail"] = setting.MailService != nil - c.HTML(200, USER_NEW) -} - -func NewUserPost(c *context.Context, f form.AdminCrateUser) { - c.Data["Title"] = c.Tr("admin.users.new_account") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminUsers"] = true - - sources, err := models.LoginSources() - if err != nil { - c.Handle(500, "LoginSources", err) - return - } - c.Data["Sources"] = sources - - c.Data["CanSendEmail"] = setting.MailService != nil - - if c.HasError() { - c.HTML(200, USER_NEW) - return - } - - u := &models.User{ - Name: f.UserName, - Email: f.Email, - Passwd: f.Password, - IsActive: true, - LoginType: models.LOGIN_PLAIN, - } - - if len(f.LoginType) > 0 { - fields := strings.Split(f.LoginType, "-") - if len(fields) == 2 { - u.LoginType = models.LoginType(com.StrTo(fields[0]).MustInt()) - u.LoginSource = com.StrTo(fields[1]).MustInt64() - u.LoginName = f.LoginName - } - } - - if err := models.CreateUser(u); err != nil { - switch { - case models.IsErrUserAlreadyExist(err): - c.Data["Err_UserName"] = true - c.RenderWithErr(c.Tr("form.username_been_taken"), USER_NEW, &f) - case models.IsErrEmailAlreadyUsed(err): - c.Data["Err_Email"] = true - c.RenderWithErr(c.Tr("form.email_been_used"), USER_NEW, &f) - case models.IsErrNameReserved(err): - c.Data["Err_UserName"] = true - c.RenderWithErr(c.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), USER_NEW, &f) - case models.IsErrNamePatternNotAllowed(err): - c.Data["Err_UserName"] = true - c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), USER_NEW, &f) - default: - c.Handle(500, "CreateUser", err) - } - return - } - log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name) - - // Send email notification. - if f.SendNotify && setting.MailService != nil { - mailer.SendRegisterNotifyMail(c.Context, models.NewMailerUser(u)) - } - - c.Flash.Success(c.Tr("admin.users.new_success", u.Name)) - c.Redirect(setting.AppSubURL + "/admin/users/" + com.ToStr(u.ID)) -} - -func prepareUserInfo(c *context.Context) *models.User { - u, err := models.GetUserByID(c.ParamsInt64(":userid")) - if err != nil { - c.Handle(500, "GetUserByID", err) - return nil - } - c.Data["User"] = u - - if u.LoginSource > 0 { - c.Data["LoginSource"], err = models.GetLoginSourceByID(u.LoginSource) - if err != nil { - c.Handle(500, "GetLoginSourceByID", err) - return nil - } - } else { - c.Data["LoginSource"] = &models.LoginSource{} - } - - sources, err := models.LoginSources() - if err != nil { - c.Handle(500, "LoginSources", err) - return nil - } - c.Data["Sources"] = sources - - return u -} - -func EditUser(c *context.Context) { - c.Data["Title"] = c.Tr("admin.users.edit_account") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminUsers"] = true - c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration - - prepareUserInfo(c) - if c.Written() { - return - } - - c.HTML(200, USER_EDIT) -} - -func EditUserPost(c *context.Context, f form.AdminEditUser) { - c.Data["Title"] = c.Tr("admin.users.edit_account") - c.Data["PageIsAdmin"] = true - c.Data["PageIsAdminUsers"] = true - c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration - - u := prepareUserInfo(c) - if c.Written() { - return - } - - if c.HasError() { - c.HTML(200, USER_EDIT) - return - } - - fields := strings.Split(f.LoginType, "-") - if len(fields) == 2 { - loginType := models.LoginType(com.StrTo(fields[0]).MustInt()) - loginSource := com.StrTo(fields[1]).MustInt64() - - if u.LoginSource != loginSource { - u.LoginSource = loginSource - u.LoginType = loginType - } - } - - if len(f.Password) > 0 { - u.Passwd = f.Password - var err error - if u.Salt, err = models.GetUserSalt(); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - u.EncodePasswd() - } - - u.LoginName = f.LoginName - u.FullName = f.FullName - u.Email = f.Email - u.Website = f.Website - u.Location = f.Location - u.MaxRepoCreation = f.MaxRepoCreation - u.IsActive = f.Active - u.IsAdmin = f.Admin - u.AllowGitHook = f.AllowGitHook - u.AllowImportLocal = f.AllowImportLocal - u.ProhibitLogin = f.ProhibitLogin - - if err := models.UpdateUser(u); err != nil { - if models.IsErrEmailAlreadyUsed(err) { - c.Data["Err_Email"] = true - c.RenderWithErr(c.Tr("form.email_been_used"), USER_EDIT, &f) - } else { - c.Handle(500, "UpdateUser", err) - } - return - } - log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name) - - c.Flash.Success(c.Tr("admin.users.update_profile_success")) - c.Redirect(setting.AppSubURL + "/admin/users/" + c.Params(":userid")) -} - -func DeleteUser(c *context.Context) { - u, err := models.GetUserByID(c.ParamsInt64(":userid")) - if err != nil { - c.Handle(500, "GetUserByID", err) - return - } - - if err = models.DeleteUser(u); err != nil { - switch { - case models.IsErrUserOwnRepos(err): - c.Flash.Error(c.Tr("admin.users.still_own_repo")) - c.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"), - }) - case models.IsErrUserHasOrgs(err): - c.Flash.Error(c.Tr("admin.users.still_has_org")) - c.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"), - }) - default: - c.Handle(500, "DeleteUser", err) - } - return - } - log.Trace("Account deleted by admin (%s): %s", c.User.Name, u.Name) - - c.Flash.Success(c.Tr("admin.users.deletion_success")) - c.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users", - }) -} diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go deleted file mode 100644 index 996a83bf..00000000 --- a/routers/api/v1/admin/org.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/api/v1/convert" - "github.com/gogits/gogs/routers/api/v1/user" -) - -// https://github.com/gogits/go-gogs-client/wiki/Administration-Organizations#create-a-new-organization -func CreateOrg(c *context.APIContext, form api.CreateOrgOption) { - u := user.GetUserByParams(c) - if c.Written() { - return - } - - org := &models.User{ - Name: form.UserName, - FullName: form.FullName, - Description: form.Description, - Website: form.Website, - Location: form.Location, - IsActive: true, - Type: models.USER_TYPE_ORGANIZATION, - } - if err := models.CreateOrganization(org, u); err != nil { - if models.IsErrUserAlreadyExist(err) || - models.IsErrNameReserved(err) || - models.IsErrNamePatternNotAllowed(err) { - c.Error(422, "", err) - } else { - c.Error(500, "CreateOrganization", err) - } - return - } - - c.JSON(201, convert.ToOrganization(org)) -} diff --git a/routers/api/v1/admin/org_repo.go b/routers/api/v1/admin/org_repo.go deleted file mode 100644 index 7abad1a8..00000000 --- a/routers/api/v1/admin/org_repo.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" -) - -func GetRepositoryByParams(c *context.APIContext) *models.Repository { - repo, err := models.GetRepositoryByName(c.Org.Team.OrgID, c.Params(":reponame")) - if err != nil { - if errors.IsRepoNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetRepositoryByName", err) - } - return nil - } - return repo -} - -func AddTeamRepository(c *context.APIContext) { - repo := GetRepositoryByParams(c) - if c.Written() { - return - } - if err := c.Org.Team.AddRepository(repo); err != nil { - c.Error(500, "AddRepository", err) - return - } - - c.Status(204) -} - -func RemoveTeamRepository(c *context.APIContext) { - repo := GetRepositoryByParams(c) - if c.Written() { - return - } - if err := c.Org.Team.RemoveRepository(repo.ID); err != nil { - c.Error(500, "RemoveRepository", err) - return - } - - c.Status(204) -} diff --git a/routers/api/v1/admin/org_team.go b/routers/api/v1/admin/org_team.go deleted file mode 100644 index 7ec6c08a..00000000 --- a/routers/api/v1/admin/org_team.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/api/v1/convert" - "github.com/gogits/gogs/routers/api/v1/user" -) - -func CreateTeam(c *context.APIContext, form api.CreateTeamOption) { - team := &models.Team{ - OrgID: c.Org.Organization.ID, - Name: form.Name, - Description: form.Description, - Authorize: models.ParseAccessMode(form.Permission), - } - if err := models.NewTeam(team); err != nil { - if models.IsErrTeamAlreadyExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "NewTeam", err) - } - return - } - - c.JSON(201, convert.ToTeam(team)) -} - -func AddTeamMember(c *context.APIContext) { - u := user.GetUserByParams(c) - if c.Written() { - return - } - if err := c.Org.Team.AddMember(u.ID); err != nil { - c.Error(500, "AddMember", err) - return - } - - c.Status(204) -} - -func RemoveTeamMember(c *context.APIContext) { - u := user.GetUserByParams(c) - if c.Written() { - return - } - - if err := c.Org.Team.RemoveMember(u.ID); err != nil { - c.Error(500, "RemoveMember", err) - return - } - - c.Status(204) -} diff --git a/routers/api/v1/admin/repo.go b/routers/api/v1/admin/repo.go deleted file mode 100644 index 6c8e8e8d..00000000 --- a/routers/api/v1/admin/repo.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/api/v1/repo" - "github.com/gogits/gogs/routers/api/v1/user" -) - -// https://github.com/gogits/go-gogs-client/wiki/Administration-Repositories#create-a-new-repository -func CreateRepo(c *context.APIContext, form api.CreateRepoOption) { - owner := user.GetUserByParams(c) - if c.Written() { - return - } - - repo.CreateUserRepo(c, owner, form) -} diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go deleted file mode 100644 index 8776217d..00000000 --- a/routers/api/v1/admin/user.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package admin - -import ( - log "gopkg.in/clog.v1" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/mailer" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/api/v1/user" -) - -func parseLoginSource(c *context.APIContext, u *models.User, sourceID int64, loginName string) { - if sourceID == 0 { - return - } - - source, err := models.GetLoginSourceByID(sourceID) - if err != nil { - if models.IsErrLoginSourceNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetLoginSourceByID", err) - } - return - } - - u.LoginType = source.Type - u.LoginSource = source.ID - u.LoginName = loginName -} - -// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#create-a-new-user -func CreateUser(c *context.APIContext, form api.CreateUserOption) { - u := &models.User{ - Name: form.Username, - FullName: form.FullName, - Email: form.Email, - Passwd: form.Password, - IsActive: true, - LoginType: models.LOGIN_PLAIN, - } - - parseLoginSource(c, u, form.SourceID, form.LoginName) - if c.Written() { - return - } - - if err := models.CreateUser(u); err != nil { - if models.IsErrUserAlreadyExist(err) || - models.IsErrEmailAlreadyUsed(err) || - models.IsErrNameReserved(err) || - models.IsErrNamePatternNotAllowed(err) { - c.Error(422, "", err) - } else { - c.Error(500, "CreateUser", err) - } - return - } - log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name) - - // Send email notification. - if form.SendNotify && setting.MailService != nil { - mailer.SendRegisterNotifyMail(c.Context.Context, models.NewMailerUser(u)) - } - - c.JSON(201, u.APIFormat()) -} - -// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#edit-an-existing-user -func EditUser(c *context.APIContext, form api.EditUserOption) { - u := user.GetUserByParams(c) - if c.Written() { - return - } - - parseLoginSource(c, u, form.SourceID, form.LoginName) - if c.Written() { - return - } - - if len(form.Password) > 0 { - u.Passwd = form.Password - var err error - if u.Salt, err = models.GetUserSalt(); err != nil { - c.Error(500, "UpdateUser", err) - return - } - u.EncodePasswd() - } - - u.LoginName = form.LoginName - u.FullName = form.FullName - u.Email = form.Email - u.Website = form.Website - u.Location = form.Location - if form.Active != nil { - u.IsActive = *form.Active - } - if form.Admin != nil { - u.IsAdmin = *form.Admin - } - if form.AllowGitHook != nil { - u.AllowGitHook = *form.AllowGitHook - } - if form.AllowImportLocal != nil { - u.AllowImportLocal = *form.AllowImportLocal - } - if form.MaxRepoCreation != nil { - u.MaxRepoCreation = *form.MaxRepoCreation - } - - if err := models.UpdateUser(u); err != nil { - if models.IsErrEmailAlreadyUsed(err) { - c.Error(422, "", err) - } else { - c.Error(500, "UpdateUser", err) - } - return - } - log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name) - - c.JSON(200, u.APIFormat()) -} - -// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#delete-a-user -func DeleteUser(c *context.APIContext) { - u := user.GetUserByParams(c) - if c.Written() { - return - } - - if err := models.DeleteUser(u); err != nil { - if models.IsErrUserOwnRepos(err) || - models.IsErrUserHasOrgs(err) { - c.Error(422, "", err) - } else { - c.Error(500, "DeleteUser", err) - } - return - } - log.Trace("Account deleted by admin(%s): %s", c.User.Name, u.Name) - - c.Status(204) -} - -// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#create-a-public-key-for-user -func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) { - u := user.GetUserByParams(c) - if c.Written() { - return - } - user.CreateUserPublicKey(c, form, u.ID) -} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go deleted file mode 100644 index 35ca861a..00000000 --- a/routers/api/v1/api.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package v1 - -import ( - "strings" - - "github.com/go-macaron/binding" - "gopkg.in/macaron.v1" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/routers/api/v1/admin" - "github.com/gogits/gogs/routers/api/v1/misc" - "github.com/gogits/gogs/routers/api/v1/org" - "github.com/gogits/gogs/routers/api/v1/repo" - "github.com/gogits/gogs/routers/api/v1/user" -) - -func repoAssignment() macaron.Handler { - return func(c *context.APIContext) { - userName := c.Params(":username") - repoName := c.Params(":reponame") - - var ( - owner *models.User - err error - ) - - // Check if the user is the same as the repository owner. - if c.IsLogged && c.User.LowerName == strings.ToLower(userName) { - owner = c.User - } else { - owner, err = models.GetUserByName(userName) - if err != nil { - if errors.IsUserNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - } - c.Repo.Owner = owner - - // Get repository. - repo, err := models.GetRepositoryByName(owner.ID, repoName) - if err != nil { - if errors.IsRepoNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetRepositoryByName", err) - } - return - } else if err = repo.GetOwner(); err != nil { - c.Error(500, "GetOwner", err) - return - } - - if c.IsLogged && c.User.IsAdmin { - c.Repo.AccessMode = models.ACCESS_MODE_OWNER - } else { - mode, err := models.AccessLevel(c.User.ID, repo) - if err != nil { - c.Error(500, "AccessLevel", err) - return - } - c.Repo.AccessMode = mode - } - - if !c.Repo.HasAccess() { - c.Status(404) - return - } - - c.Repo.Repository = repo - } -} - -// Contexter middleware already checks token for user sign in process. -func reqToken() macaron.Handler { - return func(c *context.Context) { - if !c.IsLogged { - c.Error(401) - return - } - } -} - -func reqBasicAuth() macaron.Handler { - return func(c *context.Context) { - if !c.IsBasicAuth { - c.Error(401) - return - } - } -} - -func reqAdmin() macaron.Handler { - return func(c *context.Context) { - if !c.IsLogged || !c.User.IsAdmin { - c.Error(403) - return - } - } -} - -func reqRepoWriter() macaron.Handler { - return func(c *context.Context) { - if !c.Repo.IsWriter() { - c.Error(403) - return - } - } -} - -func orgAssignment(args ...bool) macaron.Handler { - var ( - assignOrg bool - assignTeam bool - ) - if len(args) > 0 { - assignOrg = args[0] - } - if len(args) > 1 { - assignTeam = args[1] - } - return func(c *context.APIContext) { - c.Org = new(context.APIOrganization) - - var err error - if assignOrg { - c.Org.Organization, err = models.GetUserByName(c.Params(":orgname")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - } - - if assignTeam { - c.Org.Team, err = models.GetTeamByID(c.ParamsInt64(":teamid")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetTeamById", err) - } - return - } - } - } -} - -func mustEnableIssues(c *context.APIContext) { - if !c.Repo.Repository.EnableIssues || c.Repo.Repository.EnableExternalTracker { - c.Status(404) - return - } -} - -// RegisterRoutes registers all v1 APIs routes to web application. -// FIXME: custom form error response -func RegisterRoutes(m *macaron.Macaron) { - bind := binding.Bind - - m.Group("/v1", func() { - // Handle preflight OPTIONS request - m.Options("/*", func() {}) - - // Miscellaneous - m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) - m.Post("/markdown/raw", misc.MarkdownRaw) - - // Users - m.Group("/users", func() { - m.Get("/search", user.Search) - - m.Group("/:username", func() { - m.Get("", user.GetInfo) - - m.Group("/tokens", func() { - m.Combo("").Get(user.ListAccessTokens). - Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) - }, reqBasicAuth()) - }) - }) - - m.Group("/users", func() { - m.Group("/:username", func() { - m.Get("/keys", user.ListPublicKeys) - - m.Get("/followers", user.ListFollowers) - m.Group("/following", func() { - m.Get("", user.ListFollowing) - m.Get("/:target", user.CheckFollowing) - }) - }) - }, reqToken()) - - m.Group("/user", func() { - m.Get("", user.GetAuthenticatedUser) - m.Combo("/emails").Get(user.ListEmails). - Post(bind(api.CreateEmailOption{}), user.AddEmail). - Delete(bind(api.CreateEmailOption{}), user.DeleteEmail) - - m.Get("/followers", user.ListMyFollowers) - m.Group("/following", func() { - m.Get("", user.ListMyFollowing) - m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow) - }) - - m.Group("/keys", func() { - m.Combo("").Get(user.ListMyPublicKeys). - Post(bind(api.CreateKeyOption{}), user.CreatePublicKey) - m.Combo("/:id").Get(user.GetPublicKey). - Delete(user.DeletePublicKey) - }) - - m.Combo("/issues").Get(repo.ListUserIssues) - }, reqToken()) - - // Repositories - m.Get("/users/:username/repos", reqToken(), repo.ListUserRepositories) - m.Get("/orgs/:org/repos", reqToken(), repo.ListOrgRepositories) - m.Combo("/user/repos", reqToken()).Get(repo.ListMyRepos). - Post(bind(api.CreateRepoOption{}), repo.Create) - m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo) - - m.Group("/repos", func() { - m.Get("/search", repo.Search) - }) - - m.Group("/repos", func() { - m.Post("/migrate", bind(form.MigrateRepo{}), repo.Migrate) - m.Combo("/:username/:reponame", repoAssignment()).Get(repo.Get). - Delete(repo.Delete) - - m.Group("/:username/:reponame", func() { - m.Group("/hooks", func() { - m.Combo("").Get(repo.ListHooks). - Post(bind(api.CreateHookOption{}), repo.CreateHook) - m.Combo("/:id").Patch(bind(api.EditHookOption{}), repo.EditHook). - Delete(repo.DeleteHook) - }) - m.Group("/collaborators", func() { - m.Get("", repo.ListCollaborators) - m.Combo("/:collaborator").Get(repo.IsCollaborator).Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator). - Delete(repo.DeleteCollaborator) - }) - m.Get("/raw/*", context.RepoRef(), repo.GetRawFile) - m.Get("/archive/*", repo.GetArchive) - m.Get("/forks", repo.ListForks) - m.Group("/branches", func() { - m.Get("", repo.ListBranches) - m.Get("/*", repo.GetBranch) - }) - m.Group("/keys", func() { - m.Combo("").Get(repo.ListDeployKeys). - Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) - m.Combo("/:id").Get(repo.GetDeployKey). - Delete(repo.DeleteDeploykey) - }) - m.Group("/issues", func() { - m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue) - m.Group("/comments", func() { - m.Get("", repo.ListRepoIssueComments) - m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment) - }) - m.Group("/:index", func() { - m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue) - - m.Group("/comments", func() { - m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment) - m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment). - Delete(repo.DeleteIssueComment) - }) - - m.Group("/labels", func() { - m.Combo("").Get(repo.ListIssueLabels). - Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels). - Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels). - Delete(repo.ClearIssueLabels) - m.Delete("/:id", repo.DeleteIssueLabel) - }) - - }) - }, mustEnableIssues) - m.Group("/labels", func() { - m.Combo("").Get(repo.ListLabels). - Post(bind(api.CreateLabelOption{}), repo.CreateLabel) - m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel). - Delete(repo.DeleteLabel) - }) - m.Group("/milestones", func() { - m.Combo("").Get(repo.ListMilestones). - Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) - m.Combo("/:id").Get(repo.GetMilestone). - Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone). - Delete(reqRepoWriter(), repo.DeleteMilestone) - }) - m.Post("/mirror-sync", repo.MirrorSync) - m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) - }, repoAssignment()) - }, reqToken()) - - m.Get("/issues", reqToken(), repo.ListUserIssues) - - // Organizations - m.Get("/user/orgs", reqToken(), org.ListMyOrgs) - m.Get("/users/:username/orgs", org.ListUserOrgs) - m.Group("/orgs/:orgname", func() { - m.Combo("").Get(org.Get).Patch(bind(api.EditOrgOption{}), org.Edit) - m.Combo("/teams").Get(org.ListTeams) - }, orgAssignment(true)) - - m.Any("/*", func(c *context.Context) { - c.Error(404) - }) - - m.Group("/admin", func() { - m.Group("/users", func() { - m.Post("", bind(api.CreateUserOption{}), admin.CreateUser) - - m.Group("/:username", func() { - m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser). - Delete(admin.DeleteUser) - m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey) - m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) - m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) - }) - }) - - m.Group("/orgs/:orgname", func() { - m.Group("/teams", func() { - m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin.CreateTeam) - }) - }) - m.Group("/teams", func() { - m.Group("/:teamid", func() { - m.Combo("/members/:username").Put(admin.AddTeamMember).Delete(admin.RemoveTeamMember) - m.Combo("/repos/:reponame").Put(admin.AddTeamRepository).Delete(admin.RemoveTeamRepository) - }, orgAssignment(false, true)) - }) - }, reqAdmin()) - }, context.APIContexter()) -} diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go deleted file mode 100644 index fcadb51f..00000000 --- a/routers/api/v1/convert/convert.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package convert - -import ( - "fmt" - - "github.com/Unknwon/com" - - "github.com/gogits/git-module" - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" -) - -func ToEmail(email *models.EmailAddress) *api.Email { - return &api.Email{ - Email: email.Email, - Verified: email.IsActivated, - Primary: email.IsPrimary, - } -} - -func ToBranch(b *models.Branch, c *git.Commit) *api.Branch { - return &api.Branch{ - Name: b.Name, - Commit: ToCommit(c), - } -} - -func ToCommit(c *git.Commit) *api.PayloadCommit { - authorUsername := "" - author, err := models.GetUserByEmail(c.Author.Email) - if err == nil { - authorUsername = author.Name - } - committerUsername := "" - committer, err := models.GetUserByEmail(c.Committer.Email) - if err == nil { - committerUsername = committer.Name - } - return &api.PayloadCommit{ - ID: c.ID.String(), - Message: c.Message(), - URL: "Not implemented", - Author: &api.PayloadUser{ - Name: c.Author.Name, - Email: c.Author.Email, - UserName: authorUsername, - }, - Committer: &api.PayloadUser{ - Name: c.Committer.Name, - Email: c.Committer.Email, - UserName: committerUsername, - }, - Timestamp: c.Author.When, - } -} - -func ToPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey { - return &api.PublicKey{ - ID: key.ID, - Key: key.Content, - URL: apiLink + com.ToStr(key.ID), - Title: key.Name, - Created: key.Created, - } -} - -func ToHook(repoLink string, w *models.Webhook) *api.Hook { - config := map[string]string{ - "url": w.URL, - "content_type": w.ContentType.Name(), - } - if w.HookTaskType == models.SLACK { - s := w.GetSlackHook() - config["channel"] = s.Channel - config["username"] = s.Username - config["icon_url"] = s.IconURL - config["color"] = s.Color - } - - return &api.Hook{ - ID: w.ID, - Type: w.HookTaskType.Name(), - URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), - Active: w.IsActive, - Config: config, - Events: w.EventsArray(), - Updated: w.Updated, - Created: w.Created, - } -} - -func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey { - return &api.DeployKey{ - ID: key.ID, - Key: key.Content, - URL: apiLink + com.ToStr(key.ID), - Title: key.Name, - Created: key.Created, - ReadOnly: true, // All deploy keys are read-only. - } -} - -func ToOrganization(org *models.User) *api.Organization { - return &api.Organization{ - ID: org.ID, - AvatarUrl: org.AvatarLink(), - UserName: org.Name, - FullName: org.FullName, - Description: org.Description, - Website: org.Website, - Location: org.Location, - } -} - -func ToTeam(team *models.Team) *api.Team { - return &api.Team{ - ID: team.ID, - Name: team.Name, - Description: team.Description, - Permission: team.Authorize.String(), - } -} diff --git a/routers/api/v1/convert/utils.go b/routers/api/v1/convert/utils.go deleted file mode 100644 index d0beab3d..00000000 --- a/routers/api/v1/convert/utils.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package convert - -import ( - "github.com/gogits/gogs/pkg/setting" -) - -// ToCorrectPageSize makes sure page size is in allowed range. -func ToCorrectPageSize(size int) int { - if size <= 0 { - size = 10 - } else if size > setting.API.MaxResponseItems { - size = setting.API.MaxResponseItems - } - return size -} diff --git a/routers/api/v1/misc/markdown.go b/routers/api/v1/misc/markdown.go deleted file mode 100644 index 98bfd7d0..00000000 --- a/routers/api/v1/misc/markdown.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package misc - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/markup" -) - -// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-an-arbitrary-markdown-document -func Markdown(c *context.APIContext, form api.MarkdownOption) { - if c.HasApiError() { - c.Error(422, "", c.GetErrMsg()) - return - } - - if len(form.Text) == 0 { - c.Write([]byte("")) - return - } - - switch form.Mode { - case "gfm": - c.Write(markup.Markdown([]byte(form.Text), form.Context, nil)) - default: - c.Write(markup.RawMarkdown([]byte(form.Text), "")) - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-a-markdown-document-in-raw-mode -func MarkdownRaw(c *context.APIContext) { - body, err := c.Req.Body().Bytes() - if err != nil { - c.Error(422, "", err) - return - } - c.Write(markup.RawMarkdown(body, "")) -} diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go deleted file mode 100644 index 107fc19c..00000000 --- a/routers/api/v1/org/org.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package org - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/api/v1/convert" - "github.com/gogits/gogs/routers/api/v1/user" -) - -func listUserOrgs(c *context.APIContext, u *models.User, all bool) { - if err := u.GetOrganizations(all); err != nil { - c.Error(500, "GetOrganizations", err) - return - } - - apiOrgs := make([]*api.Organization, len(u.Orgs)) - for i := range u.Orgs { - apiOrgs[i] = convert.ToOrganization(u.Orgs[i]) - } - c.JSON(200, &apiOrgs) -} - -// https://github.com/gogits/go-gogs-client/wiki/Organizations#list-your-organizations -func ListMyOrgs(c *context.APIContext) { - listUserOrgs(c, c.User, true) -} - -// https://github.com/gogits/go-gogs-client/wiki/Organizations#list-user-organizations -func ListUserOrgs(c *context.APIContext) { - u := user.GetUserByParams(c) - if c.Written() { - return - } - listUserOrgs(c, u, false) -} - -// https://github.com/gogits/go-gogs-client/wiki/Organizations#get-an-organization -func Get(c *context.APIContext) { - c.JSON(200, convert.ToOrganization(c.Org.Organization)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Organizations#edit-an-organization -func Edit(c *context.APIContext, form api.EditOrgOption) { - org := c.Org.Organization - if !org.IsOwnedBy(c.User.ID) { - c.Status(403) - return - } - - org.FullName = form.FullName - org.Description = form.Description - org.Website = form.Website - org.Location = form.Location - if err := models.UpdateUser(org); err != nil { - c.Error(500, "UpdateUser", err) - return - } - - c.JSON(200, convert.ToOrganization(org)) -} diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go deleted file mode 100644 index a952ea53..00000000 --- a/routers/api/v1/org/team.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package org - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/api/v1/convert" -) - -func ListTeams(c *context.APIContext) { - org := c.Org.Organization - if err := org.GetTeams(); err != nil { - c.Error(500, "GetTeams", err) - return - } - - apiTeams := make([]*api.Team, len(org.Teams)) - for i := range org.Teams { - apiTeams[i] = convert.ToTeam(org.Teams[i]) - } - c.JSON(200, apiTeams) -} diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go deleted file mode 100644 index 3d5e646c..00000000 --- a/routers/api/v1/repo/branch.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/api/v1/convert" -) - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#get-branch -func GetBranch(c *context.APIContext) { - branch, err := c.Repo.Repository.GetBranch(c.Params("*")) - if err != nil { - if models.IsErrBranchNotExist(err) { - c.Error(404, "GetBranch", err) - } else { - c.Error(500, "GetBranch", err) - } - return - } - - commit, err := branch.GetCommit() - if err != nil { - c.Error(500, "GetCommit", err) - return - } - - c.JSON(200, convert.ToBranch(branch, commit)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-branches -func ListBranches(c *context.APIContext) { - branches, err := c.Repo.Repository.GetBranches() - if err != nil { - c.Error(500, "GetBranches", err) - return - } - - apiBranches := make([]*api.Branch, len(branches)) - for i := range branches { - commit, err := branches[i].GetCommit() - if err != nil { - c.Error(500, "GetCommit", err) - return - } - apiBranches[i] = convert.ToBranch(branches[i], commit) - } - - c.JSON(200, &apiBranches) -} diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go deleted file mode 100644 index d295ac0f..00000000 --- a/routers/api/v1/repo/collaborators.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" -) - -func ListCollaborators(c *context.APIContext) { - collaborators, err := c.Repo.Repository.GetCollaborators() - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetCollaborators", err) - } - return - } - - apiCollaborators := make([]*api.Collaborator, len(collaborators)) - for i := range collaborators { - apiCollaborators[i] = collaborators[i].APIFormat() - } - c.JSON(200, &apiCollaborators) -} - -func AddCollaborator(c *context.APIContext, form api.AddCollaboratorOption) { - collaborator, err := models.GetUserByName(c.Params(":collaborator")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - - if err := c.Repo.Repository.AddCollaborator(collaborator); err != nil { - c.Error(500, "AddCollaborator", err) - return - } - - if form.Permission != nil { - if err := c.Repo.Repository.ChangeCollaborationAccessMode(collaborator.ID, models.ParseAccessMode(*form.Permission)); err != nil { - c.Error(500, "ChangeCollaborationAccessMode", err) - return - } - } - - c.Status(204) -} - -func IsCollaborator(c *context.APIContext) { - collaborator, err := models.GetUserByName(c.Params(":collaborator")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - - if !c.Repo.Repository.IsCollaborator(collaborator.ID) { - c.Status(404) - } else { - c.Status(204) - } -} - -func DeleteCollaborator(c *context.APIContext) { - collaborator, err := models.GetUserByName(c.Params(":collaborator")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - - if err := c.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil { - c.Error(500, "DeleteCollaboration", err) - return - } - - c.Status(204) -} diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go deleted file mode 100644 index df6a4857..00000000 --- a/routers/api/v1/repo/file.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/repo" -) - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-raw-content -func GetRawFile(c *context.APIContext) { - if !c.Repo.HasAccess() { - c.Status(404) - return - } - - if c.Repo.Repository.IsBare { - c.Status(404) - return - } - - blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath) - if err != nil { - if git.IsErrNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetBlobByPath", err) - } - return - } - if err = repo.ServeBlob(c.Context, blob); err != nil { - c.Error(500, "ServeBlob", err) - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-archive -func GetArchive(c *context.APIContext) { - repoPath := models.RepoPath(c.Params(":username"), c.Params(":reponame")) - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - c.Error(500, "OpenRepository", err) - return - } - c.Repo.GitRepo = gitRepo - - repo.Download(c.Context) -} - -func GetEditorconfig(c *context.APIContext) { - ec, err := c.Repo.GetEditorconfig() - if err != nil { - if git.IsErrNotExist(err) { - c.Error(404, "GetEditorconfig", err) - } else { - c.Error(500, "GetEditorconfig", err) - } - return - } - - fileName := c.Params("filename") - def := ec.GetDefinitionForFilename(fileName) - if def == nil { - c.Error(404, "GetDefinitionForFilename", err) - return - } - c.JSON(200, def) -} diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go deleted file mode 100644 index 7c2b293e..00000000 --- a/routers/api/v1/repo/hook.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "encoding/json" - - "github.com/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/routers/api/v1/convert" -) - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-hooks -func ListHooks(c *context.APIContext) { - hooks, err := models.GetWebhooksByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Error(500, "GetWebhooksByRepoID", err) - return - } - - apiHooks := make([]*api.Hook, len(hooks)) - for i := range hooks { - apiHooks[i] = convert.ToHook(c.Repo.RepoLink, hooks[i]) - } - c.JSON(200, &apiHooks) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#create-a-hook -func CreateHook(c *context.APIContext, form api.CreateHookOption) { - if !models.IsValidHookTaskType(form.Type) { - c.Error(422, "", "Invalid hook type") - return - } - for _, name := range []string{"url", "content_type"} { - if _, ok := form.Config[name]; !ok { - c.Error(422, "", "Missing config option: "+name) - return - } - } - if !models.IsValidHookContentType(form.Config["content_type"]) { - c.Error(422, "", "Invalid content type") - return - } - - if len(form.Events) == 0 { - form.Events = []string{"push"} - } - w := &models.Webhook{ - RepoID: c.Repo.Repository.ID, - URL: form.Config["url"], - ContentType: models.ToHookContentType(form.Config["content_type"]), - Secret: form.Config["secret"], - HookEvent: &models.HookEvent{ - ChooseEvents: true, - HookEvents: models.HookEvents{ - Create: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)), - Delete: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_DELETE)), - Fork: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_FORK)), - Push: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)), - Issues: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUES)), - IssueComment: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUE_COMMENT)), - PullRequest: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PULL_REQUEST)), - Release: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_RELEASE)), - }, - }, - IsActive: form.Active, - HookTaskType: models.ToHookTaskType(form.Type), - } - if w.HookTaskType == models.SLACK { - channel, ok := form.Config["channel"] - if !ok { - c.Error(422, "", "Missing config option: channel") - return - } - meta, err := json.Marshal(&models.SlackMeta{ - Channel: channel, - Username: form.Config["username"], - IconURL: form.Config["icon_url"], - Color: form.Config["color"], - }) - if err != nil { - c.Error(500, "slack: JSON marshal failed", err) - return - } - w.Meta = string(meta) - } - - if err := w.UpdateEvent(); err != nil { - c.Error(500, "UpdateEvent", err) - return - } else if err := models.CreateWebhook(w); err != nil { - c.Error(500, "CreateWebhook", err) - return - } - - c.JSON(201, convert.ToHook(c.Repo.RepoLink, w)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook -func EditHook(c *context.APIContext, form api.EditHookOption) { - w, err := models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if errors.IsWebhookNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetWebhookOfRepoByID", err) - } - return - } - - if form.Config != nil { - if url, ok := form.Config["url"]; ok { - w.URL = url - } - if ct, ok := form.Config["content_type"]; ok { - if !models.IsValidHookContentType(ct) { - c.Error(422, "", "Invalid content type") - return - } - w.ContentType = models.ToHookContentType(ct) - } - - if w.HookTaskType == models.SLACK { - if channel, ok := form.Config["channel"]; ok { - meta, err := json.Marshal(&models.SlackMeta{ - Channel: channel, - Username: form.Config["username"], - IconURL: form.Config["icon_url"], - Color: form.Config["color"], - }) - if err != nil { - c.Error(500, "slack: JSON marshal failed", err) - return - } - w.Meta = string(meta) - } - } - } - - // Update events - if len(form.Events) == 0 { - form.Events = []string{"push"} - } - w.PushOnly = false - w.SendEverything = false - w.ChooseEvents = true - w.Create = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)) - w.Delete = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_DELETE)) - w.Fork = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_FORK)) - w.Push = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)) - w.Issues = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUES)) - w.IssueComment = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUE_COMMENT)) - w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PULL_REQUEST)) - w.Release = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_RELEASE)) - if err = w.UpdateEvent(); err != nil { - c.Error(500, "UpdateEvent", err) - return - } - - if form.Active != nil { - w.IsActive = *form.Active - } - - if err := models.UpdateWebhook(w); err != nil { - c.Error(500, "UpdateWebhook", err) - return - } - - c.JSON(200, convert.ToHook(c.Repo.RepoLink, w)) -} - -func DeleteHook(c *context.APIContext) { - if err := models.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil { - c.Error(500, "DeleteWebhookByRepoID", err) - return - } - - c.Status(204) -} diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go deleted file mode 100644 index d6ae7b4d..00000000 --- a/routers/api/v1/repo/issue.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "fmt" - "strings" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" -) - -func listIssues(c *context.APIContext, opts *models.IssuesOptions) { - issues, err := models.Issues(opts) - if err != nil { - c.Error(500, "Issues", err) - return - } - - count, err := models.IssuesCount(opts) - if err != nil { - c.Error(500, "IssuesCount", err) - return - } - - // FIXME: use IssueList to improve performance. - apiIssues := make([]*api.Issue, len(issues)) - for i := range issues { - if err = issues[i].LoadAttributes(); err != nil { - c.Error(500, "LoadAttributes", err) - return - } - apiIssues[i] = issues[i].APIFormat() - } - - c.SetLinkHeader(int(count), setting.UI.IssuePagingNum) - c.JSON(200, &apiIssues) -} - -func ListUserIssues(c *context.APIContext) { - opts := models.IssuesOptions{ - AssigneeID: c.User.ID, - Page: c.QueryInt("page"), - IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED, - } - - listIssues(c, &opts) -} - -func ListIssues(c *context.APIContext) { - opts := models.IssuesOptions{ - RepoID: c.Repo.Repository.ID, - Page: c.QueryInt("page"), - IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED, - } - - listIssues(c, &opts) -} - -func GetIssue(c *context.APIContext) { - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - if errors.IsIssueNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetIssueByIndex", err) - } - return - } - c.JSON(200, issue.APIFormat()) -} - -func CreateIssue(c *context.APIContext, form api.CreateIssueOption) { - issue := &models.Issue{ - RepoID: c.Repo.Repository.ID, - Title: form.Title, - PosterID: c.User.ID, - Poster: c.User, - Content: form.Body, - } - - if c.Repo.IsWriter() { - if len(form.Assignee) > 0 { - assignee, err := models.GetUserByName(form.Assignee) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", form.Assignee)) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - issue.AssigneeID = assignee.ID - } - issue.MilestoneID = form.Milestone - } else { - form.Labels = nil - } - - if err := models.NewIssue(c.Repo.Repository, issue, form.Labels, nil); err != nil { - c.Error(500, "NewIssue", err) - return - } - - if form.Closed { - if err := issue.ChangeStatus(c.User, c.Repo.Repository, true); err != nil { - c.Error(500, "ChangeStatus", err) - return - } - } - - // Refetch from database to assign some automatic values - var err error - issue, err = models.GetIssueByID(issue.ID) - if err != nil { - c.Error(500, "GetIssueByID", err) - return - } - c.JSON(201, issue.APIFormat()) -} - -func EditIssue(c *context.APIContext, form api.EditIssueOption) { - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - if errors.IsIssueNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetIssueByIndex", err) - } - return - } - - if !issue.IsPoster(c.User.ID) && !c.Repo.IsWriter() { - c.Status(403) - return - } - - if len(form.Title) > 0 { - issue.Title = form.Title - } - if form.Body != nil { - issue.Content = *form.Body - } - - if c.Repo.IsWriter() && form.Assignee != nil && - (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) { - if len(*form.Assignee) == 0 { - issue.AssigneeID = 0 - } else { - assignee, err := models.GetUserByName(*form.Assignee) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee)) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - issue.AssigneeID = assignee.ID - } - - if err = models.UpdateIssueUserByAssignee(issue); err != nil { - c.Error(500, "UpdateIssueUserByAssignee", err) - return - } - } - if c.Repo.IsWriter() && form.Milestone != nil && - issue.MilestoneID != *form.Milestone { - oldMilestoneID := issue.MilestoneID - issue.MilestoneID = *form.Milestone - if err = models.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil { - c.Error(500, "ChangeMilestoneAssign", err) - return - } - } - - if err = models.UpdateIssue(issue); err != nil { - c.Error(500, "UpdateIssue", err) - return - } - if form.State != nil { - if err = issue.ChangeStatus(c.User, c.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil { - c.Error(500, "ChangeStatus", err) - return - } - } - - // Refetch from database to assign some automatic values - issue, err = models.GetIssueByID(issue.ID) - if err != nil { - c.Error(500, "GetIssueByID", err) - return - } - c.JSON(201, issue.APIFormat()) -} diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go deleted file mode 100644 index 4a057d76..00000000 --- a/routers/api/v1/repo/issue_comment.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -package repo - -import ( - "time" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" -) - -func ListIssueComments(c *context.APIContext) { - var since time.Time - if len(c.Query("since")) > 0 { - since, _ = time.Parse(time.RFC3339, c.Query("since")) - } - - // comments,err:=models.GetCommentsByIssueIDSince(, since) - issue, err := models.GetRawIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - c.Error(500, "GetRawIssueByIndex", err) - return - } - - comments, err := models.GetCommentsByIssueIDSince(issue.ID, since.Unix()) - if err != nil { - c.Error(500, "GetCommentsByIssueIDSince", err) - return - } - - apiComments := make([]*api.Comment, len(comments)) - for i := range comments { - apiComments[i] = comments[i].APIFormat() - } - c.JSON(200, &apiComments) -} - -func ListRepoIssueComments(c *context.APIContext) { - var since time.Time - if len(c.Query("since")) > 0 { - since, _ = time.Parse(time.RFC3339, c.Query("since")) - } - - comments, err := models.GetCommentsByRepoIDSince(c.Repo.Repository.ID, since.Unix()) - if err != nil { - c.Error(500, "GetCommentsByRepoIDSince", err) - return - } - - apiComments := make([]*api.Comment, len(comments)) - for i := range comments { - apiComments[i] = comments[i].APIFormat() - } - c.JSON(200, &apiComments) -} - -func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption) { - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - c.Error(500, "GetIssueByIndex", err) - return - } - - comment, err := models.CreateIssueComment(c.User, c.Repo.Repository, issue, form.Body, nil) - if err != nil { - c.Error(500, "CreateIssueComment", err) - return - } - - c.JSON(201, comment.APIFormat()) -} - -func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) { - comment, err := models.GetCommentByID(c.ParamsInt64(":id")) - if err != nil { - if models.IsErrCommentNotExist(err) { - c.Error(404, "GetCommentByID", err) - } else { - c.Error(500, "GetCommentByID", err) - } - return - } - - if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() { - c.Status(403) - return - } else if comment.Type != models.COMMENT_TYPE_COMMENT { - c.Status(204) - return - } - - oldContent := comment.Content - comment.Content = form.Body - if err := models.UpdateComment(c.User, comment, oldContent); err != nil { - c.Error(500, "UpdateComment", err) - return - } - c.JSON(200, comment.APIFormat()) -} - -func DeleteIssueComment(c *context.APIContext) { - comment, err := models.GetCommentByID(c.ParamsInt64(":id")) - if err != nil { - if models.IsErrCommentNotExist(err) { - c.Error(404, "GetCommentByID", err) - } else { - c.Error(500, "GetCommentByID", err) - } - return - } - - if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() { - c.Status(403) - return - } else if comment.Type != models.COMMENT_TYPE_COMMENT { - c.Status(204) - return - } - - if err = models.DeleteCommentByID(c.User, comment.ID); err != nil { - c.Error(500, "DeleteCommentByID", err) - return - } - c.Status(204) -} diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go deleted file mode 100644 index f3f2d730..00000000 --- a/routers/api/v1/repo/issue_label.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" -) - -func ListIssueLabels(c *context.APIContext) { - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - if errors.IsIssueNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetIssueByIndex", err) - } - return - } - - apiLabels := make([]*api.Label, len(issue.Labels)) - for i := range issue.Labels { - apiLabels[i] = issue.Labels[i].APIFormat() - } - c.JSON(200, &apiLabels) -} - -func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) { - if !c.Repo.IsWriter() { - c.Status(403) - return - } - - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - if errors.IsIssueNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetIssueByIndex", err) - } - return - } - - labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels) - if err != nil { - c.Error(500, "GetLabelsInRepoByIDs", err) - return - } - - if err = issue.AddLabels(c.User, labels); err != nil { - c.Error(500, "AddLabels", err) - return - } - - labels, err = models.GetLabelsByIssueID(issue.ID) - if err != nil { - c.Error(500, "GetLabelsByIssueID", err) - return - } - - apiLabels := make([]*api.Label, len(labels)) - for i := range labels { - apiLabels[i] = issue.Labels[i].APIFormat() - } - c.JSON(200, &apiLabels) -} - -func DeleteIssueLabel(c *context.APIContext) { - if !c.Repo.IsWriter() { - c.Status(403) - return - } - - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - if errors.IsIssueNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetIssueByIndex", err) - } - return - } - - label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if models.IsErrLabelNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetLabelInRepoByID", err) - } - return - } - - if err := models.DeleteIssueLabel(issue, label); err != nil { - c.Error(500, "DeleteIssueLabel", err) - return - } - - c.Status(204) -} - -func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) { - if !c.Repo.IsWriter() { - c.Status(403) - return - } - - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - if errors.IsIssueNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetIssueByIndex", err) - } - return - } - - labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels) - if err != nil { - c.Error(500, "GetLabelsInRepoByIDs", err) - return - } - - if err := issue.ReplaceLabels(labels); err != nil { - c.Error(500, "ReplaceLabels", err) - return - } - - labels, err = models.GetLabelsByIssueID(issue.ID) - if err != nil { - c.Error(500, "GetLabelsByIssueID", err) - return - } - - apiLabels := make([]*api.Label, len(labels)) - for i := range labels { - apiLabels[i] = issue.Labels[i].APIFormat() - } - c.JSON(200, &apiLabels) -} - -func ClearIssueLabels(c *context.APIContext) { - if !c.Repo.IsWriter() { - c.Status(403) - return - } - - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - if errors.IsIssueNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetIssueByIndex", err) - } - return - } - - if err := issue.ClearLabels(c.User); err != nil { - c.Error(500, "ClearLabels", err) - return - } - - c.Status(204) -} diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go deleted file mode 100644 index 901df4c7..00000000 --- a/routers/api/v1/repo/key.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "fmt" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/api/v1/convert" -) - -func composeDeployKeysAPILink(repoPath string) string { - return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/" -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys -func ListDeployKeys(c *context.APIContext) { - keys, err := models.ListDeployKeys(c.Repo.Repository.ID) - if err != nil { - c.Error(500, "ListDeployKeys", err) - return - } - - apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name) - apiKeys := make([]*api.DeployKey, len(keys)) - for i := range keys { - if err = keys[i].GetContent(); err != nil { - c.Error(500, "GetContent", err) - return - } - apiKeys[i] = convert.ToDeployKey(apiLink, keys[i]) - } - - c.JSON(200, &apiKeys) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key -func GetDeployKey(c *context.APIContext) { - key, err := models.GetDeployKeyByID(c.ParamsInt64(":id")) - if err != nil { - if models.IsErrDeployKeyNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetDeployKeyByID", err) - } - return - } - - if err = key.GetContent(); err != nil { - c.Error(500, "GetContent", err) - return - } - - apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name) - c.JSON(200, convert.ToDeployKey(apiLink, key)) -} - -func HandleCheckKeyStringError(c *context.APIContext, err error) { - if models.IsErrKeyUnableVerify(err) { - c.Error(422, "", "Unable to verify key content") - } else { - c.Error(422, "", fmt.Errorf("Invalid key content: %v", err)) - } -} - -func HandleAddKeyError(c *context.APIContext, err error) { - switch { - case models.IsErrKeyAlreadyExist(err): - c.Error(422, "", "Key content has been used as non-deploy key") - case models.IsErrKeyNameAlreadyUsed(err): - c.Error(422, "", "Key title has been used") - default: - c.Error(500, "AddKey", err) - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key -func CreateDeployKey(c *context.APIContext, form api.CreateKeyOption) { - content, err := models.CheckPublicKeyString(form.Key) - if err != nil { - HandleCheckKeyStringError(c, err) - return - } - - key, err := models.AddDeployKey(c.Repo.Repository.ID, form.Title, content) - if err != nil { - HandleAddKeyError(c, err) - return - } - - key.Content = content - apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name) - c.JSON(201, convert.ToDeployKey(apiLink, key)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key -func DeleteDeploykey(c *context.APIContext) { - if err := models.DeleteDeployKey(c.User, c.ParamsInt64(":id")); err != nil { - if models.IsErrKeyAccessDenied(err) { - c.Error(403, "", "You do not have access to this key") - } else { - c.Error(500, "DeleteDeployKey", err) - } - return - } - - c.Status(204) -} diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go deleted file mode 100644 index 1161d633..00000000 --- a/routers/api/v1/repo/label.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "github.com/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" -) - -func ListLabels(c *context.APIContext) { - labels, err := models.GetLabelsByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Error(500, "GetLabelsByRepoID", err) - return - } - - apiLabels := make([]*api.Label, len(labels)) - for i := range labels { - apiLabels[i] = labels[i].APIFormat() - } - c.JSON(200, &apiLabels) -} - -func GetLabel(c *context.APIContext) { - var label *models.Label - var err error - idStr := c.Params(":id") - if id := com.StrTo(idStr).MustInt64(); id > 0 { - label, err = models.GetLabelOfRepoByID(c.Repo.Repository.ID, id) - } else { - label, err = models.GetLabelOfRepoByName(c.Repo.Repository.ID, idStr) - } - if err != nil { - if models.IsErrLabelNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetLabelByRepoID", err) - } - return - } - - c.JSON(200, label.APIFormat()) -} - -func CreateLabel(c *context.APIContext, form api.CreateLabelOption) { - if !c.Repo.IsWriter() { - c.Status(403) - return - } - - label := &models.Label{ - Name: form.Name, - Color: form.Color, - RepoID: c.Repo.Repository.ID, - } - if err := models.NewLabels(label); err != nil { - c.Error(500, "NewLabel", err) - return - } - c.JSON(201, label.APIFormat()) -} - -func EditLabel(c *context.APIContext, form api.EditLabelOption) { - if !c.Repo.IsWriter() { - c.Status(403) - return - } - - label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if models.IsErrLabelNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetLabelByRepoID", err) - } - return - } - - if form.Name != nil { - label.Name = *form.Name - } - if form.Color != nil { - label.Color = *form.Color - } - if err := models.UpdateLabel(label); err != nil { - c.Handle(500, "UpdateLabel", err) - return - } - c.JSON(200, label.APIFormat()) -} - -func DeleteLabel(c *context.APIContext) { - if !c.Repo.IsWriter() { - c.Status(403) - return - } - - if err := models.DeleteLabel(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil { - c.Error(500, "DeleteLabel", err) - return - } - - c.Status(204) -} diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go deleted file mode 100644 index baf8eb2f..00000000 --- a/routers/api/v1/repo/milestone.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "time" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" -) - -func ListMilestones(c *context.APIContext) { - milestones, err := models.GetMilestonesByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Error(500, "GetMilestonesByRepoID", err) - return - } - - apiMilestones := make([]*api.Milestone, len(milestones)) - for i := range milestones { - apiMilestones[i] = milestones[i].APIFormat() - } - c.JSON(200, &apiMilestones) -} - -func GetMilestone(c *context.APIContext) { - milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if models.IsErrMilestoneNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetMilestoneByRepoID", err) - } - return - } - c.JSON(200, milestone.APIFormat()) -} - -func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) { - if form.Deadline == nil { - defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local) - form.Deadline = &defaultDeadline - } - - milestone := &models.Milestone{ - RepoID: c.Repo.Repository.ID, - Name: form.Title, - Content: form.Description, - Deadline: *form.Deadline, - } - - if err := models.NewMilestone(milestone); err != nil { - c.Error(500, "NewMilestone", err) - return - } - c.JSON(201, milestone.APIFormat()) -} - -func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) { - milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if models.IsErrMilestoneNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetMilestoneByRepoID", err) - } - return - } - - if len(form.Title) > 0 { - milestone.Name = form.Title - } - if form.Description != nil { - milestone.Content = *form.Description - } - if form.Deadline != nil && !form.Deadline.IsZero() { - milestone.Deadline = *form.Deadline - } - - if form.State != nil { - if err = milestone.ChangeStatus(api.STATE_CLOSED == api.StateType(*form.State)); err != nil { - c.Error(500, "ChangeStatus", err) - return - } - } else if err = models.UpdateMilestone(milestone); err != nil { - c.Handle(500, "UpdateMilestone", err) - return - } - - c.JSON(200, milestone.APIFormat()) -} - -func DeleteMilestone(c *context.APIContext) { - if err := models.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil { - c.Error(500, "DeleteMilestoneByRepoID", err) - return - } - c.Status(204) -} diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go deleted file mode 100644 index 727e1678..00000000 --- a/routers/api/v1/repo/repo.go +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "path" - - log "gopkg.in/clog.v1" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/api/v1/convert" -) - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#search-repositories -func Search(c *context.APIContext) { - opts := &models.SearchRepoOptions{ - Keyword: path.Base(c.Query("q")), - OwnerID: c.QueryInt64("uid"), - PageSize: convert.ToCorrectPageSize(c.QueryInt("limit")), - } - - // Check visibility. - if c.IsLogged && opts.OwnerID > 0 { - if c.User.ID == opts.OwnerID { - opts.Private = true - } else { - u, err := models.GetUserByID(opts.OwnerID) - if err != nil { - c.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - if u.IsOrganization() && u.IsOwnedBy(c.User.ID) { - opts.Private = true - } - // FIXME: how about collaborators? - } - } - - repos, count, err := models.SearchRepositoryByName(opts) - if err != nil { - c.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - - if err = models.RepositoryList(repos).LoadAttributes(); err != nil { - c.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - - results := make([]*api.Repository, len(repos)) - for i := range repos { - results[i] = repos[i].APIFormat(nil) - } - - c.SetLinkHeader(int(count), setting.API.MaxResponseItems) - c.JSON(200, map[string]interface{}{ - "ok": true, - "data": results, - }) -} - -func listUserRepositories(c *context.APIContext, username string) { - user, err := models.GetUserByName(username) - if err != nil { - c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) - return - } - - // Only list public repositories if user requests someone else's repository list, - // or an organization isn't a member of. - var ownRepos []*models.Repository - if user.IsOrganization() { - ownRepos, _, err = user.GetUserRepositories(c.User.ID, 1, user.NumRepos) - } else { - ownRepos, err = models.GetUserRepositories(&models.UserRepoOptions{ - UserID: user.ID, - Private: c.User.ID == user.ID, - Page: 1, - PageSize: user.NumRepos, - }) - } - if err != nil { - c.Error(500, "GetUserRepositories", err) - return - } - - if c.User.ID != user.ID { - repos := make([]*api.Repository, len(ownRepos)) - for i := range ownRepos { - repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true}) - } - c.JSON(200, &repos) - return - } - - accessibleRepos, err := user.GetRepositoryAccesses() - if err != nil { - c.Error(500, "GetRepositoryAccesses", err) - return - } - - numOwnRepos := len(ownRepos) - repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos)) - for i := range ownRepos { - repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true}) - } - - i := numOwnRepos - for repo, access := range accessibleRepos { - repos[i] = repo.APIFormat(&api.Permission{ - Admin: access >= models.ACCESS_MODE_ADMIN, - Push: access >= models.ACCESS_MODE_WRITE, - Pull: true, - }) - i++ - } - - c.JSON(200, &repos) -} - -func ListMyRepos(c *context.APIContext) { - listUserRepositories(c, c.User.Name) -} - -func ListUserRepositories(c *context.APIContext) { - listUserRepositories(c, c.Params(":username")) -} - -func ListOrgRepositories(c *context.APIContext) { - listUserRepositories(c, c.Params(":org")) -} - -func CreateUserRepo(c *context.APIContext, owner *models.User, opt api.CreateRepoOption) { - repo, err := models.CreateRepository(c.User, owner, models.CreateRepoOptions{ - Name: opt.Name, - Description: opt.Description, - Gitignores: opt.Gitignores, - License: opt.License, - Readme: opt.Readme, - IsPrivate: opt.Private, - AutoInit: opt.AutoInit, - }) - if err != nil { - if models.IsErrRepoAlreadyExist(err) || - models.IsErrNameReserved(err) || - models.IsErrNamePatternNotAllowed(err) { - c.Error(422, "", err) - } else { - if repo != nil { - if err = models.DeleteRepository(c.User.ID, repo.ID); err != nil { - log.Error(2, "DeleteRepository: %v", err) - } - } - c.Error(500, "CreateRepository", err) - } - return - } - - c.JSON(201, repo.APIFormat(&api.Permission{true, true, true})) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#create -func Create(c *context.APIContext, opt api.CreateRepoOption) { - // Shouldn't reach this condition, but just in case. - if c.User.IsOrganization() { - c.Error(422, "", "not allowed creating repository for organization") - return - } - CreateUserRepo(c, c.User, opt) -} - -func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) { - org, err := models.GetOrgByName(c.Params(":org")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetOrgByName", err) - } - return - } - - if !org.IsOwnedBy(c.User.ID) { - c.Error(403, "", "Given user is not owner of organization.") - return - } - CreateUserRepo(c, org, opt) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#migrate -func Migrate(c *context.APIContext, f form.MigrateRepo) { - ctxUser := c.User - // Not equal means context user is an organization, - // or is another user/organization if current user is admin. - if f.Uid != ctxUser.ID { - org, err := models.GetUserByID(f.Uid) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetUserByID", err) - } - return - } else if !org.IsOrganization() && !c.User.IsAdmin { - c.Error(403, "", "Given user is not an organization") - return - } - ctxUser = org - } - - if c.HasError() { - c.Error(422, "", c.GetErrMsg()) - return - } - - if ctxUser.IsOrganization() && !c.User.IsAdmin { - // Check ownership of organization. - if !ctxUser.IsOwnedBy(c.User.ID) { - c.Error(403, "", "Given user is not owner of organization") - return - } - } - - remoteAddr, err := f.ParseRemoteAddr(c.User) - if err != nil { - if models.IsErrInvalidCloneAddr(err) { - addrErr := err.(models.ErrInvalidCloneAddr) - switch { - case addrErr.IsURLError: - c.Error(422, "", err) - case addrErr.IsPermissionDenied: - c.Error(422, "", "You are not allowed to import local repositories") - case addrErr.IsInvalidPath: - c.Error(422, "", "Invalid local path, it does not exist or not a directory") - default: - c.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error()) - } - } else { - c.Error(500, "ParseRemoteAddr", err) - } - return - } - - repo, err := models.MigrateRepository(c.User, ctxUser, models.MigrateRepoOptions{ - Name: f.RepoName, - Description: f.Description, - IsPrivate: f.Private || setting.Repository.ForcePrivate, - IsMirror: f.Mirror, - RemoteAddr: remoteAddr, - }) - if err != nil { - if repo != nil { - if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { - log.Error(2, "DeleteRepository: %v", errDelete) - } - } - - if errors.IsReachLimitOfRepo(err) { - c.Error(422, "", err) - } else { - c.Error(500, "MigrateRepository", models.HandleMirrorCredentials(err.Error(), true)) - } - return - } - - log.Trace("Repository migrated: %s/%s", ctxUser.Name, f.RepoName) - c.JSON(201, repo.APIFormat(&api.Permission{true, true, true})) -} - -func parseOwnerAndRepo(c *context.APIContext) (*models.User, *models.Repository) { - owner, err := models.GetUserByName(c.Params(":username")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Error(422, "", err) - } else { - c.Error(500, "GetUserByName", err) - } - return nil, nil - } - - repo, err := models.GetRepositoryByName(owner.ID, c.Params(":reponame")) - if err != nil { - if errors.IsRepoNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetRepositoryByName", err) - } - return nil, nil - } - - return owner, repo -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#get -func Get(c *context.APIContext) { - _, repo := parseOwnerAndRepo(c) - if c.Written() { - return - } - - c.JSON(200, repo.APIFormat(&api.Permission{ - Admin: c.Repo.IsAdmin(), - Push: c.Repo.IsWriter(), - Pull: true, - })) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#delete -func Delete(c *context.APIContext) { - owner, repo := parseOwnerAndRepo(c) - if c.Written() { - return - } - - if owner.IsOrganization() && !owner.IsOwnedBy(c.User.ID) { - c.Error(403, "", "Given user is not owner of organization.") - return - } - - if err := models.DeleteRepository(owner.ID, repo.ID); err != nil { - c.Error(500, "DeleteRepository", err) - return - } - - log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name) - c.Status(204) -} - -func ListForks(c *context.APIContext) { - forks, err := c.Repo.Repository.GetForks() - if err != nil { - c.Error(500, "GetForks", err) - return - } - - apiForks := make([]*api.Repository, len(forks)) - for i := range forks { - if err := forks[i].GetOwner(); err != nil { - c.Error(500, "GetOwner", err) - return - } - apiForks[i] = forks[i].APIFormat(&api.Permission{ - Admin: c.User.IsAdminOfRepo(forks[i]), - Push: c.User.IsWriterOfRepo(forks[i]), - Pull: true, - }) - } - - c.JSON(200, &apiForks) -} - -func MirrorSync(c *context.APIContext) { - _, repo := parseOwnerAndRepo(c) - if c.Written() { - return - } else if !repo.IsMirror { - c.Status(404) - return - } - - go models.MirrorQueue.Add(repo.ID) - c.Status(202) -} diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go deleted file mode 100644 index bda1e23f..00000000 --- a/routers/api/v1/user/app.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" -) - -// https://github.com/gogits/go-gogs-client/wiki/Users#list-access-tokens-for-a-user -func ListAccessTokens(c *context.APIContext) { - tokens, err := models.ListAccessTokens(c.User.ID) - if err != nil { - c.Error(500, "ListAccessTokens", err) - return - } - - apiTokens := make([]*api.AccessToken, len(tokens)) - for i := range tokens { - apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1} - } - c.JSON(200, &apiTokens) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users#create-a-access-token -func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) { - t := &models.AccessToken{ - UID: c.User.ID, - Name: form.Name, - } - if err := models.NewAccessToken(t); err != nil { - c.Error(500, "NewAccessToken", err) - return - } - c.JSON(201, &api.AccessToken{t.Name, t.Sha1}) -} diff --git a/routers/api/v1/user/email.go b/routers/api/v1/user/email.go deleted file mode 100644 index 7c527a1a..00000000 --- a/routers/api/v1/user/email.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/api/v1/convert" -) - -// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#list-email-addresses-for-a-user -func ListEmails(c *context.APIContext) { - emails, err := models.GetEmailAddresses(c.User.ID) - if err != nil { - c.Error(500, "GetEmailAddresses", err) - return - } - apiEmails := make([]*api.Email, len(emails)) - for i := range emails { - apiEmails[i] = convert.ToEmail(emails[i]) - } - c.JSON(200, &apiEmails) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#add-email-addresses -func AddEmail(c *context.APIContext, form api.CreateEmailOption) { - if len(form.Emails) == 0 { - c.Status(422) - return - } - - emails := make([]*models.EmailAddress, len(form.Emails)) - for i := range form.Emails { - emails[i] = &models.EmailAddress{ - UID: c.User.ID, - Email: form.Emails[i], - IsActivated: !setting.Service.RegisterEmailConfirm, - } - } - - if err := models.AddEmailAddresses(emails); err != nil { - if models.IsErrEmailAlreadyUsed(err) { - c.Error(422, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email) - } else { - c.Error(500, "AddEmailAddresses", err) - } - return - } - - apiEmails := make([]*api.Email, len(emails)) - for i := range emails { - apiEmails[i] = convert.ToEmail(emails[i]) - } - c.JSON(201, &apiEmails) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#delete-email-addresses -func DeleteEmail(c *context.APIContext, form api.CreateEmailOption) { - if len(form.Emails) == 0 { - c.Status(204) - return - } - - emails := make([]*models.EmailAddress, len(form.Emails)) - for i := range form.Emails { - emails[i] = &models.EmailAddress{ - UID: c.User.ID, - Email: form.Emails[i], - } - } - - if err := models.DeleteEmailAddresses(emails); err != nil { - c.Error(500, "DeleteEmailAddresses", err) - return - } - c.Status(204) -} diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go deleted file mode 100644 index 6bbd4c7e..00000000 --- a/routers/api/v1/user/follower.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" -) - -func responseApiUsers(c *context.APIContext, users []*models.User) { - apiUsers := make([]*api.User, len(users)) - for i := range users { - apiUsers[i] = users[i].APIFormat() - } - c.JSON(200, &apiUsers) -} - -func listUserFollowers(c *context.APIContext, u *models.User) { - users, err := u.GetFollowers(c.QueryInt("page")) - if err != nil { - c.Error(500, "GetUserFollowers", err) - return - } - responseApiUsers(c, users) -} - -func ListMyFollowers(c *context.APIContext) { - listUserFollowers(c, c.User) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-followers-of-a-user -func ListFollowers(c *context.APIContext) { - u := GetUserByParams(c) - if c.Written() { - return - } - listUserFollowers(c, u) -} - -func listUserFollowing(c *context.APIContext, u *models.User) { - users, err := u.GetFollowing(c.QueryInt("page")) - if err != nil { - c.Error(500, "GetFollowing", err) - return - } - responseApiUsers(c, users) -} - -func ListMyFollowing(c *context.APIContext) { - listUserFollowing(c, c.User) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-users-followed-by-another-user -func ListFollowing(c *context.APIContext) { - u := GetUserByParams(c) - if c.Written() { - return - } - listUserFollowing(c, u) -} - -func checkUserFollowing(c *context.APIContext, u *models.User, followID int64) { - if u.IsFollowing(followID) { - c.Status(204) - } else { - c.Status(404) - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-you-are-following-a-user -func CheckMyFollowing(c *context.APIContext) { - target := GetUserByParams(c) - if c.Written() { - return - } - checkUserFollowing(c, c.User, target.ID) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-one-user-follows-another -func CheckFollowing(c *context.APIContext) { - u := GetUserByParams(c) - if c.Written() { - return - } - target := GetUserByParamsName(c, ":target") - if c.Written() { - return - } - checkUserFollowing(c, u, target.ID) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#follow-a-user -func Follow(c *context.APIContext) { - target := GetUserByParams(c) - if c.Written() { - return - } - if err := models.FollowUser(c.User.ID, target.ID); err != nil { - c.Error(500, "FollowUser", err) - return - } - c.Status(204) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#unfollow-a-user -func Unfollow(c *context.APIContext) { - target := GetUserByParams(c) - if c.Written() { - return - } - if err := models.UnfollowUser(c.User.ID, target.ID); err != nil { - c.Error(500, "UnfollowUser", err) - return - } - c.Status(204) -} diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go deleted file mode 100644 index f0c833f5..00000000 --- a/routers/api/v1/user/key.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/api/v1/convert" - "github.com/gogits/gogs/routers/api/v1/repo" -) - -func GetUserByParamsName(c *context.APIContext, name string) *models.User { - user, err := models.GetUserByName(c.Params(name)) - if err != nil { - if errors.IsUserNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetUserByName", err) - } - return nil - } - return user -} - -// GetUserByParams returns user whose name is presented in URL paramenter. -func GetUserByParams(c *context.APIContext) *models.User { - return GetUserByParamsName(c, ":username") -} - -func composePublicKeysAPILink() string { - return setting.AppURL + "api/v1/user/keys/" -} - -func listPublicKeys(c *context.APIContext, uid int64) { - keys, err := models.ListPublicKeys(uid) - if err != nil { - c.Error(500, "ListPublicKeys", err) - return - } - - apiLink := composePublicKeysAPILink() - apiKeys := make([]*api.PublicKey, len(keys)) - for i := range keys { - apiKeys[i] = convert.ToPublicKey(apiLink, keys[i]) - } - - c.JSON(200, &apiKeys) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-your-public-keys -func ListMyPublicKeys(c *context.APIContext) { - listPublicKeys(c, c.User.ID) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-public-keys-for-a-user -func ListPublicKeys(c *context.APIContext) { - user := GetUserByParams(c) - if c.Written() { - return - } - listPublicKeys(c, user.ID) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#get-a-single-public-key -func GetPublicKey(c *context.APIContext) { - key, err := models.GetPublicKeyByID(c.ParamsInt64(":id")) - if err != nil { - if models.IsErrKeyNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetPublicKeyByID", err) - } - return - } - - apiLink := composePublicKeysAPILink() - c.JSON(200, convert.ToPublicKey(apiLink, key)) -} - -// CreateUserPublicKey creates new public key to given user by ID. -func CreateUserPublicKey(c *context.APIContext, form api.CreateKeyOption, uid int64) { - content, err := models.CheckPublicKeyString(form.Key) - if err != nil { - repo.HandleCheckKeyStringError(c, err) - return - } - - key, err := models.AddPublicKey(uid, form.Title, content) - if err != nil { - repo.HandleAddKeyError(c, err) - return - } - apiLink := composePublicKeysAPILink() - c.JSON(201, convert.ToPublicKey(apiLink, key)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key -func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) { - CreateUserPublicKey(c, form, c.User.ID) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#delete-a-public-key -func DeletePublicKey(c *context.APIContext) { - if err := models.DeletePublicKey(c.User, c.ParamsInt64(":id")); err != nil { - if models.IsErrKeyAccessDenied(err) { - c.Error(403, "", "You do not have access to this key") - } else { - c.Error(500, "DeletePublicKey", err) - } - return - } - - c.Status(204) -} diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go deleted file mode 100644 index dbf727de..00000000 --- a/routers/api/v1/user/user.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - "github.com/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" -) - -func Search(c *context.APIContext) { - opts := &models.SearchUserOptions{ - Keyword: c.Query("q"), - Type: models.USER_TYPE_INDIVIDUAL, - PageSize: com.StrTo(c.Query("limit")).MustInt(), - } - if opts.PageSize == 0 { - opts.PageSize = 10 - } - - users, _, err := models.SearchUserByName(opts) - if err != nil { - c.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - - results := make([]*api.User, len(users)) - for i := range users { - results[i] = &api.User{ - ID: users[i].ID, - UserName: users[i].Name, - AvatarUrl: users[i].AvatarLink(), - FullName: users[i].FullName, - } - if c.IsLogged { - results[i].Email = users[i].Email - } - } - - c.JSON(200, map[string]interface{}{ - "ok": true, - "data": results, - }) -} - -func GetInfo(c *context.APIContext) { - u, err := models.GetUserByName(c.Params(":username")) - if err != nil { - if errors.IsUserNotExist(err) { - c.Status(404) - } else { - c.Error(500, "GetUserByName", err) - } - return - } - - // Hide user e-mail when API caller isn't signed in. - if !c.IsLogged { - u.Email = "" - } - c.JSON(200, u.APIFormat()) -} - -func GetAuthenticatedUser(c *context.APIContext) { - c.JSON(200, c.User.APIFormat()) -} diff --git a/routers/dev/template.go b/routers/dev/template.go deleted file mode 100644 index 00afa5c4..00000000 --- a/routers/dev/template.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package dev - -import ( - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" -) - -func TemplatePreview(c *context.Context) { - c.Data["User"] = models.User{Name: "Unknown"} - c.Data["AppName"] = setting.AppName - c.Data["AppVer"] = setting.AppVer - c.Data["AppURL"] = setting.AppURL - c.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374" - c.Data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60 - c.Data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60 - c.Data["CurDbValue"] = "" - - c.HTML(200, (c.Params("*"))) -} diff --git a/routers/home.go b/routers/home.go deleted file mode 100644 index bb43dc58..00000000 --- a/routers/home.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package routers - -import ( - "github.com/Unknwon/paginater" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/user" -) - -const ( - HOME = "home" - EXPLORE_REPOS = "explore/repos" - EXPLORE_USERS = "explore/users" - EXPLORE_ORGANIZATIONS = "explore/organizations" -) - -func Home(c *context.Context) { - if c.IsLogged { - if !c.User.IsActive && setting.Service.RegisterEmailConfirm { - c.Data["Title"] = c.Tr("auth.active_your_account") - c.HTML(200, user.ACTIVATE) - } else { - user.Dashboard(c) - } - return - } - - // Check auto-login. - uname := c.GetCookie(setting.CookieUserName) - if len(uname) != 0 { - c.Redirect(setting.AppSubURL + "/user/login") - return - } - - c.Data["PageIsHome"] = true - c.HTML(200, HOME) -} - -func ExploreRepos(c *context.Context) { - c.Data["Title"] = c.Tr("explore") - c.Data["PageIsExplore"] = true - c.Data["PageIsExploreRepositories"] = true - - page := c.QueryInt("page") - if page <= 0 { - page = 1 - } - - keyword := c.Query("q") - repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ - Keyword: keyword, - UserID: c.UserID(), - OrderBy: "updated_unix DESC", - Page: page, - PageSize: setting.UI.ExplorePagingNum, - }) - if err != nil { - c.Handle(500, "SearchRepositoryByName", err) - return - } - c.Data["Keyword"] = keyword - c.Data["Total"] = count - c.Data["Page"] = paginater.New(int(count), setting.UI.ExplorePagingNum, page, 5) - - if err = models.RepositoryList(repos).LoadAttributes(); err != nil { - c.Handle(500, "LoadAttributes", err) - return - } - c.Data["Repos"] = repos - - c.HTML(200, EXPLORE_REPOS) -} - -type UserSearchOptions struct { - Type models.UserType - Counter func() int64 - Ranger func(int, int) ([]*models.User, error) - PageSize int - OrderBy string - TplName string -} - -func RenderUserSearch(c *context.Context, opts *UserSearchOptions) { - page := c.QueryInt("page") - if page <= 1 { - page = 1 - } - - var ( - users []*models.User - count int64 - err error - ) - - keyword := c.Query("q") - if len(keyword) == 0 { - users, err = opts.Ranger(page, opts.PageSize) - if err != nil { - c.Handle(500, "opts.Ranger", err) - return - } - count = opts.Counter() - } else { - users, count, err = models.SearchUserByName(&models.SearchUserOptions{ - Keyword: keyword, - Type: opts.Type, - OrderBy: opts.OrderBy, - Page: page, - PageSize: opts.PageSize, - }) - if err != nil { - c.Handle(500, "SearchUserByName", err) - return - } - } - c.Data["Keyword"] = keyword - c.Data["Total"] = count - c.Data["Page"] = paginater.New(int(count), opts.PageSize, page, 5) - c.Data["Users"] = users - - c.HTML(200, opts.TplName) -} - -func ExploreUsers(c *context.Context) { - c.Data["Title"] = c.Tr("explore") - c.Data["PageIsExplore"] = true - c.Data["PageIsExploreUsers"] = true - - RenderUserSearch(c, &UserSearchOptions{ - Type: models.USER_TYPE_INDIVIDUAL, - Counter: models.CountUsers, - Ranger: models.Users, - PageSize: setting.UI.ExplorePagingNum, - OrderBy: "updated_unix DESC", - TplName: EXPLORE_USERS, - }) -} - -func ExploreOrganizations(c *context.Context) { - c.Data["Title"] = c.Tr("explore") - c.Data["PageIsExplore"] = true - c.Data["PageIsExploreOrganizations"] = true - - RenderUserSearch(c, &UserSearchOptions{ - Type: models.USER_TYPE_ORGANIZATION, - Counter: models.CountOrganizations, - Ranger: models.Organizations, - PageSize: setting.UI.ExplorePagingNum, - OrderBy: "updated_unix DESC", - TplName: EXPLORE_ORGANIZATIONS, - }) -} - -func NotFound(c *context.Context) { - c.Data["Title"] = "Page Not Found" - c.NotFound() -} diff --git a/routers/install.go b/routers/install.go deleted file mode 100644 index 3b48d8d3..00000000 --- a/routers/install.go +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package routers - -import ( - "net/mail" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/Unknwon/com" - "github.com/go-xorm/xorm" - log "gopkg.in/clog.v1" - "gopkg.in/ini.v1" - "gopkg.in/macaron.v1" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/cron" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/mailer" - "github.com/gogits/gogs/pkg/markup" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/ssh" - "github.com/gogits/gogs/pkg/template/highlight" - "github.com/gogits/gogs/pkg/tool" - "github.com/gogits/gogs/pkg/user" -) - -const ( - INSTALL = "install" -) - -func checkRunMode() { - if setting.ProdMode { - macaron.Env = macaron.PROD - macaron.ColorLog = false - } else { - git.Debug = true - } - log.Info("Run Mode: %s", strings.Title(macaron.Env)) -} - -func NewServices() { - setting.NewServices() - mailer.NewContext() -} - -// GlobalInit is for global configuration reload-able. -func GlobalInit() { - setting.NewContext() - log.Trace("Custom path: %s", setting.CustomPath) - log.Trace("Log path: %s", setting.LogRootPath) - models.LoadConfigs() - NewServices() - - if setting.InstallLock { - highlight.NewContext() - markup.NewSanitizer() - if err := models.NewEngine(); err != nil { - log.Fatal(2, "Fail to initialize ORM engine: %v", err) - } - models.HasEngine = true - - models.LoadRepoConfig() - models.NewRepoContext() - - // Booting long running goroutines. - cron.NewContext() - models.InitSyncMirrors() - models.InitDeliverHooks() - models.InitTestPullRequests() - } - if models.EnableSQLite3 { - log.Info("SQLite3 Supported") - } - if setting.SupportMiniWinService { - log.Info("Builtin Windows Service Supported") - } - checkRunMode() - - if setting.InstallLock && setting.SSH.StartBuiltinServer { - ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers) - log.Info("SSH server started on %s:%v", setting.SSH.ListenHost, setting.SSH.ListenPort) - log.Trace("SSH server cipher list: %v", setting.SSH.ServerCiphers) - } -} - -func InstallInit(c *context.Context) { - if setting.InstallLock { - c.NotFound() - return - } - - c.Title("install.install") - c.PageIs("Install") - - dbOpts := []string{"MySQL", "PostgreSQL", "MSSQL"} - if models.EnableSQLite3 { - dbOpts = append(dbOpts, "SQLite3") - } - c.Data["DbOptions"] = dbOpts -} - -func Install(c *context.Context) { - f := form.Install{} - - // Database settings - f.DbHost = models.DbCfg.Host - f.DbUser = models.DbCfg.User - f.DbName = models.DbCfg.Name - f.DbPath = models.DbCfg.Path - - c.Data["CurDbOption"] = "MySQL" - switch models.DbCfg.Type { - case "postgres": - c.Data["CurDbOption"] = "PostgreSQL" - case "mssql": - c.Data["CurDbOption"] = "MSSQL" - case "sqlite3": - if models.EnableSQLite3 { - c.Data["CurDbOption"] = "SQLite3" - } - } - - // Application general settings - f.AppName = setting.AppName - f.RepoRootPath = setting.RepoRootPath - - // Note(unknwon): it's hard for Windows users change a running user, - // so just use current one if config says default. - if setting.IsWindows && setting.RunUser == "git" { - f.RunUser = user.CurrentUsername() - } else { - f.RunUser = setting.RunUser - } - - f.Domain = setting.Domain - f.SSHPort = setting.SSH.Port - f.UseBuiltinSSHServer = setting.SSH.StartBuiltinServer - f.HTTPPort = setting.HTTPPort - f.AppUrl = setting.AppURL - f.LogRootPath = setting.LogRootPath - - // E-mail service settings - if setting.MailService != nil { - f.SMTPHost = setting.MailService.Host - f.SMTPFrom = setting.MailService.From - f.SMTPUser = setting.MailService.User - } - f.RegisterConfirm = setting.Service.RegisterEmailConfirm - f.MailNotify = setting.Service.EnableNotifyMail - - // Server and other services settings - f.OfflineMode = setting.OfflineMode - f.DisableGravatar = setting.DisableGravatar - f.EnableFederatedAvatar = setting.EnableFederatedAvatar - f.DisableRegistration = setting.Service.DisableRegistration - f.EnableCaptcha = setting.Service.EnableCaptcha - f.RequireSignInView = setting.Service.RequireSignInView - - form.Assign(f, c.Data) - c.Success(INSTALL) -} - -func InstallPost(c *context.Context, f form.Install) { - c.Data["CurDbOption"] = f.DbType - - if c.HasError() { - if c.HasValue("Err_SMTPEmail") { - c.FormErr("SMTP") - } - if c.HasValue("Err_AdminName") || - c.HasValue("Err_AdminPasswd") || - c.HasValue("Err_AdminEmail") { - c.FormErr("Admin") - } - - c.Success(INSTALL) - return - } - - if _, err := exec.LookPath("git"); err != nil { - c.RenderWithErr(c.Tr("install.test_git_failed", err), INSTALL, &f) - return - } - - // Pass basic check, now test configuration. - // Test database setting. - dbTypes := map[string]string{"MySQL": "mysql", "PostgreSQL": "postgres", "MSSQL": "mssql", "SQLite3": "sqlite3", "TiDB": "tidb"} - models.DbCfg.Type = dbTypes[f.DbType] - models.DbCfg.Host = f.DbHost - models.DbCfg.User = f.DbUser - models.DbCfg.Passwd = f.DbPasswd - models.DbCfg.Name = f.DbName - models.DbCfg.SSLMode = f.SSLMode - models.DbCfg.Path = f.DbPath - - if models.DbCfg.Type == "sqlite3" && len(models.DbCfg.Path) == 0 { - c.FormErr("DbPath") - c.RenderWithErr(c.Tr("install.err_empty_db_path"), INSTALL, &f) - return - } - - // Set test engine. - var x *xorm.Engine - if err := models.NewTestEngine(x); err != nil { - if strings.Contains(err.Error(), `Unknown database type: sqlite3`) { - c.FormErr("DbType") - c.RenderWithErr(c.Tr("install.sqlite3_not_available", "https://gogs.io/docs/installation/install_from_binary.html"), INSTALL, &f) - } else { - c.FormErr("DbSetting") - c.RenderWithErr(c.Tr("install.invalid_db_setting", err), INSTALL, &f) - } - return - } - - // Test repository root path. - f.RepoRootPath = strings.Replace(f.RepoRootPath, "\\", "/", -1) - if err := os.MkdirAll(f.RepoRootPath, os.ModePerm); err != nil { - c.FormErr("RepoRootPath") - c.RenderWithErr(c.Tr("install.invalid_repo_path", err), INSTALL, &f) - return - } - - // Test log root path. - f.LogRootPath = strings.Replace(f.LogRootPath, "\\", "/", -1) - if err := os.MkdirAll(f.LogRootPath, os.ModePerm); err != nil { - c.FormErr("LogRootPath") - c.RenderWithErr(c.Tr("install.invalid_log_root_path", err), INSTALL, &f) - return - } - - currentUser, match := setting.IsRunUserMatchCurrentUser(f.RunUser) - if !match { - c.FormErr("RunUser") - c.RenderWithErr(c.Tr("install.run_user_not_match", f.RunUser, currentUser), INSTALL, &f) - return - } - - // Check host address and port - if len(f.SMTPHost) > 0 && !strings.Contains(f.SMTPHost, ":") { - c.FormErr("SMTP", "SMTPHost") - c.RenderWithErr(c.Tr("install.smtp_host_missing_port"), INSTALL, &f) - return - } - - // Make sure FROM field is valid - if len(f.SMTPFrom) > 0 { - _, err := mail.ParseAddress(f.SMTPFrom) - if err != nil { - c.FormErr("SMTP", "SMTPFrom") - c.RenderWithErr(c.Tr("install.invalid_smtp_from", err), INSTALL, &f) - return - } - } - - // Check logic loophole between disable self-registration and no admin account. - if f.DisableRegistration && len(f.AdminName) == 0 { - c.FormErr("Services", "Admin") - c.RenderWithErr(c.Tr("install.no_admin_and_disable_registration"), INSTALL, f) - return - } - - // Check admin password. - if len(f.AdminName) > 0 && len(f.AdminPasswd) == 0 { - c.FormErr("Admin", "AdminPasswd") - c.RenderWithErr(c.Tr("install.err_empty_admin_password"), INSTALL, f) - return - } - if f.AdminPasswd != f.AdminConfirmPasswd { - c.FormErr("Admin", "AdminPasswd") - c.RenderWithErr(c.Tr("form.password_not_match"), INSTALL, f) - return - } - - if f.AppUrl[len(f.AppUrl)-1] != '/' { - f.AppUrl += "/" - } - - // Save settings. - cfg := ini.Empty() - if com.IsFile(setting.CustomConf) { - // Keeps custom settings if there is already something. - if err := cfg.Append(setting.CustomConf); err != nil { - log.Error(2, "Fail to load custom conf '%s': %v", setting.CustomConf, err) - } - } - cfg.Section("database").Key("DB_TYPE").SetValue(models.DbCfg.Type) - cfg.Section("database").Key("HOST").SetValue(models.DbCfg.Host) - cfg.Section("database").Key("NAME").SetValue(models.DbCfg.Name) - cfg.Section("database").Key("USER").SetValue(models.DbCfg.User) - cfg.Section("database").Key("PASSWD").SetValue(models.DbCfg.Passwd) - cfg.Section("database").Key("SSL_MODE").SetValue(models.DbCfg.SSLMode) - cfg.Section("database").Key("PATH").SetValue(models.DbCfg.Path) - - cfg.Section("").Key("APP_NAME").SetValue(f.AppName) - cfg.Section("repository").Key("ROOT").SetValue(f.RepoRootPath) - cfg.Section("").Key("RUN_USER").SetValue(f.RunUser) - cfg.Section("server").Key("DOMAIN").SetValue(f.Domain) - cfg.Section("server").Key("HTTP_PORT").SetValue(f.HTTPPort) - cfg.Section("server").Key("ROOT_URL").SetValue(f.AppUrl) - - if f.SSHPort == 0 { - cfg.Section("server").Key("DISABLE_SSH").SetValue("true") - } else { - cfg.Section("server").Key("DISABLE_SSH").SetValue("false") - cfg.Section("server").Key("SSH_PORT").SetValue(com.ToStr(f.SSHPort)) - cfg.Section("server").Key("START_SSH_SERVER").SetValue(com.ToStr(f.UseBuiltinSSHServer)) - } - - if len(strings.TrimSpace(f.SMTPHost)) > 0 { - cfg.Section("mailer").Key("ENABLED").SetValue("true") - cfg.Section("mailer").Key("HOST").SetValue(f.SMTPHost) - cfg.Section("mailer").Key("FROM").SetValue(f.SMTPFrom) - cfg.Section("mailer").Key("USER").SetValue(f.SMTPUser) - cfg.Section("mailer").Key("PASSWD").SetValue(f.SMTPPasswd) - } else { - cfg.Section("mailer").Key("ENABLED").SetValue("false") - } - cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(com.ToStr(f.RegisterConfirm)) - cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(com.ToStr(f.MailNotify)) - - cfg.Section("server").Key("OFFLINE_MODE").SetValue(com.ToStr(f.OfflineMode)) - cfg.Section("picture").Key("DISABLE_GRAVATAR").SetValue(com.ToStr(f.DisableGravatar)) - cfg.Section("picture").Key("ENABLE_FEDERATED_AVATAR").SetValue(com.ToStr(f.EnableFederatedAvatar)) - cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(com.ToStr(f.DisableRegistration)) - cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(com.ToStr(f.EnableCaptcha)) - cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(f.RequireSignInView)) - - cfg.Section("").Key("RUN_MODE").SetValue("prod") - - cfg.Section("session").Key("PROVIDER").SetValue("file") - - mode := "file" - if f.EnableConsoleMode { - mode = "console, file" - } - cfg.Section("log").Key("MODE").SetValue(mode) - cfg.Section("log").Key("LEVEL").SetValue("Info") - cfg.Section("log").Key("ROOT_PATH").SetValue(f.LogRootPath) - - cfg.Section("security").Key("INSTALL_LOCK").SetValue("true") - secretKey, err := tool.RandomString(15) - if err != nil { - c.RenderWithErr(c.Tr("install.secret_key_failed", err), INSTALL, &f) - return - } - cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey) - - os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm) - if err := cfg.SaveTo(setting.CustomConf); err != nil { - c.RenderWithErr(c.Tr("install.save_config_failed", err), INSTALL, &f) - return - } - - GlobalInit() - - // Create admin account - if len(f.AdminName) > 0 { - u := &models.User{ - Name: f.AdminName, - Email: f.AdminEmail, - Passwd: f.AdminPasswd, - IsAdmin: true, - IsActive: true, - } - if err := models.CreateUser(u); err != nil { - if !models.IsErrUserAlreadyExist(err) { - setting.InstallLock = false - c.FormErr("AdminName", "AdminEmail") - c.RenderWithErr(c.Tr("install.invalid_admin_setting", err), INSTALL, &f) - return - } - log.Info("Admin account already exist") - u, _ = models.GetUserByName(u.Name) - } - - // Auto-login for admin - c.Session.Set("uid", u.ID) - c.Session.Set("uname", u.Name) - } - - log.Info("First-time run install finished!") - c.Flash.Success(c.Tr("install.install_success")) - c.Redirect(f.AppUrl + "user/login") -} diff --git a/routers/org/members.go b/routers/org/members.go deleted file mode 100644 index b529748c..00000000 --- a/routers/org/members.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package org - -import ( - "github.com/Unknwon/com" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - MEMBERS = "org/member/members" - MEMBER_INVITE = "org/member/invite" -) - -func Members(c *context.Context) { - org := c.Org.Organization - c.Data["Title"] = org.FullName - c.Data["PageIsOrgMembers"] = true - - if err := org.GetMembers(); err != nil { - c.Handle(500, "GetMembers", err) - return - } - c.Data["Members"] = org.Members - - c.HTML(200, MEMBERS) -} - -func MembersAction(c *context.Context) { - uid := com.StrTo(c.Query("uid")).MustInt64() - if uid == 0 { - c.Redirect(c.Org.OrgLink + "/members") - return - } - - org := c.Org.Organization - var err error - switch c.Params(":action") { - case "private": - if c.User.ID != uid && !c.Org.IsOwner { - c.Error(404) - return - } - err = models.ChangeOrgUserStatus(org.ID, uid, false) - case "public": - if c.User.ID != uid && !c.Org.IsOwner { - c.Error(404) - return - } - err = models.ChangeOrgUserStatus(org.ID, uid, true) - case "remove": - if !c.Org.IsOwner { - c.Error(404) - return - } - err = org.RemoveMember(uid) - if models.IsErrLastOrgOwner(err) { - c.Flash.Error(c.Tr("form.last_org_owner")) - c.Redirect(c.Org.OrgLink + "/members") - return - } - case "leave": - err = org.RemoveMember(c.User.ID) - if models.IsErrLastOrgOwner(err) { - c.Flash.Error(c.Tr("form.last_org_owner")) - c.Redirect(c.Org.OrgLink + "/members") - return - } - } - - if err != nil { - log.Error(4, "Action(%s): %v", c.Params(":action"), err) - c.JSON(200, map[string]interface{}{ - "ok": false, - "err": err.Error(), - }) - return - } - - if c.Params(":action") != "leave" { - c.Redirect(c.Org.OrgLink + "/members") - } else { - c.Redirect(setting.AppSubURL + "/") - } -} - -func Invitation(c *context.Context) { - org := c.Org.Organization - c.Data["Title"] = org.FullName - c.Data["PageIsOrgMembers"] = true - - if c.Req.Method == "POST" { - uname := c.Query("uname") - u, err := models.GetUserByName(uname) - if err != nil { - if errors.IsUserNotExist(err) { - c.Flash.Error(c.Tr("form.user_not_exist")) - c.Redirect(c.Org.OrgLink + "/invitations/new") - } else { - c.Handle(500, " GetUserByName", err) - } - return - } - - if err = org.AddMember(u.ID); err != nil { - c.Handle(500, " AddMember", err) - return - } - - log.Trace("New member added(%s): %s", org.Name, u.Name) - c.Redirect(c.Org.OrgLink + "/members") - return - } - - c.HTML(200, MEMBER_INVITE) -} diff --git a/routers/org/org.go b/routers/org/org.go deleted file mode 100644 index 775e9915..00000000 --- a/routers/org/org.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package org - -import ( - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - CREATE = "org/create" -) - -func Create(c *context.Context) { - c.Data["Title"] = c.Tr("new_org") - c.HTML(200, CREATE) -} - -func CreatePost(c *context.Context, f form.CreateOrg) { - c.Data["Title"] = c.Tr("new_org") - - if c.HasError() { - c.HTML(200, CREATE) - return - } - - org := &models.User{ - Name: f.OrgName, - IsActive: true, - Type: models.USER_TYPE_ORGANIZATION, - } - - if err := models.CreateOrganization(org, c.User); err != nil { - c.Data["Err_OrgName"] = true - switch { - case models.IsErrUserAlreadyExist(err): - c.RenderWithErr(c.Tr("form.org_name_been_taken"), CREATE, &f) - case models.IsErrNameReserved(err): - c.RenderWithErr(c.Tr("org.form.name_reserved", err.(models.ErrNameReserved).Name), CREATE, &f) - case models.IsErrNamePatternNotAllowed(err): - c.RenderWithErr(c.Tr("org.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), CREATE, &f) - default: - c.Handle(500, "CreateOrganization", err) - } - return - } - log.Trace("Organization created: %s", org.Name) - - c.Redirect(setting.AppSubURL + "/org/" + f.OrgName + "/dashboard") -} diff --git a/routers/org/setting.go b/routers/org/setting.go deleted file mode 100644 index 8e2e556f..00000000 --- a/routers/org/setting.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package org - -import ( - "strings" - - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/user" -) - -const ( - SETTINGS_OPTIONS = "org/settings/options" - SETTINGS_DELETE = "org/settings/delete" - SETTINGS_WEBHOOKS = "org/settings/webhooks" -) - -func Settings(c *context.Context) { - c.Data["Title"] = c.Tr("org.settings") - c.Data["PageIsSettingsOptions"] = true - c.HTML(200, SETTINGS_OPTIONS) -} - -func SettingsPost(c *context.Context, f form.UpdateOrgSetting) { - c.Data["Title"] = c.Tr("org.settings") - c.Data["PageIsSettingsOptions"] = true - - if c.HasError() { - c.HTML(200, SETTINGS_OPTIONS) - return - } - - org := c.Org.Organization - - // Check if organization name has been changed. - if org.LowerName != strings.ToLower(f.Name) { - isExist, err := models.IsUserExist(org.ID, f.Name) - if err != nil { - c.Handle(500, "IsUserExist", err) - return - } else if isExist { - c.Data["OrgName"] = true - c.RenderWithErr(c.Tr("form.username_been_taken"), SETTINGS_OPTIONS, &f) - return - } else if err = models.ChangeUserName(org, f.Name); err != nil { - c.Data["OrgName"] = true - switch { - case models.IsErrNameReserved(err): - c.RenderWithErr(c.Tr("user.form.name_reserved"), SETTINGS_OPTIONS, &f) - case models.IsErrNamePatternNotAllowed(err): - c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed"), SETTINGS_OPTIONS, &f) - default: - c.Handle(500, "ChangeUserName", err) - } - return - } - // reset c.org.OrgLink with new name - c.Org.OrgLink = setting.AppSubURL + "/org/" + f.Name - log.Trace("Organization name changed: %s -> %s", org.Name, f.Name) - } - // In case it's just a case change. - org.Name = f.Name - org.LowerName = strings.ToLower(f.Name) - - if c.User.IsAdmin { - org.MaxRepoCreation = f.MaxRepoCreation - } - - org.FullName = f.FullName - org.Description = f.Description - org.Website = f.Website - org.Location = f.Location - if err := models.UpdateUser(org); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - log.Trace("Organization setting updated: %s", org.Name) - c.Flash.Success(c.Tr("org.settings.update_setting_success")) - c.Redirect(c.Org.OrgLink + "/settings") -} - -func SettingsAvatar(c *context.Context, f form.Avatar) { - f.Source = form.AVATAR_LOCAL - if err := user.UpdateAvatarSetting(c, f, c.Org.Organization); err != nil { - c.Flash.Error(err.Error()) - } else { - c.Flash.Success(c.Tr("org.settings.update_avatar_success")) - } - - c.Redirect(c.Org.OrgLink + "/settings") -} - -func SettingsDeleteAvatar(c *context.Context) { - if err := c.Org.Organization.DeleteAvatar(); err != nil { - c.Flash.Error(err.Error()) - } - - c.Redirect(c.Org.OrgLink + "/settings") -} - -func SettingsDelete(c *context.Context) { - c.Data["Title"] = c.Tr("org.settings") - c.Data["PageIsSettingsDelete"] = true - - org := c.Org.Organization - if c.Req.Method == "POST" { - if _, err := models.UserSignIn(c.User.Name, c.Query("password")); err != nil { - if errors.IsUserNotExist(err) { - c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil) - } else { - c.Handle(500, "UserSignIn", err) - } - return - } - - if err := models.DeleteOrganization(org); err != nil { - if models.IsErrUserOwnRepos(err) { - c.Flash.Error(c.Tr("form.org_still_own_repo")) - c.Redirect(c.Org.OrgLink + "/settings/delete") - } else { - c.Handle(500, "DeleteOrganization", err) - } - } else { - log.Trace("Organization deleted: %s", org.Name) - c.Redirect(setting.AppSubURL + "/") - } - return - } - - c.HTML(200, SETTINGS_DELETE) -} - -func Webhooks(c *context.Context) { - c.Data["Title"] = c.Tr("org.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["BaseLink"] = c.Org.OrgLink - c.Data["Description"] = c.Tr("org.settings.hooks_desc") - c.Data["Types"] = setting.Webhook.Types - - ws, err := models.GetWebhooksByOrgID(c.Org.Organization.ID) - if err != nil { - c.Handle(500, "GetWebhooksByOrgId", err) - return - } - - c.Data["Webhooks"] = ws - c.HTML(200, SETTINGS_WEBHOOKS) -} - -func DeleteWebhook(c *context.Context) { - if err := models.DeleteWebhookOfOrgByID(c.Org.Organization.ID, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteWebhookByOrgID: " + err.Error()) - } else { - c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Org.OrgLink + "/settings/hooks", - }) -} diff --git a/routers/org/teams.go b/routers/org/teams.go deleted file mode 100644 index c97d470d..00000000 --- a/routers/org/teams.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package org - -import ( - "path" - - "github.com/Unknwon/com" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" -) - -const ( - TEAMS = "org/team/teams" - TEAM_NEW = "org/team/new" - TEAM_MEMBERS = "org/team/members" - TEAM_REPOSITORIES = "org/team/repositories" -) - -func Teams(c *context.Context) { - org := c.Org.Organization - c.Data["Title"] = org.FullName - c.Data["PageIsOrgTeams"] = true - - for _, t := range org.Teams { - if err := t.GetMembers(); err != nil { - c.Handle(500, "GetMembers", err) - return - } - } - c.Data["Teams"] = org.Teams - - c.HTML(200, TEAMS) -} - -func TeamsAction(c *context.Context) { - uid := com.StrTo(c.Query("uid")).MustInt64() - if uid == 0 { - c.Redirect(c.Org.OrgLink + "/teams") - return - } - - page := c.Query("page") - var err error - switch c.Params(":action") { - case "join": - if !c.Org.IsOwner { - c.Error(404) - return - } - err = c.Org.Team.AddMember(c.User.ID) - case "leave": - err = c.Org.Team.RemoveMember(c.User.ID) - case "remove": - if !c.Org.IsOwner { - c.Error(404) - return - } - err = c.Org.Team.RemoveMember(uid) - page = "team" - case "add": - if !c.Org.IsOwner { - c.Error(404) - return - } - uname := c.Query("uname") - var u *models.User - u, err = models.GetUserByName(uname) - if err != nil { - if errors.IsUserNotExist(err) { - c.Flash.Error(c.Tr("form.user_not_exist")) - c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName) - } else { - c.Handle(500, " GetUserByName", err) - } - return - } - - err = c.Org.Team.AddMember(u.ID) - page = "team" - } - - if err != nil { - if models.IsErrLastOrgOwner(err) { - c.Flash.Error(c.Tr("form.last_org_owner")) - } else { - log.Error(3, "Action(%s): %v", c.Params(":action"), err) - c.JSON(200, map[string]interface{}{ - "ok": false, - "err": err.Error(), - }) - return - } - } - - switch page { - case "team": - c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName) - default: - c.Redirect(c.Org.OrgLink + "/teams") - } -} - -func TeamsRepoAction(c *context.Context) { - if !c.Org.IsOwner { - c.Error(404) - return - } - - var err error - switch c.Params(":action") { - case "add": - repoName := path.Base(c.Query("repo_name")) - var repo *models.Repository - repo, err = models.GetRepositoryByName(c.Org.Organization.ID, repoName) - if err != nil { - if errors.IsRepoNotExist(err) { - c.Flash.Error(c.Tr("org.teams.add_nonexistent_repo")) - c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName + "/repositories") - return - } - c.Handle(500, "GetRepositoryByName", err) - return - } - err = c.Org.Team.AddRepository(repo) - case "remove": - err = c.Org.Team.RemoveRepository(com.StrTo(c.Query("repoid")).MustInt64()) - } - - if err != nil { - log.Error(3, "Action(%s): '%s' %v", c.Params(":action"), c.Org.Team.Name, err) - c.Handle(500, "TeamsRepoAction", err) - return - } - c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName + "/repositories") -} - -func NewTeam(c *context.Context) { - c.Data["Title"] = c.Org.Organization.FullName - c.Data["PageIsOrgTeams"] = true - c.Data["PageIsOrgTeamsNew"] = true - c.Data["Team"] = &models.Team{} - c.HTML(200, TEAM_NEW) -} - -func NewTeamPost(c *context.Context, f form.CreateTeam) { - c.Data["Title"] = c.Org.Organization.FullName - c.Data["PageIsOrgTeams"] = true - c.Data["PageIsOrgTeamsNew"] = true - - t := &models.Team{ - OrgID: c.Org.Organization.ID, - Name: f.TeamName, - Description: f.Description, - Authorize: models.ParseAccessMode(f.Permission), - } - c.Data["Team"] = t - - if c.HasError() { - c.HTML(200, TEAM_NEW) - return - } - - if err := models.NewTeam(t); err != nil { - c.Data["Err_TeamName"] = true - switch { - case models.IsErrTeamAlreadyExist(err): - c.RenderWithErr(c.Tr("form.team_name_been_taken"), TEAM_NEW, &f) - case models.IsErrNameReserved(err): - c.RenderWithErr(c.Tr("org.form.team_name_reserved", err.(models.ErrNameReserved).Name), TEAM_NEW, &f) - default: - c.Handle(500, "NewTeam", err) - } - return - } - log.Trace("Team created: %s/%s", c.Org.Organization.Name, t.Name) - c.Redirect(c.Org.OrgLink + "/teams/" + t.LowerName) -} - -func TeamMembers(c *context.Context) { - c.Data["Title"] = c.Org.Team.Name - c.Data["PageIsOrgTeams"] = true - if err := c.Org.Team.GetMembers(); err != nil { - c.Handle(500, "GetMembers", err) - return - } - c.HTML(200, TEAM_MEMBERS) -} - -func TeamRepositories(c *context.Context) { - c.Data["Title"] = c.Org.Team.Name - c.Data["PageIsOrgTeams"] = true - if err := c.Org.Team.GetRepositories(); err != nil { - c.Handle(500, "GetRepositories", err) - return - } - c.HTML(200, TEAM_REPOSITORIES) -} - -func EditTeam(c *context.Context) { - c.Data["Title"] = c.Org.Organization.FullName - c.Data["PageIsOrgTeams"] = true - c.Data["team_name"] = c.Org.Team.Name - c.Data["desc"] = c.Org.Team.Description - c.HTML(200, TEAM_NEW) -} - -func EditTeamPost(c *context.Context, f form.CreateTeam) { - t := c.Org.Team - c.Data["Title"] = c.Org.Organization.FullName - c.Data["PageIsOrgTeams"] = true - c.Data["Team"] = t - - if c.HasError() { - c.HTML(200, TEAM_NEW) - return - } - - isAuthChanged := false - if !t.IsOwnerTeam() { - // Validate permission level. - var auth models.AccessMode - switch f.Permission { - case "read": - auth = models.ACCESS_MODE_READ - case "write": - auth = models.ACCESS_MODE_WRITE - case "admin": - auth = models.ACCESS_MODE_ADMIN - default: - c.Error(401) - return - } - - t.Name = f.TeamName - if t.Authorize != auth { - isAuthChanged = true - t.Authorize = auth - } - } - t.Description = f.Description - if err := models.UpdateTeam(t, isAuthChanged); err != nil { - c.Data["Err_TeamName"] = true - switch { - case models.IsErrTeamAlreadyExist(err): - c.RenderWithErr(c.Tr("form.team_name_been_taken"), TEAM_NEW, &f) - default: - c.Handle(500, "UpdateTeam", err) - } - return - } - c.Redirect(c.Org.OrgLink + "/teams/" + t.LowerName) -} - -func DeleteTeam(c *context.Context) { - if err := models.DeleteTeam(c.Org.Team); err != nil { - c.Flash.Error("DeleteTeam: " + err.Error()) - } else { - c.Flash.Success(c.Tr("org.teams.delete_team_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Org.OrgLink + "/teams", - }) -} diff --git a/routers/repo/branch.go b/routers/repo/branch.go deleted file mode 100644 index 685df2e7..00000000 --- a/routers/repo/branch.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "time" - - log "gopkg.in/clog.v1" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" -) - -const ( - BRANCHES_OVERVIEW = "repo/branches/overview" - BRANCHES_ALL = "repo/branches/all" -) - -type Branch struct { - Name string - Commit *git.Commit - IsProtected bool -} - -func loadBranches(c *context.Context) []*Branch { - rawBranches, err := c.Repo.Repository.GetBranches() - if err != nil { - c.Handle(500, "GetBranches", err) - return nil - } - - protectBranches, err := models.GetProtectBranchesByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Handle(500, "GetProtectBranchesByRepoID", err) - return nil - } - - branches := make([]*Branch, len(rawBranches)) - for i := range rawBranches { - commit, err := rawBranches[i].GetCommit() - if err != nil { - c.Handle(500, "GetCommit", err) - return nil - } - - branches[i] = &Branch{ - Name: rawBranches[i].Name, - Commit: commit, - } - - for j := range protectBranches { - if branches[i].Name == protectBranches[j].Name { - branches[i].IsProtected = true - break - } - } - } - - c.Data["AllowPullRequest"] = c.Repo.Repository.AllowsPulls() - return branches -} - -func Branches(c *context.Context) { - c.Data["Title"] = c.Tr("repo.git_branches") - c.Data["PageIsBranchesOverview"] = true - - branches := loadBranches(c) - if c.Written() { - return - } - - now := time.Now() - activeBranches := make([]*Branch, 0, 3) - staleBranches := make([]*Branch, 0, 3) - for i := range branches { - switch { - case branches[i].Name == c.Repo.BranchName: - c.Data["DefaultBranch"] = branches[i] - case branches[i].Commit.Committer.When.Add(30 * 24 * time.Hour).After(now): // 30 days - activeBranches = append(activeBranches, branches[i]) - case branches[i].Commit.Committer.When.Add(3 * 30 * 24 * time.Hour).Before(now): // 90 days - staleBranches = append(staleBranches, branches[i]) - } - } - - c.Data["ActiveBranches"] = activeBranches - c.Data["StaleBranches"] = staleBranches - c.HTML(200, BRANCHES_OVERVIEW) -} - -func AllBranches(c *context.Context) { - c.Data["Title"] = c.Tr("repo.git_branches") - c.Data["PageIsBranchesAll"] = true - - branches := loadBranches(c) - if c.Written() { - return - } - c.Data["Branches"] = branches - - c.HTML(200, BRANCHES_ALL) -} - -func DeleteBranchPost(c *context.Context) { - branchName := c.Params("*") - commitID := c.Query("commit") - - defer func() { - redirectTo := c.Query("redirect_to") - if len(redirectTo) == 0 { - redirectTo = c.Repo.RepoLink - } - c.Redirect(redirectTo) - }() - - if !c.Repo.GitRepo.IsBranchExist(branchName) { - return - } - if len(commitID) > 0 { - branchCommitID, err := c.Repo.GitRepo.GetBranchCommitID(branchName) - if err != nil { - log.Error(2, "GetBranchCommitID: %v", err) - return - } - - if branchCommitID != commitID { - c.Flash.Error(c.Tr("repo.pulls.delete_branch_has_new_commits")) - return - } - } - - if err := c.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ - Force: true, - }); err != nil { - log.Error(2, "DeleteBranch '%s': %v", branchName, err) - return - } -} diff --git a/routers/repo/commit.go b/routers/repo/commit.go deleted file mode 100644 index 17ea5dbe..00000000 --- a/routers/repo/commit.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "container/list" - "path" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - COMMITS = "repo/commits" - DIFF = "repo/diff/page" -) - -func RefCommits(c *context.Context) { - c.Data["PageIsViewFiles"] = true - switch { - case len(c.Repo.TreePath) == 0: - Commits(c) - case c.Repo.TreePath == "search": - SearchCommits(c) - default: - FileHistory(c) - } -} - -func RenderIssueLinks(oldCommits *list.List, repoLink string) *list.List { - newCommits := list.New() - for e := oldCommits.Front(); e != nil; e = e.Next() { - c := e.Value.(*git.Commit) - newCommits.PushBack(c) - } - return newCommits -} - -func renderCommits(c *context.Context, filename string) { - c.Data["Title"] = c.Tr("repo.commits.commit_history") + " · " + c.Repo.Repository.FullName() - c.Data["PageIsCommits"] = true - - page := c.QueryInt("page") - if page < 1 { - page = 1 - } - pageSize := c.QueryInt("pageSize") - if pageSize < 1 { - pageSize = git.DefaultCommitsPageSize - } - - // Both 'git log branchName' and 'git log commitID' work. - var err error - var commits *list.List - if len(filename) == 0 { - commits, err = c.Repo.Commit.CommitsByRangeSize(page, pageSize) - } else { - commits, err = c.Repo.GitRepo.CommitsByFileAndRangeSize(c.Repo.BranchName, filename, page, pageSize) - } - if err != nil { - c.Handle(500, "CommitsByRangeSize/CommitsByFileAndRangeSize", err) - return - } - commits = RenderIssueLinks(commits, c.Repo.RepoLink) - commits = models.ValidateCommitsWithEmails(commits) - c.Data["Commits"] = commits - - if page > 1 { - c.Data["HasPrevious"] = true - c.Data["PreviousPage"] = page - 1 - } - if commits.Len() == pageSize { - c.Data["HasNext"] = true - c.Data["NextPage"] = page + 1 - } - c.Data["PageSize"] = pageSize - - c.Data["Username"] = c.Repo.Owner.Name - c.Data["Reponame"] = c.Repo.Repository.Name - c.HTML(200, COMMITS) -} - -func Commits(c *context.Context) { - renderCommits(c, "") -} - -func SearchCommits(c *context.Context) { - c.Data["PageIsCommits"] = true - - keyword := c.Query("q") - if len(keyword) == 0 { - c.Redirect(c.Repo.RepoLink + "/commits/" + c.Repo.BranchName) - return - } - - commits, err := c.Repo.Commit.SearchCommits(keyword) - if err != nil { - c.Handle(500, "SearchCommits", err) - return - } - commits = RenderIssueLinks(commits, c.Repo.RepoLink) - commits = models.ValidateCommitsWithEmails(commits) - c.Data["Commits"] = commits - - c.Data["Keyword"] = keyword - c.Data["Username"] = c.Repo.Owner.Name - c.Data["Reponame"] = c.Repo.Repository.Name - c.Data["Branch"] = c.Repo.BranchName - c.HTML(200, COMMITS) -} - -func FileHistory(c *context.Context) { - renderCommits(c, c.Repo.TreePath) -} - -func Diff(c *context.Context) { - c.Data["PageIsDiff"] = true - c.Data["RequireHighlightJS"] = true - - userName := c.Repo.Owner.Name - repoName := c.Repo.Repository.Name - commitID := c.Params(":sha") - - commit, err := c.Repo.GitRepo.GetCommit(commitID) - if err != nil { - if git.IsErrNotExist(err) { - c.Handle(404, "Repo.GitRepo.GetCommit", err) - } else { - c.Handle(500, "Repo.GitRepo.GetCommit", err) - } - return - } - - diff, err := models.GetDiffCommit(models.RepoPath(userName, repoName), - commitID, setting.Git.MaxGitDiffLines, - setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) - if err != nil { - c.NotFoundOrServerError("GetDiffCommit", git.IsErrNotExist, 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 { - c.Handle(404, "repo.Diff", err) - return - } - } - - setEditorconfigIfExists(c) - if c.Written() { - return - } - - c.Data["CommitID"] = commitID - c.Data["IsSplitStyle"] = c.Query("style") == "split" - c.Data["Username"] = userName - c.Data["Reponame"] = repoName - c.Data["IsImageFile"] = commit.IsImageFile - c.Data["Title"] = commit.Summary() + " · " + tool.ShortSHA1(commitID) - c.Data["Commit"] = commit - c.Data["Author"] = models.ValidateCommitWithEmail(commit) - c.Data["Diff"] = diff - c.Data["Parents"] = parents - c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 - c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", commitID) - if commit.ParentCount() > 0 { - c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", parents[0]) - } - c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", commitID) - c.HTML(200, DIFF) -} - -func RawDiff(c *context.Context) { - if err := git.GetRawDiff( - models.RepoPath(c.Repo.Owner.Name, c.Repo.Repository.Name), - c.Params(":sha"), - git.RawDiffType(c.Params(":ext")), - c.Resp, - ); err != nil { - c.NotFoundOrServerError("GetRawDiff", git.IsErrNotExist, err) - return - } -} - -func CompareDiff(c *context.Context) { - c.Data["IsDiffCompare"] = true - userName := c.Repo.Owner.Name - repoName := c.Repo.Repository.Name - beforeCommitID := c.Params(":before") - afterCommitID := c.Params(":after") - - commit, err := c.Repo.GitRepo.GetCommit(afterCommitID) - if err != nil { - c.Handle(404, "GetCommit", err) - return - } - - diff, err := models.GetDiffRange(models.RepoPath(userName, repoName), beforeCommitID, - afterCommitID, setting.Git.MaxGitDiffLines, - setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) - if err != nil { - c.Handle(404, "GetDiffRange", err) - return - } - - commits, err := commit.CommitsBeforeUntil(beforeCommitID) - if err != nil { - c.Handle(500, "CommitsBeforeUntil", err) - return - } - commits = models.ValidateCommitsWithEmails(commits) - - c.Data["IsSplitStyle"] = c.Query("style") == "split" - c.Data["CommitRepoLink"] = c.Repo.RepoLink - c.Data["Commits"] = commits - c.Data["CommitsCount"] = commits.Len() - c.Data["BeforeCommitID"] = beforeCommitID - c.Data["AfterCommitID"] = afterCommitID - c.Data["Username"] = userName - c.Data["Reponame"] = repoName - c.Data["IsImageFile"] = commit.IsImageFile - c.Data["Title"] = "Comparing " + tool.ShortSHA1(beforeCommitID) + "..." + tool.ShortSHA1(afterCommitID) + " · " + userName + "/" + repoName - c.Data["Commit"] = commit - c.Data["Diff"] = diff - c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 - c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", afterCommitID) - c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", beforeCommitID) - c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", afterCommitID) - c.HTML(200, DIFF) -} diff --git a/routers/repo/download.go b/routers/repo/download.go deleted file mode 100644 index e9a29989..00000000 --- a/routers/repo/download.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "io" - "path" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/pkg/tool" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" -) - -func ServeData(c *context.Context, name string, reader io.Reader) error { - buf := make([]byte, 1024) - n, _ := reader.Read(buf) - if n >= 0 { - buf = buf[:n] - } - - if !tool.IsTextFile(buf) { - if !tool.IsImageFile(buf) { - c.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"") - c.Resp.Header().Set("Content-Transfer-Encoding", "binary") - } - } else if !setting.Repository.EnableRawFileRenderMode || !c.QueryBool("render") { - c.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") - } - c.Resp.Write(buf) - _, err := io.Copy(c.Resp, reader) - return err -} - -func ServeBlob(c *context.Context, blob *git.Blob) error { - dataRc, err := blob.Data() - if err != nil { - return err - } - - return ServeData(c, path.Base(c.Repo.TreePath), dataRc) -} - -func SingleDownload(c *context.Context) { - blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath) - if err != nil { - if git.IsErrNotExist(err) { - c.Handle(404, "GetBlobByPath", nil) - } else { - c.Handle(500, "GetBlobByPath", err) - } - return - } - if err = ServeBlob(c, blob); err != nil { - c.Handle(500, "ServeBlob", err) - } -} diff --git a/routers/repo/editor.go b/routers/repo/editor.go deleted file mode 100644 index 4cd78d70..00000000 --- a/routers/repo/editor.go +++ /dev/null @@ -1,571 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "fmt" - "io/ioutil" - "net/http" - "path" - "strings" - - log "gopkg.in/clog.v1" - - "github.com/gogits/git-module" - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/template" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - EDIT_FILE = "repo/editor/edit" - EDIT_DIFF_PREVIEW = "repo/editor/diff_preview" - DELETE_FILE = "repo/editor/delete" - UPLOAD_FILE = "repo/editor/upload" -) - -// getParentTreeFields returns list of parent tree names and corresponding tree paths -// based on given tree path. -func getParentTreeFields(treePath string) (treeNames []string, treePaths []string) { - if len(treePath) == 0 { - return treeNames, treePaths - } - - treeNames = strings.Split(treePath, "/") - treePaths = make([]string, len(treeNames)) - for i := range treeNames { - treePaths[i] = strings.Join(treeNames[:i+1], "/") - } - return treeNames, treePaths -} - -func editFile(c *context.Context, isNewFile bool) { - c.PageIs("Edit") - c.RequireHighlightJS() - c.RequireSimpleMDE() - c.Data["IsNewFile"] = isNewFile - - treeNames, treePaths := getParentTreeFields(c.Repo.TreePath) - - if !isNewFile { - entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath) - if err != nil { - c.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err) - return - } - - // No way to edit a directory online. - if entry.IsDir() { - c.NotFound() - return - } - - blob := entry.Blob() - dataRc, err := blob.Data() - if err != nil { - c.ServerError("blob.Data", err) - return - } - - c.Data["FileSize"] = blob.Size() - c.Data["FileName"] = blob.Name() - - buf := make([]byte, 1024) - n, _ := dataRc.Read(buf) - buf = buf[:n] - - // Only text file are editable online. - if !tool.IsTextFile(buf) { - c.NotFound() - return - } - - d, _ := ioutil.ReadAll(dataRc) - buf = append(buf, d...) - if err, content := template.ToUTF8WithErr(buf); err != nil { - if err != nil { - log.Error(2, "ToUTF8WithErr: %v", err) - } - c.Data["FileContent"] = string(buf) - } else { - c.Data["FileContent"] = content - } - } else { - treeNames = append(treeNames, "") // Append empty string to allow user name the new file. - } - - c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath) - c.Data["TreeNames"] = treeNames - c.Data["TreePaths"] = treePaths - c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName - c.Data["commit_summary"] = "" - c.Data["commit_message"] = "" - c.Data["commit_choice"] = "direct" - c.Data["new_branch_name"] = "" - c.Data["last_commit"] = c.Repo.Commit.ID - c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",") - c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") - c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",") - c.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", setting.AppSubURL, c.Repo.Repository.FullName()) - - c.Success(EDIT_FILE) -} - -func EditFile(c *context.Context) { - editFile(c, false) -} - -func NewFile(c *context.Context) { - editFile(c, true) -} - -func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) { - c.PageIs("Edit") - c.RequireHighlightJS() - c.RequireSimpleMDE() - c.Data["IsNewFile"] = isNewFile - - oldBranchName := c.Repo.BranchName - branchName := oldBranchName - oldTreePath := c.Repo.TreePath - lastCommit := f.LastCommit - f.LastCommit = c.Repo.Commit.ID.String() - - if f.IsNewBrnach() { - branchName = f.NewBranchName - } - - f.TreePath = strings.Trim(f.TreePath, " /") - treeNames, treePaths := getParentTreeFields(f.TreePath) - - c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath) - c.Data["TreePath"] = f.TreePath - c.Data["TreeNames"] = treeNames - c.Data["TreePaths"] = treePaths - c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName - c.Data["FileContent"] = f.Content - c.Data["commit_summary"] = f.CommitSummary - c.Data["commit_message"] = f.CommitMessage - c.Data["commit_choice"] = f.CommitChoice - c.Data["new_branch_name"] = branchName - c.Data["last_commit"] = f.LastCommit - c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",") - c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") - c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",") - - if c.HasError() { - c.Success(EDIT_FILE) - return - } - - if len(f.TreePath) == 0 { - c.FormErr("TreePath") - c.RenderWithErr(c.Tr("repo.editor.filename_cannot_be_empty"), EDIT_FILE, &f) - return - } - - if oldBranchName != branchName { - if _, err := c.Repo.Repository.GetBranch(branchName); err == nil { - c.FormErr("NewBranchName") - c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), EDIT_FILE, &f) - return - } - } - - var newTreePath string - for index, part := range treeNames { - newTreePath = path.Join(newTreePath, part) - entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath) - if err != nil { - if git.IsErrNotExist(err) { - // Means there is no item with that name, so we're good - break - } - - c.ServerError("Repo.Commit.GetTreeEntryByPath", err) - return - } - if index != len(treeNames)-1 { - if !entry.IsDir() { - c.FormErr("TreePath") - c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), EDIT_FILE, &f) - return - } - } else { - if entry.IsLink() { - c.FormErr("TreePath") - c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), EDIT_FILE, &f) - return - } else if entry.IsDir() { - c.FormErr("TreePath") - c.RenderWithErr(c.Tr("repo.editor.filename_is_a_directory", part), EDIT_FILE, &f) - return - } - } - } - - if !isNewFile { - _, err := c.Repo.Commit.GetTreeEntryByPath(oldTreePath) - if err != nil { - if git.IsErrNotExist(err) { - c.FormErr("TreePath") - c.RenderWithErr(c.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), EDIT_FILE, &f) - } else { - c.ServerError("GetTreeEntryByPath", err) - } - return - } - if lastCommit != c.Repo.CommitID { - files, err := c.Repo.Commit.GetFilesChangedSinceCommit(lastCommit) - if err != nil { - c.ServerError("GetFilesChangedSinceCommit", err) - return - } - - for _, file := range files { - if file == f.TreePath { - c.RenderWithErr(c.Tr("repo.editor.file_changed_while_editing", c.Repo.RepoLink+"/compare/"+lastCommit+"..."+c.Repo.CommitID), EDIT_FILE, &f) - return - } - } - } - } - - if oldTreePath != f.TreePath { - // We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber. - entry, err := c.Repo.Commit.GetTreeEntryByPath(f.TreePath) - if err != nil { - if !git.IsErrNotExist(err) { - c.ServerError("GetTreeEntryByPath", err) - return - } - } - if entry != nil { - c.FormErr("TreePath") - c.RenderWithErr(c.Tr("repo.editor.file_already_exists", f.TreePath), EDIT_FILE, &f) - return - } - } - - message := strings.TrimSpace(f.CommitSummary) - if len(message) == 0 { - if isNewFile { - message = c.Tr("repo.editor.add", f.TreePath) - } else { - message = c.Tr("repo.editor.update", f.TreePath) - } - } - - f.CommitMessage = strings.TrimSpace(f.CommitMessage) - if len(f.CommitMessage) > 0 { - message += "\n\n" + f.CommitMessage - } - - if err := c.Repo.Repository.UpdateRepoFile(c.User, models.UpdateRepoFileOptions{ - LastCommitID: lastCommit, - OldBranch: oldBranchName, - NewBranch: branchName, - OldTreeName: oldTreePath, - NewTreeName: f.TreePath, - Message: message, - Content: strings.Replace(f.Content, "\r", "", -1), - IsNewFile: isNewFile, - }); err != nil { - c.FormErr("TreePath") - c.RenderWithErr(c.Tr("repo.editor.fail_to_update_file", f.TreePath, err), EDIT_FILE, &f) - return - } - - if f.IsNewBrnach() && c.Repo.PullRequest.Allowed { - c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName)) - } else { - c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + template.EscapePound(f.TreePath)) - } -} - -func EditFilePost(c *context.Context, f form.EditRepoFile) { - editFilePost(c, f, false) -} - -func NewFilePost(c *context.Context, f form.EditRepoFile) { - editFilePost(c, f, true) -} - -func DiffPreviewPost(c *context.Context, f form.EditPreviewDiff) { - treePath := c.Repo.TreePath - - entry, err := c.Repo.Commit.GetTreeEntryByPath(treePath) - if err != nil { - c.Error(500, "GetTreeEntryByPath: "+err.Error()) - return - } else if entry.IsDir() { - c.Error(422) - return - } - - diff, err := c.Repo.Repository.GetDiffPreview(c.Repo.BranchName, treePath, f.Content) - if err != nil { - c.Error(500, "GetDiffPreview: "+err.Error()) - return - } - - if diff.NumFiles() == 0 { - c.PlainText(200, []byte(c.Tr("repo.editor.no_changes_to_show"))) - return - } - c.Data["File"] = diff.Files[0] - - c.HTML(200, EDIT_DIFF_PREVIEW) -} - -func DeleteFile(c *context.Context) { - c.Data["PageIsDelete"] = true - c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName - c.Data["TreePath"] = c.Repo.TreePath - c.Data["commit_summary"] = "" - c.Data["commit_message"] = "" - c.Data["commit_choice"] = "direct" - c.Data["new_branch_name"] = "" - c.HTML(200, DELETE_FILE) -} - -func DeleteFilePost(c *context.Context, f form.DeleteRepoFile) { - c.Data["PageIsDelete"] = true - c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName - c.Data["TreePath"] = c.Repo.TreePath - - oldBranchName := c.Repo.BranchName - branchName := oldBranchName - - if f.IsNewBrnach() { - branchName = f.NewBranchName - } - c.Data["commit_summary"] = f.CommitSummary - c.Data["commit_message"] = f.CommitMessage - c.Data["commit_choice"] = f.CommitChoice - c.Data["new_branch_name"] = branchName - - if c.HasError() { - c.HTML(200, DELETE_FILE) - return - } - - if oldBranchName != branchName { - if _, err := c.Repo.Repository.GetBranch(branchName); err == nil { - c.Data["Err_NewBranchName"] = true - c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), DELETE_FILE, &f) - return - } - } - - message := strings.TrimSpace(f.CommitSummary) - if len(message) == 0 { - message = c.Tr("repo.editor.delete", c.Repo.TreePath) - } - - f.CommitMessage = strings.TrimSpace(f.CommitMessage) - if len(f.CommitMessage) > 0 { - message += "\n\n" + f.CommitMessage - } - - if err := c.Repo.Repository.DeleteRepoFile(c.User, models.DeleteRepoFileOptions{ - LastCommitID: c.Repo.CommitID, - OldBranch: oldBranchName, - NewBranch: branchName, - TreePath: c.Repo.TreePath, - Message: message, - }); err != nil { - c.Handle(500, "DeleteRepoFile", err) - return - } - - if f.IsNewBrnach() && c.Repo.PullRequest.Allowed { - c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName)) - } else { - c.Flash.Success(c.Tr("repo.editor.file_delete_success", c.Repo.TreePath)) - c.Redirect(c.Repo.RepoLink + "/src/" + branchName) - } -} - -func renderUploadSettings(c *context.Context) { - c.Data["RequireDropzone"] = true - c.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",") - c.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize - c.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles -} - -func UploadFile(c *context.Context) { - c.Data["PageIsUpload"] = true - renderUploadSettings(c) - - treeNames, treePaths := getParentTreeFields(c.Repo.TreePath) - if len(treeNames) == 0 { - // We must at least have one element for user to input. - treeNames = []string{""} - } - - c.Data["TreeNames"] = treeNames - c.Data["TreePaths"] = treePaths - c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName - c.Data["commit_summary"] = "" - c.Data["commit_message"] = "" - c.Data["commit_choice"] = "direct" - c.Data["new_branch_name"] = "" - - c.HTML(200, UPLOAD_FILE) -} - -func UploadFilePost(c *context.Context, f form.UploadRepoFile) { - c.Data["PageIsUpload"] = true - renderUploadSettings(c) - - oldBranchName := c.Repo.BranchName - branchName := oldBranchName - - if f.IsNewBrnach() { - branchName = f.NewBranchName - } - - f.TreePath = strings.Trim(f.TreePath, " /") - treeNames, treePaths := getParentTreeFields(f.TreePath) - if len(treeNames) == 0 { - // We must at least have one element for user to input. - treeNames = []string{""} - } - - c.Data["TreePath"] = f.TreePath - c.Data["TreeNames"] = treeNames - c.Data["TreePaths"] = treePaths - c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName - c.Data["commit_summary"] = f.CommitSummary - c.Data["commit_message"] = f.CommitMessage - c.Data["commit_choice"] = f.CommitChoice - c.Data["new_branch_name"] = branchName - - if c.HasError() { - c.HTML(200, UPLOAD_FILE) - return - } - - if oldBranchName != branchName { - if _, err := c.Repo.Repository.GetBranch(branchName); err == nil { - c.Data["Err_NewBranchName"] = true - c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), UPLOAD_FILE, &f) - return - } - } - - var newTreePath string - for _, part := range treeNames { - newTreePath = path.Join(newTreePath, part) - entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath) - if err != nil { - if git.IsErrNotExist(err) { - // Means there is no item with that name, so we're good - break - } - - c.Handle(500, "Repo.Commit.GetTreeEntryByPath", err) - return - } - - // User can only upload files to a directory. - if !entry.IsDir() { - c.Data["Err_TreePath"] = true - c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), UPLOAD_FILE, &f) - return - } - } - - message := strings.TrimSpace(f.CommitSummary) - if len(message) == 0 { - message = c.Tr("repo.editor.upload_files_to_dir", f.TreePath) - } - - f.CommitMessage = strings.TrimSpace(f.CommitMessage) - if len(f.CommitMessage) > 0 { - message += "\n\n" + f.CommitMessage - } - - if err := c.Repo.Repository.UploadRepoFiles(c.User, models.UploadRepoFileOptions{ - LastCommitID: c.Repo.CommitID, - OldBranch: oldBranchName, - NewBranch: branchName, - TreePath: f.TreePath, - Message: message, - Files: f.Files, - }); err != nil { - c.Data["Err_TreePath"] = true - c.RenderWithErr(c.Tr("repo.editor.unable_to_upload_files", f.TreePath, err), UPLOAD_FILE, &f) - return - } - - if f.IsNewBrnach() && c.Repo.PullRequest.Allowed { - c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName)) - } else { - c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath) - } -} - -func UploadFileToServer(c *context.Context) { - file, header, err := c.Req.FormFile("file") - if err != nil { - c.Error(500, fmt.Sprintf("FormFile: %v", err)) - return - } - defer file.Close() - - buf := make([]byte, 1024) - n, _ := file.Read(buf) - if n > 0 { - buf = buf[:n] - } - fileType := http.DetectContentType(buf) - - if len(setting.Repository.Upload.AllowedTypes) > 0 { - allowed := false - for _, t := range setting.Repository.Upload.AllowedTypes { - t := strings.Trim(t, " ") - if t == "*/*" || t == fileType { - allowed = true - break - } - } - - if !allowed { - c.Error(400, ErrFileTypeForbidden.Error()) - return - } - } - - upload, err := models.NewUpload(header.Filename, buf, file) - if err != nil { - c.Error(500, fmt.Sprintf("NewUpload: %v", err)) - return - } - - log.Trace("New file uploaded: %s", upload.UUID) - c.JSON(200, map[string]string{ - "uuid": upload.UUID, - }) -} - -func RemoveUploadFileFromServer(c *context.Context, f form.RemoveUploadFile) { - if len(f.File) == 0 { - c.Status(204) - return - } - - if err := models.DeleteUploadByUUID(f.File); err != nil { - c.Error(500, fmt.Sprintf("DeleteUploadByUUID: %v", err)) - return - } - - log.Trace("Upload file removed: %s", f.File) - c.Status(204) -} diff --git a/routers/repo/http.go b/routers/repo/http.go deleted file mode 100644 index b8f519ba..00000000 --- a/routers/repo/http.go +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "bytes" - "compress/gzip" - "fmt" - "net/http" - "os" - "os/exec" - "path" - "regexp" - "strconv" - "strings" - "time" - - "github.com/Unknwon/com" - log "gopkg.in/clog.v1" - "gopkg.in/macaron.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - ENV_AUTH_USER_ID = "GOGS_AUTH_USER_ID" - ENV_AUTH_USER_NAME = "GOGS_AUTH_USER_NAME" - ENV_AUTH_USER_EMAIL = "GOGS_AUTH_USER_EMAIL" - ENV_REPO_OWNER_NAME = "GOGS_REPO_OWNER_NAME" - ENV_REPO_OWNER_SALT_MD5 = "GOGS_REPO_OWNER_SALT_MD5" - ENV_REPO_ID = "GOGS_REPO_ID" - ENV_REPO_NAME = "GOGS_REPO_NAME" - ENV_REPO_CUSTOM_HOOKS_PATH = "GOGS_REPO_CUSTOM_HOOKS_PATH" -) - -type HTTPContext struct { - *context.Context - OwnerName string - OwnerSalt string - RepoID int64 - RepoName string - AuthUser *models.User -} - -// askCredentials responses HTTP header and status which informs client to provide credentials. -func askCredentials(c *context.Context, status int, text string) { - c.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"") - c.HandleText(status, text) -} - -func HTTPContexter() macaron.Handler { - return func(c *context.Context) { - ownerName := c.Params(":username") - repoName := strings.TrimSuffix(c.Params(":reponame"), ".git") - repoName = strings.TrimSuffix(repoName, ".wiki") - - isPull := c.Query("service") == "git-upload-pack" || - strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") || - c.Req.Method == "GET" - - owner, err := models.GetUserByName(ownerName) - if err != nil { - c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) - return - } - - repo, err := models.GetRepositoryByName(owner.ID, repoName) - if err != nil { - c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err) - return - } - - // Authentication is not required for pulling from public repositories. - if isPull && !repo.IsPrivate && !setting.Service.RequireSignInView { - c.Map(&HTTPContext{ - Context: c, - }) - return - } - - // In case user requested a wrong URL and not intended to access Git objects. - action := c.Params("*") - if !strings.Contains(action, "git-") && - !strings.Contains(action, "info/") && - !strings.Contains(action, "HEAD") && - !strings.Contains(action, "objects/") { - c.NotFound() - return - } - - // Handle HTTP Basic Authentication - authHead := c.Req.Header.Get("Authorization") - if len(authHead) == 0 { - askCredentials(c, http.StatusUnauthorized, "") - return - } - - auths := strings.Fields(authHead) - if len(auths) != 2 || auths[0] != "Basic" { - askCredentials(c, http.StatusUnauthorized, "") - return - } - authUsername, authPassword, err := tool.BasicAuthDecode(auths[1]) - if err != nil { - askCredentials(c, http.StatusUnauthorized, "") - return - } - - authUser, err := models.UserSignIn(authUsername, authPassword) - if err != nil && !errors.IsUserNotExist(err) { - c.Handle(http.StatusInternalServerError, "UserSignIn", err) - return - } - - // If username and password combination failed, try again using username as a token. - if authUser == nil { - token, err := models.GetAccessTokenBySHA(authUsername) - if err != nil { - if models.IsErrAccessTokenEmpty(err) || models.IsErrAccessTokenNotExist(err) { - askCredentials(c, http.StatusUnauthorized, "") - } else { - c.Handle(http.StatusInternalServerError, "GetAccessTokenBySHA", err) - } - return - } - token.Updated = time.Now() - - authUser, err = models.GetUserByID(token.UID) - if err != nil { - // Once we found token, we're supposed to find its related user, - // thus any error is unexpected. - c.Handle(http.StatusInternalServerError, "GetUserByID", err) - return - } - } else if authUser.IsEnabledTwoFactor() { - askCredentials(c, http.StatusUnauthorized, `User with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password -Please create and use personal access token on user settings page`) - return - } - - log.Trace("HTTPGit - Authenticated user: %s", authUser.Name) - - mode := models.ACCESS_MODE_WRITE - if isPull { - mode = models.ACCESS_MODE_READ - } - has, err := models.HasAccess(authUser.ID, repo, mode) - if err != nil { - c.Handle(http.StatusInternalServerError, "HasAccess", err) - return - } else if !has { - askCredentials(c, http.StatusForbidden, "User permission denied") - return - } - - if !isPull && repo.IsMirror { - c.HandleText(http.StatusForbidden, "Mirror repository is read-only") - return - } - - c.Map(&HTTPContext{ - Context: c, - OwnerName: ownerName, - OwnerSalt: owner.Salt, - RepoID: repo.ID, - RepoName: repoName, - AuthUser: authUser, - }) - } -} - -type serviceHandler struct { - w http.ResponseWriter - r *http.Request - dir string - file string - - authUser *models.User - ownerName string - ownerSalt string - repoID int64 - repoName string -} - -func (h *serviceHandler) setHeaderNoCache() { - h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT") - h.w.Header().Set("Pragma", "no-cache") - h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") -} - -func (h *serviceHandler) setHeaderCacheForever() { - now := time.Now().Unix() - expires := now + 31536000 - h.w.Header().Set("Date", fmt.Sprintf("%d", now)) - h.w.Header().Set("Expires", fmt.Sprintf("%d", expires)) - h.w.Header().Set("Cache-Control", "public, max-age=31536000") -} - -func (h *serviceHandler) sendFile(contentType string) { - reqFile := path.Join(h.dir, h.file) - fi, err := os.Stat(reqFile) - if os.IsNotExist(err) { - h.w.WriteHeader(http.StatusNotFound) - return - } - - h.w.Header().Set("Content-Type", contentType) - h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) - h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat)) - http.ServeFile(h.w, h.r, reqFile) -} - -type ComposeHookEnvsOptions struct { - AuthUser *models.User - OwnerName string - OwnerSalt string - RepoID int64 - RepoName string - RepoPath string -} - -func ComposeHookEnvs(opts ComposeHookEnvsOptions) []string { - envs := []string{ - "SSH_ORIGINAL_COMMAND=1", - ENV_AUTH_USER_ID + "=" + com.ToStr(opts.AuthUser.ID), - ENV_AUTH_USER_NAME + "=" + opts.AuthUser.Name, - ENV_AUTH_USER_EMAIL + "=" + opts.AuthUser.Email, - ENV_REPO_OWNER_NAME + "=" + opts.OwnerName, - ENV_REPO_OWNER_SALT_MD5 + "=" + tool.MD5(opts.OwnerSalt), - ENV_REPO_ID + "=" + com.ToStr(opts.RepoID), - ENV_REPO_NAME + "=" + opts.RepoName, - ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(opts.RepoPath, "custom_hooks"), - } - return envs -} - -func serviceRPC(h serviceHandler, service string) { - defer h.r.Body.Close() - - if h.r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", service) { - h.w.WriteHeader(http.StatusUnauthorized) - return - } - h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service)) - - var ( - reqBody = h.r.Body - err error - ) - - // Handle GZIP - if h.r.Header.Get("Content-Encoding") == "gzip" { - reqBody, err = gzip.NewReader(reqBody) - if err != nil { - log.Error(2, "HTTP.Get: fail to create gzip reader: %v", err) - h.w.WriteHeader(http.StatusInternalServerError) - return - } - } - - var stderr bytes.Buffer - cmd := exec.Command("git", service, "--stateless-rpc", h.dir) - if service == "receive-pack" { - cmd.Env = append(os.Environ(), ComposeHookEnvs(ComposeHookEnvsOptions{ - AuthUser: h.authUser, - OwnerName: h.ownerName, - OwnerSalt: h.ownerSalt, - RepoID: h.repoID, - RepoName: h.repoName, - RepoPath: h.dir, - })...) - } - cmd.Dir = h.dir - cmd.Stdout = h.w - cmd.Stderr = &stderr - cmd.Stdin = reqBody - if err = cmd.Run(); err != nil { - log.Error(2, "HTTP.serviceRPC: fail to serve RPC '%s': %v - %s", service, err, stderr) - h.w.WriteHeader(http.StatusInternalServerError) - return - } -} - -func serviceUploadPack(h serviceHandler) { - serviceRPC(h, "upload-pack") -} - -func serviceReceivePack(h serviceHandler) { - serviceRPC(h, "receive-pack") -} - -func getServiceType(r *http.Request) string { - serviceType := r.FormValue("service") - if !strings.HasPrefix(serviceType, "git-") { - return "" - } - return strings.TrimPrefix(serviceType, "git-") -} - -// FIXME: use process module -func gitCommand(dir string, args ...string) []byte { - cmd := exec.Command("git", args...) - cmd.Dir = dir - out, err := cmd.Output() - if err != nil { - log.Error(2, fmt.Sprintf("Git: %v - %s", err, out)) - } - return out -} - -func updateServerInfo(dir string) []byte { - return gitCommand(dir, "update-server-info") -} - -func packetWrite(str string) []byte { - s := strconv.FormatInt(int64(len(str)+4), 16) - if len(s)%4 != 0 { - s = strings.Repeat("0", 4-len(s)%4) + s - } - return []byte(s + str) -} - -func getInfoRefs(h serviceHandler) { - h.setHeaderNoCache() - service := getServiceType(h.r) - if service != "upload-pack" && service != "receive-pack" { - updateServerInfo(h.dir) - h.sendFile("text/plain; charset=utf-8") - return - } - - refs := gitCommand(h.dir, service, "--stateless-rpc", "--advertise-refs", ".") - h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service)) - h.w.WriteHeader(http.StatusOK) - h.w.Write(packetWrite("# service=git-" + service + "\n")) - h.w.Write([]byte("0000")) - h.w.Write(refs) -} - -func getTextFile(h serviceHandler) { - h.setHeaderNoCache() - h.sendFile("text/plain") -} - -func getInfoPacks(h serviceHandler) { - h.setHeaderCacheForever() - h.sendFile("text/plain; charset=utf-8") -} - -func getLooseObject(h serviceHandler) { - h.setHeaderCacheForever() - h.sendFile("application/x-git-loose-object") -} - -func getPackFile(h serviceHandler) { - h.setHeaderCacheForever() - h.sendFile("application/x-git-packed-objects") -} - -func getIdxFile(h serviceHandler) { - h.setHeaderCacheForever() - h.sendFile("application/x-git-packed-objects-toc") -} - -var routes = []struct { - reg *regexp.Regexp - method string - handler func(serviceHandler) -}{ - {regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack}, - {regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack}, - {regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs}, - {regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile}, - {regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile}, - {regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile}, - {regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks}, - {regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile}, - {regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject}, - {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile}, - {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile}, -} - -func getGitRepoPath(dir string) (string, error) { - if !strings.HasSuffix(dir, ".git") { - dir += ".git" - } - - filename := path.Join(setting.RepoRootPath, dir) - if _, err := os.Stat(filename); os.IsNotExist(err) { - return "", err - } - - return filename, nil -} - -func HTTP(c *HTTPContext) { - for _, route := range routes { - reqPath := strings.ToLower(c.Req.URL.Path) - m := route.reg.FindStringSubmatch(reqPath) - if m == nil { - continue - } - - // We perform check here because routes matched in cmd/web.go is wider than needed, - // but we only want to output this message only if user is really trying to access - // Git HTTP endpoints. - if setting.Repository.DisableHTTPGit { - c.HandleText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is not disabled") - return - } - - if route.method != c.Req.Method { - c.NotFound() - return - } - - file := strings.TrimPrefix(reqPath, m[1]+"/") - dir, err := getGitRepoPath(m[1]) - if err != nil { - log.Warn("HTTP.getGitRepoPath: %v", err) - c.NotFound() - return - } - - route.handler(serviceHandler{ - w: c.Resp, - r: c.Req.Request, - dir: dir, - file: file, - - authUser: c.AuthUser, - ownerName: c.OwnerName, - ownerSalt: c.OwnerSalt, - repoID: c.RepoID, - repoName: c.RepoName, - }) - return - } - - c.NotFound() -} diff --git a/routers/repo/issue.go b/routers/repo/issue.go deleted file mode 100644 index 8920bc32..00000000 --- a/routers/repo/issue.go +++ /dev/null @@ -1,1263 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" - - "github.com/Unknwon/com" - "github.com/Unknwon/paginater" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/markup" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - ISSUES = "repo/issue/list" - ISSUE_NEW = "repo/issue/new" - ISSUE_VIEW = "repo/issue/view" - - LABELS = "repo/issue/labels" - - MILESTONE = "repo/issue/milestones" - MILESTONE_NEW = "repo/issue/milestone_new" - MILESTONE_EDIT = "repo/issue/milestone_edit" - - ISSUE_TEMPLATE_KEY = "IssueTemplate" -) - -var ( - ErrFileTypeForbidden = errors.New("File type is not allowed") - ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded") - - IssueTemplateCandidates = []string{ - "ISSUE_TEMPLATE.md", - ".gogs/ISSUE_TEMPLATE.md", - ".github/ISSUE_TEMPLATE.md", - } -) - -func MustEnableIssues(c *context.Context) { - if !c.Repo.Repository.EnableIssues { - c.Handle(404, "MustEnableIssues", nil) - return - } - - if c.Repo.Repository.EnableExternalTracker { - c.Redirect(c.Repo.Repository.ExternalTrackerURL) - return - } -} - -func MustAllowPulls(c *context.Context) { - if !c.Repo.Repository.AllowsPulls() { - c.Handle(404, "MustAllowPulls", nil) - return - } - - // User can send pull request if owns a forked repository. - if c.IsLogged && c.User.HasForkedRepo(c.Repo.Repository.ID) { - c.Repo.PullRequest.Allowed = true - c.Repo.PullRequest.HeadInfo = c.User.Name + ":" + c.Repo.BranchName - } -} - -func RetrieveLabels(c *context.Context) { - labels, err := models.GetLabelsByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Handle(500, "RetrieveLabels.GetLabels", err) - return - } - for _, l := range labels { - l.CalOpenIssues() - } - c.Data["Labels"] = labels - c.Data["NumLabels"] = len(labels) -} - -func issues(c *context.Context, isPullList bool) { - if isPullList { - MustAllowPulls(c) - if c.Written() { - return - } - c.Data["Title"] = c.Tr("repo.pulls") - c.Data["PageIsPullList"] = true - - } else { - MustEnableIssues(c) - if c.Written() { - return - } - c.Data["Title"] = c.Tr("repo.issues") - c.Data["PageIsIssueList"] = true - } - - viewType := c.Query("type") - sortType := c.Query("sort") - types := []string{"assigned", "created_by", "mentioned"} - if !com.IsSliceContainsStr(types, viewType) { - viewType = "all" - } - - // Must sign in to see issues about you. - if viewType != "all" && !c.IsLogged { - c.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubURL+c.Req.RequestURI), 0, setting.AppSubURL) - c.Redirect(setting.AppSubURL + "/user/login") - return - } - - var ( - assigneeID = c.QueryInt64("assignee") - posterID int64 - ) - filterMode := models.FILTER_MODE_YOUR_REPOS - switch viewType { - case "assigned": - filterMode = models.FILTER_MODE_ASSIGN - assigneeID = c.User.ID - case "created_by": - filterMode = models.FILTER_MODE_CREATE - posterID = c.User.ID - case "mentioned": - filterMode = models.FILTER_MODE_MENTION - } - - var uid int64 = -1 - if c.IsLogged { - uid = c.User.ID - } - - repo := c.Repo.Repository - selectLabels := c.Query("labels") - milestoneID := c.QueryInt64("milestone") - isShowClosed := c.Query("state") == "closed" - issueStats := models.GetIssueStats(&models.IssueStatsOptions{ - RepoID: repo.ID, - UserID: uid, - Labels: selectLabels, - MilestoneID: milestoneID, - AssigneeID: assigneeID, - FilterMode: filterMode, - IsPull: isPullList, - }) - - page := c.QueryInt("page") - if page <= 1 { - page = 1 - } - - var total int - if !isShowClosed { - total = int(issueStats.OpenCount) - } else { - total = int(issueStats.ClosedCount) - } - pager := paginater.New(total, setting.UI.IssuePagingNum, page, 5) - c.Data["Page"] = pager - - issues, err := models.Issues(&models.IssuesOptions{ - UserID: uid, - AssigneeID: assigneeID, - RepoID: repo.ID, - PosterID: posterID, - MilestoneID: milestoneID, - Page: pager.Current(), - IsClosed: isShowClosed, - IsMention: filterMode == models.FILTER_MODE_MENTION, - IsPull: isPullList, - Labels: selectLabels, - SortType: sortType, - }) - if err != nil { - c.Handle(500, "Issues", err) - return - } - - // Get issue-user relations. - pairs, err := models.GetIssueUsers(repo.ID, posterID, isShowClosed) - if err != nil { - c.Handle(500, "GetIssueUsers", err) - return - } - - // Get posters. - for i := range issues { - if !c.IsLogged { - issues[i].IsRead = true - continue - } - - // Check read status. - idx := models.PairsContains(pairs, issues[i].ID, c.User.ID) - if idx > -1 { - issues[i].IsRead = pairs[idx].IsRead - } else { - issues[i].IsRead = true - } - } - c.Data["Issues"] = issues - - // Get milestones. - c.Data["Milestones"], err = models.GetMilestonesByRepoID(repo.ID) - if err != nil { - c.Handle(500, "GetAllRepoMilestones", err) - return - } - - // Get assignees. - c.Data["Assignees"], err = repo.GetAssignees() - if err != nil { - c.Handle(500, "GetAssignees", err) - return - } - - if viewType == "assigned" { - assigneeID = 0 // Reset ID to prevent unexpected selection of assignee. - } - - c.Data["IssueStats"] = issueStats - c.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64() - c.Data["ViewType"] = viewType - c.Data["SortType"] = sortType - c.Data["MilestoneID"] = milestoneID - c.Data["AssigneeID"] = assigneeID - c.Data["IsShowClosed"] = isShowClosed - if isShowClosed { - c.Data["State"] = "closed" - } else { - c.Data["State"] = "open" - } - - c.HTML(200, ISSUES) -} - -func Issues(c *context.Context) { - issues(c, false) -} - -func Pulls(c *context.Context) { - issues(c, true) -} - -func renderAttachmentSettings(c *context.Context) { - c.Data["RequireDropzone"] = true - c.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled - c.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes - c.Data["AttachmentMaxSize"] = setting.AttachmentMaxSize - c.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles -} - -func RetrieveRepoMilestonesAndAssignees(c *context.Context, repo *models.Repository) { - var err error - c.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false) - if err != nil { - c.Handle(500, "GetMilestones", err) - return - } - c.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true) - if err != nil { - c.Handle(500, "GetMilestones", err) - return - } - - c.Data["Assignees"], err = repo.GetAssignees() - if err != nil { - c.Handle(500, "GetAssignees", err) - return - } -} - -func RetrieveRepoMetas(c *context.Context, repo *models.Repository) []*models.Label { - if !c.Repo.IsWriter() { - return nil - } - - labels, err := models.GetLabelsByRepoID(repo.ID) - if err != nil { - c.Handle(500, "GetLabelsByRepoID", err) - return nil - } - c.Data["Labels"] = labels - - RetrieveRepoMilestonesAndAssignees(c, repo) - if c.Written() { - return nil - } - - return labels -} - -func getFileContentFromDefaultBranch(c *context.Context, filename string) (string, bool) { - var r io.Reader - var bytes []byte - - if c.Repo.Commit == nil { - var err error - c.Repo.Commit, err = c.Repo.GitRepo.GetBranchCommit(c.Repo.Repository.DefaultBranch) - if err != nil { - return "", false - } - } - - entry, err := c.Repo.Commit.GetTreeEntryByPath(filename) - if err != nil { - return "", false - } - r, err = entry.Blob().Data() - if err != nil { - return "", false - } - bytes, err = ioutil.ReadAll(r) - if err != nil { - return "", false - } - return string(bytes), true -} - -func setTemplateIfExists(c *context.Context, ctxDataKey string, possibleFiles []string) { - for _, filename := range possibleFiles { - content, found := getFileContentFromDefaultBranch(c, filename) - if found { - c.Data[ctxDataKey] = content - return - } - } -} - -func NewIssue(c *context.Context) { - c.Data["Title"] = c.Tr("repo.issues.new") - c.Data["PageIsIssueList"] = true - c.Data["RequireHighlightJS"] = true - c.Data["RequireSimpleMDE"] = true - setTemplateIfExists(c, ISSUE_TEMPLATE_KEY, IssueTemplateCandidates) - renderAttachmentSettings(c) - - RetrieveRepoMetas(c, c.Repo.Repository) - if c.Written() { - return - } - - c.HTML(200, ISSUE_NEW) -} - -func ValidateRepoMetas(c *context.Context, f form.NewIssue) ([]int64, int64, int64) { - var ( - repo = c.Repo.Repository - err error - ) - - labels := RetrieveRepoMetas(c, c.Repo.Repository) - if c.Written() { - return nil, 0, 0 - } - - if !c.Repo.IsWriter() { - return nil, 0, 0 - } - - // Check labels. - labelIDs := tool.StringsToInt64s(strings.Split(f.LabelIDs, ",")) - labelIDMark := tool.Int64sToMap(labelIDs) - hasSelected := false - for i := range labels { - if labelIDMark[labels[i].ID] { - labels[i].IsChecked = true - hasSelected = true - } - } - c.Data["HasSelectedLabel"] = hasSelected - c.Data["label_ids"] = f.LabelIDs - c.Data["Labels"] = labels - - // Check milestone. - milestoneID := f.MilestoneID - if milestoneID > 0 { - c.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID) - if err != nil { - c.Handle(500, "GetMilestoneByID", err) - return nil, 0, 0 - } - c.Data["milestone_id"] = milestoneID - } - - // Check assignee. - assigneeID := f.AssigneeID - if assigneeID > 0 { - c.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID) - if err != nil { - c.Handle(500, "GetAssigneeByID", err) - return nil, 0, 0 - } - c.Data["assignee_id"] = assigneeID - } - - return labelIDs, milestoneID, assigneeID -} - -func NewIssuePost(c *context.Context, f form.NewIssue) { - c.Data["Title"] = c.Tr("repo.issues.new") - c.Data["PageIsIssueList"] = true - c.Data["RequireHighlightJS"] = true - c.Data["RequireSimpleMDE"] = true - renderAttachmentSettings(c) - - labelIDs, milestoneID, assigneeID := ValidateRepoMetas(c, f) - if c.Written() { - return - } - - if c.HasError() { - c.HTML(200, ISSUE_NEW) - return - } - - var attachments []string - if setting.AttachmentEnabled { - attachments = f.Files - } - - issue := &models.Issue{ - RepoID: c.Repo.Repository.ID, - Title: f.Title, - PosterID: c.User.ID, - Poster: c.User, - MilestoneID: milestoneID, - AssigneeID: assigneeID, - Content: f.Content, - } - if err := models.NewIssue(c.Repo.Repository, issue, labelIDs, attachments); err != nil { - c.Handle(500, "NewIssue", err) - return - } - - log.Trace("Issue created: %d/%d", c.Repo.Repository.ID, issue.ID) - c.Redirect(c.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) -} - -func uploadAttachment(c *context.Context, allowedTypes []string) { - file, header, err := c.Req.FormFile("file") - if err != nil { - c.Error(500, fmt.Sprintf("FormFile: %v", err)) - return - } - defer file.Close() - - buf := make([]byte, 1024) - n, _ := file.Read(buf) - if n > 0 { - buf = buf[:n] - } - fileType := http.DetectContentType(buf) - - allowed := false - for _, t := range allowedTypes { - t := strings.Trim(t, " ") - if t == "*/*" || t == fileType { - allowed = true - break - } - } - - if !allowed { - c.Error(400, ErrFileTypeForbidden.Error()) - return - } - - attach, err := models.NewAttachment(header.Filename, buf, file) - if err != nil { - c.Error(500, fmt.Sprintf("NewAttachment: %v", err)) - return - } - - log.Trace("New attachment uploaded: %s", attach.UUID) - c.JSON(200, map[string]string{ - "uuid": attach.UUID, - }) -} - -func UploadIssueAttachment(c *context.Context) { - if !setting.AttachmentEnabled { - c.NotFound() - return - } - - uploadAttachment(c, strings.Split(setting.AttachmentAllowedTypes, ",")) -} - -func viewIssue(c *context.Context, isPullList bool) { - c.Data["RequireHighlightJS"] = true - c.Data["RequireDropzone"] = true - renderAttachmentSettings(c) - - index := c.ParamsInt64(":index") - if index <= 0 { - c.NotFound() - return - } - - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, index) - if err != nil { - c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err) - return - } - c.Data["Title"] = issue.Title - - // Make sure type and URL matches. - if !isPullList && issue.IsPull { - c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) - return - } else if isPullList && !issue.IsPull { - c.Redirect(c.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) - return - } - - if issue.IsPull { - MustAllowPulls(c) - if c.Written() { - return - } - c.Data["PageIsPullList"] = true - c.Data["PageIsPullConversation"] = true - } else { - MustEnableIssues(c) - if c.Written() { - return - } - c.Data["PageIsIssueList"] = true - } - - issue.RenderedContent = string(markup.Markdown(issue.Content, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) - - repo := c.Repo.Repository - - // Get more information if it's a pull request. - if issue.IsPull { - if issue.PullRequest.HasMerged { - c.Data["DisableStatusChange"] = issue.PullRequest.HasMerged - PrepareMergedViewPullInfo(c, issue) - } else { - PrepareViewPullInfo(c, issue) - } - if c.Written() { - return - } - } - - // Metas. - // Check labels. - labelIDMark := make(map[int64]bool) - for i := range issue.Labels { - labelIDMark[issue.Labels[i].ID] = true - } - labels, err := models.GetLabelsByRepoID(repo.ID) - if err != nil { - c.Handle(500, "GetLabelsByRepoID", err) - return - } - hasSelected := false - for i := range labels { - if labelIDMark[labels[i].ID] { - labels[i].IsChecked = true - hasSelected = true - } - } - c.Data["HasSelectedLabel"] = hasSelected - c.Data["Labels"] = labels - - // Check milestone and assignee. - if c.Repo.IsWriter() { - RetrieveRepoMilestonesAndAssignees(c, repo) - if c.Written() { - return - } - } - - if c.IsLogged { - // Update issue-user. - if err = issue.ReadBy(c.User.ID); err != nil { - c.Handle(500, "ReadBy", err) - return - } - } - - var ( - tag models.CommentTag - ok bool - marked = make(map[int64]models.CommentTag) - comment *models.Comment - participants = make([]*models.User, 1, 10) - ) - - // Render comments and and fetch participants. - participants[0] = issue.Poster - for _, comment = range issue.Comments { - if comment.Type == models.COMMENT_TYPE_COMMENT { - comment.RenderedContent = string(markup.Markdown(comment.Content, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) - - // Check tag. - tag, ok = marked[comment.PosterID] - if ok { - comment.ShowTag = tag - continue - } - - if repo.IsOwnedBy(comment.PosterID) || - (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) { - comment.ShowTag = models.COMMENT_TAG_OWNER - } else if comment.Poster.IsWriterOfRepo(repo) { - comment.ShowTag = models.COMMENT_TAG_WRITER - } else if comment.PosterID == issue.PosterID { - comment.ShowTag = models.COMMENT_TAG_POSTER - } - - marked[comment.PosterID] = comment.ShowTag - - isAdded := false - for j := range participants { - if comment.Poster == participants[j] { - isAdded = true - break - } - } - if !isAdded && !issue.IsPoster(comment.Poster.ID) { - participants = append(participants, comment.Poster) - } - } - } - - if issue.IsPull && issue.PullRequest.HasMerged { - pull := issue.PullRequest - c.Data["IsPullBranchDeletable"] = pull.BaseRepoID == pull.HeadRepoID && - c.Repo.IsWriter() && c.Repo.GitRepo.IsBranchExist(pull.HeadBranch) - - deleteBranchUrl := c.Repo.RepoLink + "/branches/delete/" + pull.HeadBranch - c.Data["DeleteBranchLink"] = fmt.Sprintf("%s?commit=%s&redirect_to=%s", deleteBranchUrl, pull.MergedCommitID, c.Data["Link"]) - } - - c.Data["Participants"] = participants - c.Data["NumParticipants"] = len(participants) - c.Data["Issue"] = issue - c.Data["IsIssueOwner"] = c.Repo.IsWriter() || (c.IsLogged && issue.IsPoster(c.User.ID)) - c.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + c.Data["Link"].(string) - c.HTML(200, ISSUE_VIEW) -} - -func ViewIssue(c *context.Context) { - viewIssue(c, false) -} - -func ViewPull(c *context.Context) { - viewIssue(c, true) -} - -func getActionIssue(c *context.Context) *models.Issue { - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err) - return nil - } - - // Prevent guests accessing pull requests - if !c.Repo.HasAccess() && issue.IsPull { - c.NotFound() - return nil - } - - return issue -} - -func UpdateIssueTitle(c *context.Context) { - issue := getActionIssue(c) - if c.Written() { - return - } - - if !c.IsLogged || (!issue.IsPoster(c.User.ID) && !c.Repo.IsWriter()) { - c.Error(403) - return - } - - title := c.QueryTrim("title") - if len(title) == 0 { - c.Error(204) - return - } - - if err := issue.ChangeTitle(c.User, title); err != nil { - c.Handle(500, "ChangeTitle", err) - return - } - - c.JSON(200, map[string]interface{}{ - "title": issue.Title, - }) -} - -func UpdateIssueContent(c *context.Context) { - issue := getActionIssue(c) - if c.Written() { - return - } - - if !c.IsLogged || (c.User.ID != issue.PosterID && !c.Repo.IsWriter()) { - c.Error(403) - return - } - - content := c.Query("content") - if err := issue.ChangeContent(c.User, content); err != nil { - c.Handle(500, "ChangeContent", err) - return - } - - c.JSON(200, map[string]string{ - "content": string(markup.Markdown(issue.Content, c.Query("context"), c.Repo.Repository.ComposeMetas())), - }) -} - -func UpdateIssueLabel(c *context.Context) { - issue := getActionIssue(c) - if c.Written() { - return - } - - if c.Query("action") == "clear" { - if err := issue.ClearLabels(c.User); err != nil { - c.Handle(500, "ClearLabels", err) - return - } - } else { - isAttach := c.Query("action") == "attach" - label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")) - if err != nil { - if models.IsErrLabelNotExist(err) { - c.Error(404, "GetLabelByID") - } else { - c.Handle(500, "GetLabelByID", err) - } - return - } - - if isAttach && !issue.HasLabel(label.ID) { - if err = issue.AddLabel(c.User, label); err != nil { - c.Handle(500, "AddLabel", err) - return - } - } else if !isAttach && issue.HasLabel(label.ID) { - if err = issue.RemoveLabel(c.User, label); err != nil { - c.Handle(500, "RemoveLabel", err) - return - } - } - } - - c.JSON(200, map[string]interface{}{ - "ok": true, - }) -} - -func UpdateIssueMilestone(c *context.Context) { - issue := getActionIssue(c) - if c.Written() { - return - } - - oldMilestoneID := issue.MilestoneID - milestoneID := c.QueryInt64("id") - if oldMilestoneID == milestoneID { - c.JSON(200, map[string]interface{}{ - "ok": true, - }) - return - } - - // Not check for invalid milestone id and give responsibility to owners. - issue.MilestoneID = milestoneID - if err := models.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil { - c.Handle(500, "ChangeMilestoneAssign", err) - return - } - - c.JSON(200, map[string]interface{}{ - "ok": true, - }) -} - -func UpdateIssueAssignee(c *context.Context) { - issue := getActionIssue(c) - if c.Written() { - return - } - - assigneeID := c.QueryInt64("id") - if issue.AssigneeID == assigneeID { - c.JSON(200, map[string]interface{}{ - "ok": true, - }) - return - } - - if err := issue.ChangeAssignee(c.User, assigneeID); err != nil { - c.Handle(500, "ChangeAssignee", err) - return - } - - c.JSON(200, map[string]interface{}{ - "ok": true, - }) -} - -func NewComment(c *context.Context, f form.CreateComment) { - issue := getActionIssue(c) - if c.Written() { - return - } - - var attachments []string - if setting.AttachmentEnabled { - attachments = f.Files - } - - if c.HasError() { - c.Flash.Error(c.Data["ErrorMsg"].(string)) - c.Redirect(fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issue.Index)) - return - } - - var err error - var comment *models.Comment - defer func() { - // Check if issue admin/poster changes the status of issue. - if (c.Repo.IsWriter() || (c.IsLogged && issue.IsPoster(c.User.ID))) && - (f.Status == "reopen" || f.Status == "close") && - !(issue.IsPull && issue.PullRequest.HasMerged) { - - // Duplication and conflict check should apply to reopen pull request. - var pr *models.PullRequest - - if f.Status == "reopen" && issue.IsPull { - pull := issue.PullRequest - pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch) - if err != nil { - if !models.IsErrPullRequestNotExist(err) { - c.ServerError("GetUnmergedPullRequest", err) - return - } - } - - // Regenerate patch and test conflict. - if pr == nil { - if err = issue.PullRequest.UpdatePatch(); err != nil { - c.ServerError("UpdatePatch", err) - return - } - - issue.PullRequest.AddToTaskQueue() - } - } - - if pr != nil { - c.Flash.Info(c.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index)) - } else { - if err = issue.ChangeStatus(c.User, c.Repo.Repository, f.Status == "close"); err != nil { - log.Error(2, "ChangeStatus: %v", err) - } else { - log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed) - } - } - } - - // Redirect to comment hashtag if there is any actual content. - typeName := "issues" - if issue.IsPull { - typeName = "pulls" - } - if comment != nil { - c.Redirect(fmt.Sprintf("%s/%s/%d#%s", c.Repo.RepoLink, typeName, issue.Index, comment.HashTag())) - } else { - c.Redirect(fmt.Sprintf("%s/%s/%d", c.Repo.RepoLink, typeName, issue.Index)) - } - }() - - // Fix #321: Allow empty comments, as long as we have attachments. - if len(f.Content) == 0 && len(attachments) == 0 { - return - } - - comment, err = models.CreateIssueComment(c.User, c.Repo.Repository, issue, f.Content, attachments) - if err != nil { - c.ServerError("CreateIssueComment", err) - return - } - - log.Trace("Comment created: %d/%d/%d", c.Repo.Repository.ID, issue.ID, comment.ID) -} - -func UpdateCommentContent(c *context.Context) { - comment, err := models.GetCommentByID(c.ParamsInt64(":id")) - if err != nil { - c.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err) - return - } - - if c.UserID() != comment.PosterID && !c.Repo.IsAdmin() { - c.Error(404) - return - } else if comment.Type != models.COMMENT_TYPE_COMMENT { - c.Error(204) - return - } - - oldContent := comment.Content - comment.Content = c.Query("content") - if len(comment.Content) == 0 { - c.JSON(200, map[string]interface{}{ - "content": "", - }) - return - } - if err = models.UpdateComment(c.User, comment, oldContent); err != nil { - c.Handle(500, "UpdateComment", err) - return - } - - c.JSON(200, map[string]string{ - "content": string(markup.Markdown(comment.Content, c.Query("context"), c.Repo.Repository.ComposeMetas())), - }) -} - -func DeleteComment(c *context.Context) { - comment, err := models.GetCommentByID(c.ParamsInt64(":id")) - if err != nil { - c.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err) - return - } - - if c.UserID() != comment.PosterID && !c.Repo.IsAdmin() { - c.Error(404) - return - } else if comment.Type != models.COMMENT_TYPE_COMMENT { - c.Error(204) - return - } - - if err = models.DeleteCommentByID(c.User, comment.ID); err != nil { - c.Handle(500, "DeleteCommentByID", err) - return - } - - c.Status(200) -} - -func Labels(c *context.Context) { - c.Data["Title"] = c.Tr("repo.labels") - c.Data["PageIsIssueList"] = true - c.Data["PageIsLabels"] = true - c.Data["RequireMinicolors"] = true - c.Data["LabelTemplates"] = models.LabelTemplates - c.HTML(200, LABELS) -} - -func InitializeLabels(c *context.Context, f form.InitializeLabels) { - if c.HasError() { - c.Redirect(c.Repo.RepoLink + "/labels") - return - } - list, err := models.GetLabelTemplateFile(f.TemplateName) - if err != nil { - c.Flash.Error(c.Tr("repo.issues.label_templates.fail_to_load_file", f.TemplateName, err)) - c.Redirect(c.Repo.RepoLink + "/labels") - return - } - - labels := make([]*models.Label, len(list)) - for i := 0; i < len(list); i++ { - labels[i] = &models.Label{ - RepoID: c.Repo.Repository.ID, - Name: list[i][0], - Color: list[i][1], - } - } - if err := models.NewLabels(labels...); err != nil { - c.Handle(500, "NewLabels", err) - return - } - c.Redirect(c.Repo.RepoLink + "/labels") -} - -func NewLabel(c *context.Context, f form.CreateLabel) { - c.Data["Title"] = c.Tr("repo.labels") - c.Data["PageIsLabels"] = true - - if c.HasError() { - c.Flash.Error(c.Data["ErrorMsg"].(string)) - c.Redirect(c.Repo.RepoLink + "/labels") - return - } - - l := &models.Label{ - RepoID: c.Repo.Repository.ID, - Name: f.Title, - Color: f.Color, - } - if err := models.NewLabels(l); err != nil { - c.Handle(500, "NewLabel", err) - return - } - c.Redirect(c.Repo.RepoLink + "/labels") -} - -func UpdateLabel(c *context.Context, f form.CreateLabel) { - l, err := models.GetLabelByID(f.ID) - if err != nil { - switch { - case models.IsErrLabelNotExist(err): - c.Error(404) - default: - c.Handle(500, "UpdateLabel", err) - } - return - } - - l.Name = f.Title - l.Color = f.Color - if err := models.UpdateLabel(l); err != nil { - c.Handle(500, "UpdateLabel", err) - return - } - c.Redirect(c.Repo.RepoLink + "/labels") -} - -func DeleteLabel(c *context.Context) { - if err := models.DeleteLabel(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteLabel: " + err.Error()) - } else { - c.Flash.Success(c.Tr("repo.issues.label_deletion_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/labels", - }) - return -} - -func Milestones(c *context.Context) { - c.Data["Title"] = c.Tr("repo.milestones") - c.Data["PageIsIssueList"] = true - c.Data["PageIsMilestones"] = true - - isShowClosed := c.Query("state") == "closed" - openCount, closedCount := models.MilestoneStats(c.Repo.Repository.ID) - c.Data["OpenCount"] = openCount - c.Data["ClosedCount"] = closedCount - - page := c.QueryInt("page") - if page <= 1 { - page = 1 - } - - var total int - if !isShowClosed { - total = int(openCount) - } else { - total = int(closedCount) - } - c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5) - - miles, err := models.GetMilestones(c.Repo.Repository.ID, page, isShowClosed) - if err != nil { - c.Handle(500, "GetMilestones", err) - return - } - for _, m := range miles { - m.NumOpenIssues = int(m.CountIssues(false, false)) - m.NumClosedIssues = int(m.CountIssues(true, false)) - if m.NumOpenIssues+m.NumClosedIssues > 0 { - m.Completeness = m.NumClosedIssues * 100 / (m.NumOpenIssues + m.NumClosedIssues) - } - m.RenderedContent = string(markup.Markdown(m.Content, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) - } - c.Data["Milestones"] = miles - - if isShowClosed { - c.Data["State"] = "closed" - } else { - c.Data["State"] = "open" - } - - c.Data["IsShowClosed"] = isShowClosed - c.HTML(200, MILESTONE) -} - -func NewMilestone(c *context.Context) { - c.Data["Title"] = c.Tr("repo.milestones.new") - c.Data["PageIsIssueList"] = true - c.Data["PageIsMilestones"] = true - c.Data["RequireDatetimepicker"] = true - c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) - c.HTML(200, MILESTONE_NEW) -} - -func NewMilestonePost(c *context.Context, f form.CreateMilestone) { - c.Data["Title"] = c.Tr("repo.milestones.new") - c.Data["PageIsIssueList"] = true - c.Data["PageIsMilestones"] = true - c.Data["RequireDatetimepicker"] = true - c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) - - if c.HasError() { - c.HTML(200, MILESTONE_NEW) - return - } - - if len(f.Deadline) == 0 { - f.Deadline = "9999-12-31" - } - deadline, err := time.ParseInLocation("2006-01-02", f.Deadline, time.Local) - if err != nil { - c.Data["Err_Deadline"] = true - c.RenderWithErr(c.Tr("repo.milestones.invalid_due_date_format"), MILESTONE_NEW, &f) - return - } - - if err = models.NewMilestone(&models.Milestone{ - RepoID: c.Repo.Repository.ID, - Name: f.Title, - Content: f.Content, - Deadline: deadline, - }); err != nil { - c.Handle(500, "NewMilestone", err) - return - } - - c.Flash.Success(c.Tr("repo.milestones.create_success", f.Title)) - c.Redirect(c.Repo.RepoLink + "/milestones") -} - -func EditMilestone(c *context.Context) { - c.Data["Title"] = c.Tr("repo.milestones.edit") - c.Data["PageIsMilestones"] = true - c.Data["PageIsEditMilestone"] = true - c.Data["RequireDatetimepicker"] = true - c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) - - m, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if models.IsErrMilestoneNotExist(err) { - c.Handle(404, "", nil) - } else { - c.Handle(500, "GetMilestoneByRepoID", err) - } - return - } - c.Data["title"] = m.Name - c.Data["content"] = m.Content - if len(m.DeadlineString) > 0 { - c.Data["deadline"] = m.DeadlineString - } - c.HTML(200, MILESTONE_NEW) -} - -func EditMilestonePost(c *context.Context, f form.CreateMilestone) { - c.Data["Title"] = c.Tr("repo.milestones.edit") - c.Data["PageIsMilestones"] = true - c.Data["PageIsEditMilestone"] = true - c.Data["RequireDatetimepicker"] = true - c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) - - if c.HasError() { - c.HTML(200, MILESTONE_NEW) - return - } - - if len(f.Deadline) == 0 { - f.Deadline = "9999-12-31" - } - deadline, err := time.ParseInLocation("2006-01-02", f.Deadline, time.Local) - if err != nil { - c.Data["Err_Deadline"] = true - c.RenderWithErr(c.Tr("repo.milestones.invalid_due_date_format"), MILESTONE_NEW, &f) - return - } - - m, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if models.IsErrMilestoneNotExist(err) { - c.Handle(404, "", nil) - } else { - c.Handle(500, "GetMilestoneByRepoID", err) - } - return - } - m.Name = f.Title - m.Content = f.Content - m.Deadline = deadline - if err = models.UpdateMilestone(m); err != nil { - c.Handle(500, "UpdateMilestone", err) - return - } - - c.Flash.Success(c.Tr("repo.milestones.edit_success", m.Name)) - c.Redirect(c.Repo.RepoLink + "/milestones") -} - -func ChangeMilestonStatus(c *context.Context) { - m, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - if models.IsErrMilestoneNotExist(err) { - c.Handle(404, "", err) - } else { - c.Handle(500, "GetMilestoneByRepoID", err) - } - return - } - - switch c.Params(":action") { - case "open": - if m.IsClosed { - if err = models.ChangeMilestoneStatus(m, false); err != nil { - c.Handle(500, "ChangeMilestoneStatus", err) - return - } - } - c.Redirect(c.Repo.RepoLink + "/milestones?state=open") - case "close": - if !m.IsClosed { - m.ClosedDate = time.Now() - if err = models.ChangeMilestoneStatus(m, true); err != nil { - c.Handle(500, "ChangeMilestoneStatus", err) - return - } - } - c.Redirect(c.Repo.RepoLink + "/milestones?state=closed") - default: - c.Redirect(c.Repo.RepoLink + "/milestones") - } -} - -func DeleteMilestone(c *context.Context) { - if err := models.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteMilestoneByRepoID: " + err.Error()) - } else { - c.Flash.Success(c.Tr("repo.milestones.deletion_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/milestones", - }) -} diff --git a/routers/repo/pull.go b/routers/repo/pull.go deleted file mode 100644 index 73757280..00000000 --- a/routers/repo/pull.go +++ /dev/null @@ -1,763 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "container/list" - "path" - "strings" - - "github.com/Unknwon/com" - log "gopkg.in/clog.v1" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - FORK = "repo/pulls/fork" - COMPARE_PULL = "repo/pulls/compare" - PULL_COMMITS = "repo/pulls/commits" - PULL_FILES = "repo/pulls/files" - - PULL_REQUEST_TEMPLATE_KEY = "PullRequestTemplate" -) - -var ( - PullRequestTemplateCandidates = []string{ - "PULL_REQUEST.md", - ".gogs/PULL_REQUEST.md", - ".github/PULL_REQUEST.md", - } -) - -func parseBaseRepository(c *context.Context) *models.Repository { - baseRepo, err := models.GetRepositoryByID(c.ParamsInt64(":repoid")) - if err != nil { - c.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err) - return nil - } - - if !baseRepo.CanBeForked() || !baseRepo.HasAccess(c.User.ID) { - c.NotFound() - return nil - } - - c.Data["repo_name"] = baseRepo.Name - c.Data["description"] = baseRepo.Description - c.Data["IsPrivate"] = baseRepo.IsPrivate - - if err = baseRepo.GetOwner(); err != nil { - c.ServerError("GetOwner", err) - return nil - } - c.Data["ForkFrom"] = baseRepo.Owner.Name + "/" + baseRepo.Name - - if err := c.User.GetOrganizations(true); err != nil { - c.ServerError("GetOrganizations", err) - return nil - } - c.Data["Orgs"] = c.User.Orgs - - return baseRepo -} - -func Fork(c *context.Context) { - c.Data["Title"] = c.Tr("new_fork") - - parseBaseRepository(c) - if c.Written() { - return - } - - c.Data["ContextUser"] = c.User - c.Success(FORK) -} - -func ForkPost(c *context.Context, f form.CreateRepo) { - c.Data["Title"] = c.Tr("new_fork") - - baseRepo := parseBaseRepository(c) - if c.Written() { - return - } - - ctxUser := checkContextUser(c, f.UserID) - if c.Written() { - return - } - c.Data["ContextUser"] = ctxUser - - if c.HasError() { - c.Success(FORK) - return - } - - repo, has := models.HasForkedRepo(ctxUser.ID, baseRepo.ID) - if has { - c.Redirect(repo.Link()) - return - } - - // Check ownership of organization. - if ctxUser.IsOrganization() && !ctxUser.IsOwnedBy(c.User.ID) { - c.Error(403) - return - } - - // Cannot fork to same owner - if ctxUser.ID == baseRepo.OwnerID { - c.RenderWithErr(c.Tr("repo.settings.cannot_fork_to_same_owner"), FORK, &f) - return - } - - repo, err := models.ForkRepository(c.User, ctxUser, baseRepo, f.RepoName, f.Description) - if err != nil { - c.Data["Err_RepoName"] = true - switch { - case models.IsErrRepoAlreadyExist(err): - c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), FORK, &f) - case models.IsErrNameReserved(err): - c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), FORK, &f) - case models.IsErrNamePatternNotAllowed(err): - c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), FORK, &f) - default: - c.ServerError("ForkPost", err) - } - return - } - - log.Trace("Repository forked from '%s' -> '%s'", baseRepo.FullName(), repo.FullName()) - c.Redirect(repo.Link()) -} - -func checkPullInfo(c *context.Context) *models.Issue { - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) - if err != nil { - c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err) - return nil - } - c.Data["Title"] = issue.Title - c.Data["Issue"] = issue - - if !issue.IsPull { - c.Handle(404, "ViewPullCommits", nil) - return nil - } - - if c.IsLogged { - // Update issue-user. - if err = issue.ReadBy(c.User.ID); err != nil { - c.ServerError("ReadBy", err) - return nil - } - } - - return issue -} - -func PrepareMergedViewPullInfo(c *context.Context, issue *models.Issue) { - pull := issue.PullRequest - c.Data["HasMerged"] = true - c.Data["HeadTarget"] = issue.PullRequest.HeadUserName + "/" + pull.HeadBranch - c.Data["BaseTarget"] = c.Repo.Owner.Name + "/" + pull.BaseBranch - - var err error - c.Data["NumCommits"], err = c.Repo.GitRepo.CommitsCountBetween(pull.MergeBase, pull.MergedCommitID) - if err != nil { - c.ServerError("Repo.GitRepo.CommitsCountBetween", err) - return - } - c.Data["NumFiles"], err = c.Repo.GitRepo.FilesCountBetween(pull.MergeBase, pull.MergedCommitID) - if err != nil { - c.ServerError("Repo.GitRepo.FilesCountBetween", err) - return - } -} - -func PrepareViewPullInfo(c *context.Context, issue *models.Issue) *git.PullRequestInfo { - repo := c.Repo.Repository - pull := issue.PullRequest - - c.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBranch - c.Data["BaseTarget"] = c.Repo.Owner.Name + "/" + pull.BaseBranch - - var ( - headGitRepo *git.Repository - err error - ) - - if pull.HeadRepo != nil { - headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath()) - if err != nil { - c.ServerError("OpenRepository", err) - return nil - } - } - - if pull.HeadRepo == nil || !headGitRepo.IsBranchExist(pull.HeadBranch) { - c.Data["IsPullReuqestBroken"] = true - c.Data["HeadTarget"] = "deleted" - c.Data["NumCommits"] = 0 - c.Data["NumFiles"] = 0 - return nil - } - - prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), - pull.BaseBranch, pull.HeadBranch) - if err != nil { - if strings.Contains(err.Error(), "fatal: Not a valid object name") { - c.Data["IsPullReuqestBroken"] = true - c.Data["BaseTarget"] = "deleted" - c.Data["NumCommits"] = 0 - c.Data["NumFiles"] = 0 - return nil - } - - c.ServerError("GetPullRequestInfo", err) - return nil - } - c.Data["NumCommits"] = prInfo.Commits.Len() - c.Data["NumFiles"] = prInfo.NumFiles - return prInfo -} - -func ViewPullCommits(c *context.Context) { - c.Data["PageIsPullList"] = true - c.Data["PageIsPullCommits"] = true - - issue := checkPullInfo(c) - if c.Written() { - return - } - pull := issue.PullRequest - - if pull.HeadRepo != nil { - c.Data["Username"] = pull.HeadUserName - c.Data["Reponame"] = pull.HeadRepo.Name - } - - var commits *list.List - if pull.HasMerged { - PrepareMergedViewPullInfo(c, issue) - if c.Written() { - return - } - startCommit, err := c.Repo.GitRepo.GetCommit(pull.MergeBase) - if err != nil { - c.ServerError("Repo.GitRepo.GetCommit", err) - return - } - endCommit, err := c.Repo.GitRepo.GetCommit(pull.MergedCommitID) - if err != nil { - c.ServerError("Repo.GitRepo.GetCommit", err) - return - } - commits, err = c.Repo.GitRepo.CommitsBetween(endCommit, startCommit) - if err != nil { - c.ServerError("Repo.GitRepo.CommitsBetween", err) - return - } - - } else { - prInfo := PrepareViewPullInfo(c, issue) - if c.Written() { - return - } else if prInfo == nil { - c.Handle(404, "ViewPullCommits", nil) - return - } - commits = prInfo.Commits - } - - commits = models.ValidateCommitsWithEmails(commits) - c.Data["Commits"] = commits - c.Data["CommitsCount"] = commits.Len() - - c.Success(PULL_COMMITS) -} - -func ViewPullFiles(c *context.Context) { - c.Data["PageIsPullList"] = true - c.Data["PageIsPullFiles"] = true - - issue := checkPullInfo(c) - if c.Written() { - return - } - pull := issue.PullRequest - - var ( - diffRepoPath string - startCommitID string - endCommitID string - gitRepo *git.Repository - ) - - if pull.HasMerged { - PrepareMergedViewPullInfo(c, issue) - if c.Written() { - return - } - - diffRepoPath = c.Repo.GitRepo.Path - startCommitID = pull.MergeBase - endCommitID = pull.MergedCommitID - gitRepo = c.Repo.GitRepo - } else { - prInfo := PrepareViewPullInfo(c, issue) - if c.Written() { - return - } else if prInfo == nil { - c.Handle(404, "ViewPullFiles", nil) - return - } - - headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name) - - headGitRepo, err := git.OpenRepository(headRepoPath) - if err != nil { - c.ServerError("OpenRepository", err) - return - } - - headCommitID, err := headGitRepo.GetBranchCommitID(pull.HeadBranch) - if err != nil { - c.ServerError("GetBranchCommitID", err) - return - } - - diffRepoPath = headRepoPath - startCommitID = prInfo.MergeBase - endCommitID = headCommitID - gitRepo = headGitRepo - } - - diff, err := models.GetDiffRange(diffRepoPath, - startCommitID, endCommitID, setting.Git.MaxGitDiffLines, - setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) - if err != nil { - c.ServerError("GetDiffRange", err) - return - } - c.Data["Diff"] = diff - c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 - - commit, err := gitRepo.GetCommit(endCommitID) - if err != nil { - c.ServerError("GetCommit", err) - return - } - - setEditorconfigIfExists(c) - if c.Written() { - return - } - - c.Data["IsSplitStyle"] = c.Query("style") == "split" - c.Data["IsImageFile"] = commit.IsImageFile - - // It is possible head repo has been deleted for merged pull requests - if pull.HeadRepo != nil { - c.Data["Username"] = pull.HeadUserName - c.Data["Reponame"] = pull.HeadRepo.Name - - headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name) - c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", endCommitID) - c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", startCommitID) - c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", endCommitID) - } - - c.Data["RequireHighlightJS"] = true - c.Success(PULL_FILES) -} - -func MergePullRequest(c *context.Context) { - issue := checkPullInfo(c) - if c.Written() { - return - } - if issue.IsClosed { - c.Handle(404, "MergePullRequest", nil) - return - } - - pr, err := models.GetPullRequestByIssueID(issue.ID) - if err != nil { - c.NotFoundOrServerError("GetPullRequestByIssueID", models.IsErrPullRequestNotExist, err) - return - } - - if !pr.CanAutoMerge() || pr.HasMerged { - c.Handle(404, "MergePullRequest", nil) - return - } - - pr.Issue = issue - pr.Issue.Repo = c.Repo.Repository - if err = pr.Merge(c.User, c.Repo.GitRepo); err != nil { - c.ServerError("Merge", err) - return - } - - log.Trace("Pull request merged: %d", pr.ID) - c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) -} - -func ParseCompareInfo(c *context.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { - baseRepo := c.Repo.Repository - - // Get compared branches information - // format: ...[:] - // base<-head: master...head:feature - // same repo: master...feature - infos := strings.Split(c.Params("*"), "...") - if len(infos) != 2 { - log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos) - c.NotFound() - return nil, nil, nil, nil, "", "" - } - - baseBranch := infos[0] - c.Data["BaseBranch"] = baseBranch - - var ( - headUser *models.User - headBranch string - isSameRepo bool - err error - ) - - // If there is no head repository, it means pull request between same repository. - headInfos := strings.Split(infos[1], ":") - if len(headInfos) == 1 { - isSameRepo = true - headUser = c.Repo.Owner - headBranch = headInfos[0] - - } else if len(headInfos) == 2 { - headUser, err = models.GetUserByName(headInfos[0]) - if err != nil { - c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) - return nil, nil, nil, nil, "", "" - } - headBranch = headInfos[1] - isSameRepo = headUser.ID == baseRepo.OwnerID - - } else { - c.NotFound() - return nil, nil, nil, nil, "", "" - } - c.Data["HeadUser"] = headUser - c.Data["HeadBranch"] = headBranch - c.Repo.PullRequest.SameRepo = isSameRepo - - // Check if base branch is valid. - if !c.Repo.GitRepo.IsBranchExist(baseBranch) { - c.NotFound() - return nil, nil, nil, nil, "", "" - } - - var ( - headRepo *models.Repository - headGitRepo *git.Repository - ) - - // In case user included redundant head user name for comparison in same repository, - // no need to check the fork relation. - if !isSameRepo { - var has bool - headRepo, has = models.HasForkedRepo(headUser.ID, baseRepo.ID) - if !has { - log.Trace("ParseCompareInfo [base_repo_id: %d]: does not have fork or in same repository", baseRepo.ID) - c.NotFound() - return nil, nil, nil, nil, "", "" - } - - headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name)) - if err != nil { - c.ServerError("OpenRepository", err) - return nil, nil, nil, nil, "", "" - } - } else { - headRepo = c.Repo.Repository - headGitRepo = c.Repo.GitRepo - } - - if !c.User.IsWriterOfRepo(headRepo) && !c.User.IsAdmin { - log.Trace("ParseCompareInfo [base_repo_id: %d]: does not have write access or site admin", baseRepo.ID) - c.NotFound() - return nil, nil, nil, nil, "", "" - } - - // Check if head branch is valid. - if !headGitRepo.IsBranchExist(headBranch) { - c.NotFound() - return nil, nil, nil, nil, "", "" - } - - headBranches, err := headGitRepo.GetBranches() - if err != nil { - c.ServerError("GetBranches", err) - return nil, nil, nil, nil, "", "" - } - c.Data["HeadBranches"] = headBranches - - prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) - if err != nil { - if git.IsErrNoMergeBase(err) { - c.Data["IsNoMergeBase"] = true - c.Success(COMPARE_PULL) - } else { - c.ServerError("GetPullRequestInfo", err) - } - return nil, nil, nil, nil, "", "" - } - c.Data["BeforeCommitID"] = prInfo.MergeBase - - return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch -} - -func PrepareCompareDiff( - c *context.Context, - headUser *models.User, - headRepo *models.Repository, - headGitRepo *git.Repository, - prInfo *git.PullRequestInfo, - baseBranch, headBranch string) bool { - - var ( - repo = c.Repo.Repository - err error - ) - - // Get diff information. - c.Data["CommitRepoLink"] = headRepo.Link() - - headCommitID, err := headGitRepo.GetBranchCommitID(headBranch) - if err != nil { - c.ServerError("GetBranchCommitID", err) - return false - } - c.Data["AfterCommitID"] = headCommitID - - if headCommitID == prInfo.MergeBase { - c.Data["IsNothingToCompare"] = true - return true - } - - diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name), - prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines, - setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) - if err != nil { - c.ServerError("GetDiffRange", err) - return false - } - c.Data["Diff"] = diff - c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 - - headCommit, err := headGitRepo.GetCommit(headCommitID) - if err != nil { - c.ServerError("GetCommit", err) - return false - } - - prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits) - c.Data["Commits"] = prInfo.Commits - c.Data["CommitCount"] = prInfo.Commits.Len() - c.Data["Username"] = headUser.Name - c.Data["Reponame"] = headRepo.Name - c.Data["IsImageFile"] = headCommit.IsImageFile - - headTarget := path.Join(headUser.Name, repo.Name) - c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", headCommitID) - c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", prInfo.MergeBase) - c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", headCommitID) - return false -} - -func CompareAndPullRequest(c *context.Context) { - c.Data["Title"] = c.Tr("repo.pulls.compare_changes") - c.Data["PageIsComparePull"] = true - c.Data["IsDiffCompare"] = true - c.Data["RequireHighlightJS"] = true - setTemplateIfExists(c, PULL_REQUEST_TEMPLATE_KEY, PullRequestTemplateCandidates) - renderAttachmentSettings(c) - - headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(c) - if c.Written() { - return - } - - pr, err := models.GetUnmergedPullRequest(headRepo.ID, c.Repo.Repository.ID, headBranch, baseBranch) - if err != nil { - if !models.IsErrPullRequestNotExist(err) { - c.ServerError("GetUnmergedPullRequest", err) - return - } - } else { - c.Data["HasPullRequest"] = true - c.Data["PullRequest"] = pr - c.Success(COMPARE_PULL) - return - } - - nothingToCompare := PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) - if c.Written() { - return - } - - if !nothingToCompare { - // Setup information for new form. - RetrieveRepoMetas(c, c.Repo.Repository) - if c.Written() { - return - } - } - - setEditorconfigIfExists(c) - if c.Written() { - return - } - - c.Data["IsSplitStyle"] = c.Query("style") == "split" - c.Success(COMPARE_PULL) -} - -func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) { - c.Data["Title"] = c.Tr("repo.pulls.compare_changes") - c.Data["PageIsComparePull"] = true - c.Data["IsDiffCompare"] = true - c.Data["RequireHighlightJS"] = true - renderAttachmentSettings(c) - - var ( - repo = c.Repo.Repository - attachments []string - ) - - headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(c) - if c.Written() { - return - } - - labelIDs, milestoneID, assigneeID := ValidateRepoMetas(c, f) - if c.Written() { - return - } - - if setting.AttachmentEnabled { - attachments = f.Files - } - - if c.HasError() { - form.Assign(f, c.Data) - - // This stage is already stop creating new pull request, so it does not matter if it has - // something to compare or not. - PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) - if c.Written() { - return - } - - c.Success(COMPARE_PULL) - return - } - - patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch) - if err != nil { - c.ServerError("GetPatch", err) - return - } - - pullIssue := &models.Issue{ - RepoID: repo.ID, - Index: repo.NextIssueIndex(), - Title: f.Title, - PosterID: c.User.ID, - Poster: c.User, - MilestoneID: milestoneID, - AssigneeID: assigneeID, - IsPull: true, - Content: f.Content, - } - pullRequest := &models.PullRequest{ - HeadRepoID: headRepo.ID, - BaseRepoID: repo.ID, - HeadUserName: headUser.Name, - HeadBranch: headBranch, - BaseBranch: baseBranch, - HeadRepo: headRepo, - BaseRepo: repo, - MergeBase: prInfo.MergeBase, - Type: models.PULL_REQUEST_GOGS, - } - // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt - // instead of 500. - if err := models.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch); err != nil { - c.ServerError("NewPullRequest", err) - return - } else if err := pullRequest.PushToBaseRepo(); err != nil { - c.ServerError("PushToBaseRepo", err) - return - } - - log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID) - c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index)) -} - -func parseOwnerAndRepo(c *context.Context) (*models.User, *models.Repository) { - owner, err := models.GetUserByName(c.Params(":username")) - if err != nil { - c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) - return nil, nil - } - - repo, err := models.GetRepositoryByName(owner.ID, c.Params(":reponame")) - if err != nil { - c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err) - return nil, nil - } - - return owner, repo -} - -func TriggerTask(c *context.Context) { - pusherID := c.QueryInt64("pusher") - branch := c.Query("branch") - secret := c.Query("secret") - if len(branch) == 0 || len(secret) == 0 || pusherID <= 0 { - c.Error(404) - log.Trace("TriggerTask: branch or secret is empty, or pusher ID is not valid") - return - } - owner, repo := parseOwnerAndRepo(c) - if c.Written() { - return - } - if secret != tool.MD5(owner.Salt) { - c.Error(404) - log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name) - return - } - - pusher, err := models.GetUserByID(pusherID) - if err != nil { - c.NotFoundOrServerError("GetUserByID", errors.IsUserNotExist, err) - return - } - - log.Trace("TriggerTask '%s/%s' by '%s'", repo.Name, branch, pusher.Name) - - go models.HookQueue.Add(repo.ID) - go models.AddTestPullRequestTask(pusher, repo.ID, branch, true) - c.Status(202) -} diff --git a/routers/repo/release.go b/routers/repo/release.go deleted file mode 100644 index 86dfe6f7..00000000 --- a/routers/repo/release.go +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "fmt" - "strings" - - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/markup" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - RELEASES = "repo/release/list" - RELEASE_NEW = "repo/release/new" -) - -// calReleaseNumCommitsBehind calculates given release has how many commits behind release target. -func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *models.Release, countCache map[string]int64) error { - // Get count if not exists - if _, ok := countCache[release.Target]; !ok { - if repoCtx.GitRepo.IsBranchExist(release.Target) { - commit, err := repoCtx.GitRepo.GetBranchCommit(release.Target) - if err != nil { - return fmt.Errorf("GetBranchCommit: %v", err) - } - countCache[release.Target], err = commit.CommitsCount() - if err != nil { - return fmt.Errorf("CommitsCount: %v", err) - } - } else { - // Use NumCommits of the newest release on that target - countCache[release.Target] = release.NumCommits - } - } - release.NumCommitsBehind = countCache[release.Target] - release.NumCommits - return nil -} - -func Releases(c *context.Context) { - c.Data["Title"] = c.Tr("repo.release.releases") - c.Data["PageIsViewFiles"] = true - c.Data["PageIsReleaseList"] = true - - tagsResult, err := c.Repo.GitRepo.GetTagsAfter(c.Query("after"), 10) - if err != nil { - c.Handle(500, fmt.Sprintf("GetTags '%s'", c.Repo.Repository.RepoPath()), err) - return - } - - releases, err := models.GetPublishedReleasesByRepoID(c.Repo.Repository.ID, tagsResult.Tags...) - if err != nil { - c.Handle(500, "GetPublishedReleasesByRepoID", err) - return - } - - // Temproray cache commits count of used branches to speed up. - countCache := make(map[string]int64) - - results := make([]*models.Release, len(tagsResult.Tags)) - for i, rawTag := range tagsResult.Tags { - for j, r := range releases { - if r == nil || r.TagName != rawTag { - continue - } - releases[j] = nil // Mark as used. - - if err = r.LoadAttributes(); err != nil { - c.Handle(500, "LoadAttributes", err) - return - } - - if err := calReleaseNumCommitsBehind(c.Repo, r, countCache); err != nil { - c.Handle(500, "calReleaseNumCommitsBehind", err) - return - } - - r.Note = string(markup.Markdown(r.Note, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) - results[i] = r - break - } - - // No published release matches this tag - if results[i] == nil { - commit, err := c.Repo.GitRepo.GetTagCommit(rawTag) - if err != nil { - c.Handle(500, "GetTagCommit", err) - return - } - - results[i] = &models.Release{ - Title: rawTag, - TagName: rawTag, - Sha1: commit.ID.String(), - } - - results[i].NumCommits, err = commit.CommitsCount() - if err != nil { - c.Handle(500, "CommitsCount", err) - return - } - results[i].NumCommitsBehind = c.Repo.CommitsCount - results[i].NumCommits - } - } - models.SortReleases(results) - - // Only show drafts if user is viewing the latest page - var drafts []*models.Release - if tagsResult.HasLatest { - drafts, err = models.GetDraftReleasesByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Handle(500, "GetDraftReleasesByRepoID", err) - return - } - - for _, r := range drafts { - if err = r.LoadAttributes(); err != nil { - c.Handle(500, "LoadAttributes", err) - return - } - - if err := calReleaseNumCommitsBehind(c.Repo, r, countCache); err != nil { - c.Handle(500, "calReleaseNumCommitsBehind", err) - return - } - - r.Note = string(markup.Markdown(r.Note, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) - } - - if len(drafts) > 0 { - results = append(drafts, results...) - } - } - - c.Data["Releases"] = results - c.Data["HasPrevious"] = !tagsResult.HasLatest - c.Data["ReachEnd"] = tagsResult.ReachEnd - c.Data["PreviousAfter"] = tagsResult.PreviousAfter - if len(results) > 0 { - c.Data["NextAfter"] = results[len(results)-1].TagName - } - c.HTML(200, RELEASES) -} - -func renderReleaseAttachmentSettings(c *context.Context) { - c.Data["RequireDropzone"] = true - c.Data["IsAttachmentEnabled"] = setting.Release.Attachment.Enabled - c.Data["AttachmentAllowedTypes"] = strings.Join(setting.Release.Attachment.AllowedTypes, ",") - c.Data["AttachmentMaxSize"] = setting.Release.Attachment.MaxSize - c.Data["AttachmentMaxFiles"] = setting.Release.Attachment.MaxFiles -} - -func NewRelease(c *context.Context) { - c.Data["Title"] = c.Tr("repo.release.new_release") - c.Data["PageIsReleaseList"] = true - c.Data["tag_target"] = c.Repo.Repository.DefaultBranch - renderReleaseAttachmentSettings(c) - c.HTML(200, RELEASE_NEW) -} - -func NewReleasePost(c *context.Context, f form.NewRelease) { - c.Data["Title"] = c.Tr("repo.release.new_release") - c.Data["PageIsReleaseList"] = true - renderReleaseAttachmentSettings(c) - - if c.HasError() { - c.HTML(200, RELEASE_NEW) - return - } - - if !c.Repo.GitRepo.IsBranchExist(f.Target) { - c.RenderWithErr(c.Tr("form.target_branch_not_exist"), RELEASE_NEW, &f) - return - } - - // Use current time if tag not yet exist, otherwise get time from Git - var tagCreatedUnix int64 - tag, err := c.Repo.GitRepo.GetTag(f.TagName) - if err == nil { - commit, err := tag.Commit() - if err == nil { - tagCreatedUnix = commit.Author.When.Unix() - } - } - - commit, err := c.Repo.GitRepo.GetBranchCommit(f.Target) - if err != nil { - c.Handle(500, "GetBranchCommit", err) - return - } - - commitsCount, err := commit.CommitsCount() - if err != nil { - c.Handle(500, "CommitsCount", err) - return - } - - var attachments []string - if setting.Release.Attachment.Enabled { - attachments = f.Files - } - - rel := &models.Release{ - RepoID: c.Repo.Repository.ID, - PublisherID: c.User.ID, - Title: f.Title, - TagName: f.TagName, - Target: f.Target, - Sha1: commit.ID.String(), - NumCommits: commitsCount, - Note: f.Content, - IsDraft: len(f.Draft) > 0, - IsPrerelease: f.Prerelease, - CreatedUnix: tagCreatedUnix, - } - if err = models.NewRelease(c.Repo.GitRepo, rel, attachments); err != nil { - c.Data["Err_TagName"] = true - switch { - case models.IsErrReleaseAlreadyExist(err): - c.RenderWithErr(c.Tr("repo.release.tag_name_already_exist"), RELEASE_NEW, &f) - case models.IsErrInvalidTagName(err): - c.RenderWithErr(c.Tr("repo.release.tag_name_invalid"), RELEASE_NEW, &f) - default: - c.Handle(500, "NewRelease", err) - } - return - } - log.Trace("Release created: %s/%s:%s", c.User.LowerName, c.Repo.Repository.Name, f.TagName) - - c.Redirect(c.Repo.RepoLink + "/releases") -} - -func EditRelease(c *context.Context) { - c.Data["Title"] = c.Tr("repo.release.edit_release") - c.Data["PageIsReleaseList"] = true - c.Data["PageIsEditRelease"] = true - renderReleaseAttachmentSettings(c) - - tagName := c.Params("*") - rel, err := models.GetRelease(c.Repo.Repository.ID, tagName) - if err != nil { - if models.IsErrReleaseNotExist(err) { - c.Handle(404, "GetRelease", err) - } else { - c.Handle(500, "GetRelease", err) - } - return - } - c.Data["ID"] = rel.ID - c.Data["tag_name"] = rel.TagName - c.Data["tag_target"] = rel.Target - c.Data["title"] = rel.Title - c.Data["content"] = rel.Note - c.Data["attachments"] = rel.Attachments - c.Data["prerelease"] = rel.IsPrerelease - c.Data["IsDraft"] = rel.IsDraft - - c.HTML(200, RELEASE_NEW) -} - -func EditReleasePost(c *context.Context, f form.EditRelease) { - c.Data["Title"] = c.Tr("repo.release.edit_release") - c.Data["PageIsReleaseList"] = true - c.Data["PageIsEditRelease"] = true - renderReleaseAttachmentSettings(c) - - tagName := c.Params("*") - rel, err := models.GetRelease(c.Repo.Repository.ID, tagName) - if err != nil { - if models.IsErrReleaseNotExist(err) { - c.Handle(404, "GetRelease", err) - } else { - c.Handle(500, "GetRelease", err) - } - return - } - c.Data["tag_name"] = rel.TagName - c.Data["tag_target"] = rel.Target - c.Data["title"] = rel.Title - c.Data["content"] = rel.Note - c.Data["attachments"] = rel.Attachments - c.Data["prerelease"] = rel.IsPrerelease - c.Data["IsDraft"] = rel.IsDraft - - if c.HasError() { - c.HTML(200, RELEASE_NEW) - return - } - - var attachments []string - if setting.Release.Attachment.Enabled { - attachments = f.Files - } - - isPublish := rel.IsDraft && len(f.Draft) == 0 - rel.Title = f.Title - rel.Note = f.Content - rel.IsDraft = len(f.Draft) > 0 - rel.IsPrerelease = f.Prerelease - if err = models.UpdateRelease(c.User, c.Repo.GitRepo, rel, isPublish, attachments); err != nil { - c.Handle(500, "UpdateRelease", err) - return - } - c.Redirect(c.Repo.RepoLink + "/releases") -} - -func UploadReleaseAttachment(c *context.Context) { - if !setting.Release.Attachment.Enabled { - c.NotFound() - return - } - uploadAttachment(c, setting.Release.Attachment.AllowedTypes) -} - -func DeleteRelease(c *context.Context) { - if err := models.DeleteReleaseOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteReleaseByID: " + err.Error()) - } else { - c.Flash.Success(c.Tr("repo.release.deletion_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/releases", - }) -} diff --git a/routers/repo/repo.go b/routers/repo/repo.go deleted file mode 100644 index ea3c1a60..00000000 --- a/routers/repo/repo.go +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "fmt" - "os" - "path" - "strings" - - "github.com/Unknwon/com" - log "gopkg.in/clog.v1" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - CREATE = "repo/create" - MIGRATE = "repo/migrate" -) - -func MustBeNotBare(c *context.Context) { - if c.Repo.Repository.IsBare { - c.Handle(404, "MustBeNotBare", nil) - } -} - -func checkContextUser(c *context.Context, uid int64) *models.User { - orgs, err := models.GetOwnedOrgsByUserIDDesc(c.User.ID, "updated_unix") - if err != nil { - c.Handle(500, "GetOwnedOrgsByUserIDDesc", err) - return nil - } - c.Data["Orgs"] = orgs - - // Not equal means current user is an organization. - if uid == c.User.ID || uid == 0 { - return c.User - } - - org, err := models.GetUserByID(uid) - if errors.IsUserNotExist(err) { - return c.User - } - - if err != nil { - c.Handle(500, "GetUserByID", fmt.Errorf("[%d]: %v", uid, err)) - return nil - } - - // Check ownership of organization. - if !org.IsOrganization() || !(c.User.IsAdmin || org.IsOwnedBy(c.User.ID)) { - c.Error(403) - return nil - } - return org -} - -func Create(c *context.Context) { - c.Data["Title"] = c.Tr("new_repo") - - // Give default value for template to render. - c.Data["Gitignores"] = models.Gitignores - c.Data["Licenses"] = models.Licenses - c.Data["Readmes"] = models.Readmes - c.Data["readme"] = "Default" - c.Data["private"] = c.User.LastRepoVisibility - c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - - ctxUser := checkContextUser(c, c.QueryInt64("org")) - if c.Written() { - return - } - c.Data["ContextUser"] = ctxUser - - c.HTML(200, CREATE) -} - -func handleCreateError(c *context.Context, owner *models.User, err error, name, tpl string, form interface{}) { - switch { - case errors.IsReachLimitOfRepo(err): - c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", owner.RepoCreationNum()), tpl, form) - case models.IsErrRepoAlreadyExist(err): - c.Data["Err_RepoName"] = true - c.RenderWithErr(c.Tr("form.repo_name_been_taken"), tpl, form) - case models.IsErrNameReserved(err): - c.Data["Err_RepoName"] = true - c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) - case models.IsErrNamePatternNotAllowed(err): - c.Data["Err_RepoName"] = true - c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) - default: - c.Handle(500, name, err) - } -} - -func CreatePost(c *context.Context, f form.CreateRepo) { - c.Data["Title"] = c.Tr("new_repo") - - c.Data["Gitignores"] = models.Gitignores - c.Data["Licenses"] = models.Licenses - c.Data["Readmes"] = models.Readmes - - ctxUser := checkContextUser(c, f.UserID) - if c.Written() { - return - } - c.Data["ContextUser"] = ctxUser - - if c.HasError() { - c.HTML(200, CREATE) - return - } - - repo, err := models.CreateRepository(c.User, ctxUser, models.CreateRepoOptions{ - Name: f.RepoName, - Description: f.Description, - Gitignores: f.Gitignores, - License: f.License, - Readme: f.Readme, - IsPrivate: f.Private || setting.Repository.ForcePrivate, - AutoInit: f.AutoInit, - }) - if err == nil { - log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) - c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name) - return - } - - if repo != nil { - if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { - log.Error(4, "DeleteRepository: %v", errDelete) - } - } - - handleCreateError(c, ctxUser, err, "CreatePost", CREATE, &f) -} - -func Migrate(c *context.Context) { - c.Data["Title"] = c.Tr("new_migrate") - c.Data["private"] = c.User.LastRepoVisibility - c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - c.Data["mirror"] = c.Query("mirror") == "1" - - ctxUser := checkContextUser(c, c.QueryInt64("org")) - if c.Written() { - return - } - c.Data["ContextUser"] = ctxUser - - c.HTML(200, MIGRATE) -} - -func MigratePost(c *context.Context, f form.MigrateRepo) { - c.Data["Title"] = c.Tr("new_migrate") - - ctxUser := checkContextUser(c, f.Uid) - if c.Written() { - return - } - c.Data["ContextUser"] = ctxUser - - if c.HasError() { - c.HTML(200, MIGRATE) - return - } - - remoteAddr, err := f.ParseRemoteAddr(c.User) - if err != nil { - if models.IsErrInvalidCloneAddr(err) { - c.Data["Err_CloneAddr"] = true - addrErr := err.(models.ErrInvalidCloneAddr) - switch { - case addrErr.IsURLError: - c.RenderWithErr(c.Tr("form.url_error"), MIGRATE, &f) - case addrErr.IsPermissionDenied: - c.RenderWithErr(c.Tr("repo.migrate.permission_denied"), MIGRATE, &f) - case addrErr.IsInvalidPath: - c.RenderWithErr(c.Tr("repo.migrate.invalid_local_path"), MIGRATE, &f) - default: - c.Handle(500, "Unknown error", err) - } - } else { - c.Handle(500, "ParseRemoteAddr", err) - } - return - } - - repo, err := models.MigrateRepository(c.User, ctxUser, models.MigrateRepoOptions{ - Name: f.RepoName, - Description: f.Description, - IsPrivate: f.Private || setting.Repository.ForcePrivate, - IsMirror: f.Mirror, - RemoteAddr: remoteAddr, - }) - if err == nil { - log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, f.RepoName) - c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + f.RepoName) - return - } - - if repo != nil { - if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { - log.Error(4, "DeleteRepository: %v", errDelete) - } - } - - if strings.Contains(err.Error(), "Authentication failed") || - strings.Contains(err.Error(), "could not read Username") { - c.Data["Err_Auth"] = true - c.RenderWithErr(c.Tr("form.auth_failed", models.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f) - return - } else if strings.Contains(err.Error(), "fatal:") { - c.Data["Err_CloneAddr"] = true - c.RenderWithErr(c.Tr("repo.migrate.failed", models.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f) - return - } - - handleCreateError(c, ctxUser, err, "MigratePost", MIGRATE, &f) -} - -func Action(c *context.Context) { - var err error - switch c.Params(":action") { - case "watch": - err = models.WatchRepo(c.User.ID, c.Repo.Repository.ID, true) - case "unwatch": - err = models.WatchRepo(c.User.ID, c.Repo.Repository.ID, false) - case "star": - err = models.StarRepo(c.User.ID, c.Repo.Repository.ID, true) - case "unstar": - err = models.StarRepo(c.User.ID, c.Repo.Repository.ID, false) - case "desc": // FIXME: this is not used - if !c.Repo.IsOwner() { - c.Error(404) - return - } - - c.Repo.Repository.Description = c.Query("desc") - c.Repo.Repository.Website = c.Query("site") - err = models.UpdateRepository(c.Repo.Repository, false) - } - - if err != nil { - c.Handle(500, fmt.Sprintf("Action (%s)", c.Params(":action")), err) - return - } - - redirectTo := c.Query("redirect_to") - if len(redirectTo) == 0 { - redirectTo = c.Repo.RepoLink - } - c.Redirect(redirectTo) -} - -func Download(c *context.Context) { - var ( - uri = c.Params("*") - refName string - ext string - archivePath string - archiveType git.ArchiveType - ) - - switch { - case strings.HasSuffix(uri, ".zip"): - ext = ".zip" - archivePath = path.Join(c.Repo.GitRepo.Path, "archives/zip") - archiveType = git.ZIP - case strings.HasSuffix(uri, ".tar.gz"): - ext = ".tar.gz" - archivePath = path.Join(c.Repo.GitRepo.Path, "archives/targz") - archiveType = git.TARGZ - default: - log.Trace("Unknown format: %s", uri) - c.Error(404) - return - } - refName = strings.TrimSuffix(uri, ext) - - if !com.IsDir(archivePath) { - if err := os.MkdirAll(archivePath, os.ModePerm); err != nil { - c.Handle(500, "Download -> os.MkdirAll(archivePath)", err) - return - } - } - - // Get corresponding commit. - var ( - commit *git.Commit - err error - ) - gitRepo := c.Repo.GitRepo - if gitRepo.IsBranchExist(refName) { - commit, err = gitRepo.GetBranchCommit(refName) - if err != nil { - c.Handle(500, "GetBranchCommit", err) - return - } - } else if gitRepo.IsTagExist(refName) { - commit, err = gitRepo.GetTagCommit(refName) - if err != nil { - c.Handle(500, "GetTagCommit", err) - return - } - } else if len(refName) >= 7 && len(refName) <= 40 { - commit, err = gitRepo.GetCommit(refName) - if err != nil { - c.NotFound() - return - } - } else { - c.NotFound() - return - } - - archivePath = path.Join(archivePath, tool.ShortSHA1(commit.ID.String())+ext) - if !com.IsFile(archivePath) { - if err := commit.CreateArchive(archivePath, archiveType); err != nil { - c.Handle(500, "Download -> CreateArchive "+archivePath, err) - return - } - } - - c.ServeFile(archivePath, c.Repo.Repository.Name+"-"+refName+ext) -} diff --git a/routers/repo/setting.go b/routers/repo/setting.go deleted file mode 100644 index 9168b04a..00000000 --- a/routers/repo/setting.go +++ /dev/null @@ -1,631 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "fmt" - "strings" - "time" - - log "gopkg.in/clog.v1" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/mailer" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - SETTINGS_OPTIONS = "repo/settings/options" - SETTINGS_COLLABORATION = "repo/settings/collaboration" - SETTINGS_BRANCHES = "repo/settings/branches" - SETTINGS_PROTECTED_BRANCH = "repo/settings/protected_branch" - SETTINGS_GITHOOKS = "repo/settings/githooks" - SETTINGS_GITHOOK_EDIT = "repo/settings/githook_edit" - SETTINGS_DEPLOY_KEYS = "repo/settings/deploy_keys" -) - -func Settings(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsOptions"] = true - c.HTML(200, SETTINGS_OPTIONS) -} - -func SettingsPost(c *context.Context, f form.RepoSetting) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsOptions"] = true - - repo := c.Repo.Repository - - switch c.Query("action") { - case "update": - if c.HasError() { - c.HTML(200, SETTINGS_OPTIONS) - return - } - - isNameChanged := false - oldRepoName := repo.Name - newRepoName := f.RepoName - // Check if repository name has been changed. - if repo.LowerName != strings.ToLower(newRepoName) { - isNameChanged = true - if err := models.ChangeRepositoryName(c.Repo.Owner, repo.Name, newRepoName); err != nil { - c.Data["Err_RepoName"] = true - switch { - case models.IsErrRepoAlreadyExist(err): - c.RenderWithErr(c.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, &f) - case models.IsErrNameReserved(err): - c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), SETTINGS_OPTIONS, &f) - case models.IsErrNamePatternNotAllowed(err): - c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), SETTINGS_OPTIONS, &f) - default: - c.Handle(500, "ChangeRepositoryName", err) - } - return - } - - log.Trace("Repository name changed: %s/%s -> %s", c.Repo.Owner.Name, repo.Name, newRepoName) - } - // In case it's just a case change. - repo.Name = newRepoName - repo.LowerName = strings.ToLower(newRepoName) - - repo.Description = f.Description - repo.Website = f.Website - - // Visibility of forked repository is forced sync with base repository. - if repo.IsFork { - f.Private = repo.BaseRepo.IsPrivate - } - - visibilityChanged := repo.IsPrivate != f.Private - repo.IsPrivate = f.Private - if err := models.UpdateRepository(repo, visibilityChanged); err != nil { - c.Handle(500, "UpdateRepository", err) - return - } - log.Trace("Repository basic settings updated: %s/%s", c.Repo.Owner.Name, repo.Name) - - if isNameChanged { - if err := models.RenameRepoAction(c.User, oldRepoName, repo); err != nil { - log.Error(4, "RenameRepoAction: %v", err) - } - } - - c.Flash.Success(c.Tr("repo.settings.update_settings_success")) - c.Redirect(repo.Link() + "/settings") - - case "mirror": - if !repo.IsMirror { - c.Handle(404, "", nil) - return - } - - if f.Interval > 0 { - c.Repo.Mirror.EnablePrune = f.EnablePrune - c.Repo.Mirror.Interval = f.Interval - c.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(f.Interval) * time.Hour) - if err := models.UpdateMirror(c.Repo.Mirror); err != nil { - c.Handle(500, "UpdateMirror", err) - return - } - } - if err := c.Repo.Mirror.SaveAddress(f.MirrorAddress); err != nil { - c.Handle(500, "SaveAddress", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.update_settings_success")) - c.Redirect(repo.Link() + "/settings") - - case "mirror-sync": - if !repo.IsMirror { - c.Handle(404, "", nil) - return - } - - go models.MirrorQueue.Add(repo.ID) - c.Flash.Info(c.Tr("repo.settings.mirror_sync_in_progress")) - c.Redirect(repo.Link() + "/settings") - - case "advanced": - repo.EnableWiki = f.EnableWiki - repo.AllowPublicWiki = f.AllowPublicWiki - repo.EnableExternalWiki = f.EnableExternalWiki - repo.ExternalWikiURL = f.ExternalWikiURL - repo.EnableIssues = f.EnableIssues - repo.AllowPublicIssues = f.AllowPublicIssues - repo.EnableExternalTracker = f.EnableExternalTracker - repo.ExternalTrackerURL = f.ExternalTrackerURL - repo.ExternalTrackerFormat = f.TrackerURLFormat - repo.ExternalTrackerStyle = f.TrackerIssueStyle - repo.EnablePulls = f.EnablePulls - - if err := models.UpdateRepository(repo, false); err != nil { - c.Handle(500, "UpdateRepository", err) - return - } - log.Trace("Repository advanced settings updated: %s/%s", c.Repo.Owner.Name, repo.Name) - - c.Flash.Success(c.Tr("repo.settings.update_settings_success")) - c.Redirect(c.Repo.RepoLink + "/settings") - - case "convert": - if !c.Repo.IsOwner() { - c.Error(404) - return - } - if repo.Name != f.RepoName { - c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) - return - } - - if c.Repo.Owner.IsOrganization() { - if !c.Repo.Owner.IsOwnedBy(c.User.ID) { - c.Error(404) - return - } - } - - if !repo.IsMirror { - c.Error(404) - return - } - repo.IsMirror = false - - if _, err := models.CleanUpMigrateInfo(repo); err != nil { - c.Handle(500, "CleanUpMigrateInfo", err) - return - } else if err = models.DeleteMirrorByRepoID(c.Repo.Repository.ID); err != nil { - c.Handle(500, "DeleteMirrorByRepoID", err) - return - } - log.Trace("Repository converted from mirror to regular: %s/%s", c.Repo.Owner.Name, repo.Name) - c.Flash.Success(c.Tr("repo.settings.convert_succeed")) - c.Redirect(setting.AppSubURL + "/" + c.Repo.Owner.Name + "/" + repo.Name) - - case "transfer": - if !c.Repo.IsOwner() { - c.Error(404) - return - } - if repo.Name != f.RepoName { - c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) - return - } - - if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin { - if !c.Repo.Owner.IsOwnedBy(c.User.ID) { - c.Error(404) - return - } - } - - newOwner := c.Query("new_owner_name") - isExist, err := models.IsUserExist(0, newOwner) - if err != nil { - c.Handle(500, "IsUserExist", err) - return - } else if !isExist { - c.RenderWithErr(c.Tr("form.enterred_invalid_owner_name"), SETTINGS_OPTIONS, nil) - return - } - - if err = models.TransferOwnership(c.User, newOwner, repo); err != nil { - if models.IsErrRepoAlreadyExist(err) { - c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), SETTINGS_OPTIONS, nil) - } else { - c.Handle(500, "TransferOwnership", err) - } - return - } - log.Trace("Repository transfered: %s/%s -> %s", c.Repo.Owner.Name, repo.Name, newOwner) - c.Flash.Success(c.Tr("repo.settings.transfer_succeed")) - c.Redirect(setting.AppSubURL + "/" + newOwner + "/" + repo.Name) - - case "delete": - if !c.Repo.IsOwner() { - c.Error(404) - return - } - if repo.Name != f.RepoName { - c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) - return - } - - if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin { - if !c.Repo.Owner.IsOwnedBy(c.User.ID) { - c.Error(404) - return - } - } - - if err := models.DeleteRepository(c.Repo.Owner.ID, repo.ID); err != nil { - c.Handle(500, "DeleteRepository", err) - return - } - log.Trace("Repository deleted: %s/%s", c.Repo.Owner.Name, repo.Name) - - c.Flash.Success(c.Tr("repo.settings.deletion_success")) - c.Redirect(c.Repo.Owner.DashboardLink()) - - case "delete-wiki": - if !c.Repo.IsOwner() { - c.Error(404) - return - } - if repo.Name != f.RepoName { - c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) - return - } - - if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin { - if !c.Repo.Owner.IsOwnedBy(c.User.ID) { - c.Error(404) - return - } - } - - repo.DeleteWiki() - log.Trace("Repository wiki deleted: %s/%s", c.Repo.Owner.Name, repo.Name) - - repo.EnableWiki = false - if err := models.UpdateRepository(repo, false); err != nil { - c.Handle(500, "UpdateRepository", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.wiki_deletion_success")) - c.Redirect(c.Repo.RepoLink + "/settings") - - default: - c.Handle(404, "", nil) - } -} - -func SettingsCollaboration(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsCollaboration"] = true - - users, err := c.Repo.Repository.GetCollaborators() - if err != nil { - c.Handle(500, "GetCollaborators", err) - return - } - c.Data["Collaborators"] = users - - c.HTML(200, SETTINGS_COLLABORATION) -} - -func SettingsCollaborationPost(c *context.Context) { - name := strings.ToLower(c.Query("collaborator")) - if len(name) == 0 || c.Repo.Owner.LowerName == name { - c.Redirect(setting.AppSubURL + c.Req.URL.Path) - return - } - - u, err := models.GetUserByName(name) - if err != nil { - if errors.IsUserNotExist(err) { - c.Flash.Error(c.Tr("form.user_not_exist")) - c.Redirect(setting.AppSubURL + c.Req.URL.Path) - } else { - c.Handle(500, "GetUserByName", err) - } - return - } - - // Organization is not allowed to be added as a collaborator - if u.IsOrganization() { - c.Flash.Error(c.Tr("repo.settings.org_not_allowed_to_be_collaborator")) - c.Redirect(setting.AppSubURL + c.Req.URL.Path) - return - } - - if err = c.Repo.Repository.AddCollaborator(u); err != nil { - c.Handle(500, "AddCollaborator", err) - return - } - - if setting.Service.EnableNotifyMail { - mailer.SendCollaboratorMail(models.NewMailerUser(u), models.NewMailerUser(c.User), models.NewMailerRepo(c.Repo.Repository)) - } - - c.Flash.Success(c.Tr("repo.settings.add_collaborator_success")) - c.Redirect(setting.AppSubURL + c.Req.URL.Path) -} - -func ChangeCollaborationAccessMode(c *context.Context) { - if err := c.Repo.Repository.ChangeCollaborationAccessMode( - c.QueryInt64("uid"), - models.AccessMode(c.QueryInt("mode"))); err != nil { - log.Error(2, "ChangeCollaborationAccessMode: %v", err) - return - } - - c.Status(204) -} - -func DeleteCollaboration(c *context.Context) { - if err := c.Repo.Repository.DeleteCollaboration(c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteCollaboration: " + err.Error()) - } else { - c.Flash.Success(c.Tr("repo.settings.remove_collaborator_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/settings/collaboration", - }) -} - -func SettingsBranches(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.branches") - c.Data["PageIsSettingsBranches"] = true - - if c.Repo.Repository.IsBare { - c.Flash.Info(c.Tr("repo.settings.branches_bare"), true) - c.HTML(200, SETTINGS_BRANCHES) - return - } - - protectBranches, err := models.GetProtectBranchesByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Handle(500, "GetProtectBranchesByRepoID", err) - return - } - - // Filter out deleted branches - branches := make([]string, 0, len(protectBranches)) - for i := range protectBranches { - if c.Repo.GitRepo.IsBranchExist(protectBranches[i].Name) { - branches = append(branches, protectBranches[i].Name) - } - } - c.Data["ProtectBranches"] = branches - - c.HTML(200, SETTINGS_BRANCHES) -} - -func UpdateDefaultBranch(c *context.Context) { - branch := c.Query("branch") - if c.Repo.GitRepo.IsBranchExist(branch) && - c.Repo.Repository.DefaultBranch != branch { - c.Repo.Repository.DefaultBranch = branch - if err := c.Repo.GitRepo.SetDefaultBranch(branch); err != nil { - if !git.IsErrUnsupportedVersion(err) { - c.Handle(500, "SetDefaultBranch", err) - return - } - - c.Flash.Warning(c.Tr("repo.settings.update_default_branch_unsupported")) - c.Redirect(c.Repo.RepoLink + "/settings/branches") - return - } - } - - if err := models.UpdateRepository(c.Repo.Repository, false); err != nil { - c.Handle(500, "UpdateRepository", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.update_default_branch_success")) - c.Redirect(c.Repo.RepoLink + "/settings/branches") -} - -func SettingsProtectedBranch(c *context.Context) { - branch := c.Params("*") - if !c.Repo.GitRepo.IsBranchExist(branch) { - c.NotFound() - return - } - - c.Data["Title"] = c.Tr("repo.settings.protected_branches") + " - " + branch - c.Data["PageIsSettingsBranches"] = true - - protectBranch, err := models.GetProtectBranchOfRepoByName(c.Repo.Repository.ID, branch) - if err != nil { - if !models.IsErrBranchNotExist(err) { - c.Handle(500, "GetProtectBranchOfRepoByName", err) - return - } - - // No options found, create defaults. - protectBranch = &models.ProtectBranch{ - Name: branch, - } - } - - if c.Repo.Owner.IsOrganization() { - users, err := c.Repo.Repository.GetWriters() - if err != nil { - c.Handle(500, "Repo.Repository.GetPushers", err) - return - } - c.Data["Users"] = users - c.Data["whitelist_users"] = protectBranch.WhitelistUserIDs - - teams, err := c.Repo.Owner.TeamsHaveAccessToRepo(c.Repo.Repository.ID, models.ACCESS_MODE_WRITE) - if err != nil { - c.Handle(500, "Repo.Owner.TeamsHaveAccessToRepo", err) - return - } - c.Data["Teams"] = teams - c.Data["whitelist_teams"] = protectBranch.WhitelistTeamIDs - } - - c.Data["Branch"] = protectBranch - c.HTML(200, SETTINGS_PROTECTED_BRANCH) -} - -func SettingsProtectedBranchPost(c *context.Context, f form.ProtectBranch) { - branch := c.Params("*") - if !c.Repo.GitRepo.IsBranchExist(branch) { - c.NotFound() - return - } - - protectBranch, err := models.GetProtectBranchOfRepoByName(c.Repo.Repository.ID, branch) - if err != nil { - if !models.IsErrBranchNotExist(err) { - c.Handle(500, "GetProtectBranchOfRepoByName", err) - return - } - - // No options found, create defaults. - protectBranch = &models.ProtectBranch{ - RepoID: c.Repo.Repository.ID, - Name: branch, - } - } - - protectBranch.Protected = f.Protected - protectBranch.RequirePullRequest = f.RequirePullRequest - protectBranch.EnableWhitelist = f.EnableWhitelist - if c.Repo.Owner.IsOrganization() { - err = models.UpdateOrgProtectBranch(c.Repo.Repository, protectBranch, f.WhitelistUsers, f.WhitelistTeams) - } else { - err = models.UpdateProtectBranch(protectBranch) - } - if err != nil { - c.Handle(500, "UpdateOrgProtectBranch/UpdateProtectBranch", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.update_protect_branch_success")) - c.Redirect(fmt.Sprintf("%s/settings/branches/%s", c.Repo.RepoLink, branch)) -} - -func SettingsGitHooks(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.githooks") - c.Data["PageIsSettingsGitHooks"] = true - - hooks, err := c.Repo.GitRepo.Hooks() - if err != nil { - c.Handle(500, "Hooks", err) - return - } - c.Data["Hooks"] = hooks - - c.HTML(200, SETTINGS_GITHOOKS) -} - -func SettingsGitHooksEdit(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.githooks") - c.Data["PageIsSettingsGitHooks"] = true - c.Data["RequireSimpleMDE"] = true - - name := c.Params(":name") - hook, err := c.Repo.GitRepo.GetHook(name) - if err != nil { - if err == git.ErrNotValidHook { - c.Handle(404, "GetHook", err) - } else { - c.Handle(500, "GetHook", err) - } - return - } - c.Data["Hook"] = hook - c.HTML(200, SETTINGS_GITHOOK_EDIT) -} - -func SettingsGitHooksEditPost(c *context.Context) { - name := c.Params(":name") - hook, err := c.Repo.GitRepo.GetHook(name) - if err != nil { - if err == git.ErrNotValidHook { - c.Handle(404, "GetHook", err) - } else { - c.Handle(500, "GetHook", err) - } - return - } - hook.Content = c.Query("content") - if err = hook.Update(); err != nil { - c.Handle(500, "hook.Update", err) - return - } - c.Redirect(c.Data["Link"].(string)) -} - -func SettingsDeployKeys(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.deploy_keys") - c.Data["PageIsSettingsKeys"] = true - - keys, err := models.ListDeployKeys(c.Repo.Repository.ID) - if err != nil { - c.Handle(500, "ListDeployKeys", err) - return - } - c.Data["Deploykeys"] = keys - - c.HTML(200, SETTINGS_DEPLOY_KEYS) -} - -func SettingsDeployKeysPost(c *context.Context, f form.AddSSHKey) { - c.Data["Title"] = c.Tr("repo.settings.deploy_keys") - c.Data["PageIsSettingsKeys"] = true - - keys, err := models.ListDeployKeys(c.Repo.Repository.ID) - if err != nil { - c.Handle(500, "ListDeployKeys", err) - return - } - c.Data["Deploykeys"] = keys - - if c.HasError() { - c.HTML(200, SETTINGS_DEPLOY_KEYS) - return - } - - content, err := models.CheckPublicKeyString(f.Content) - if err != nil { - if models.IsErrKeyUnableVerify(err) { - c.Flash.Info(c.Tr("form.unable_verify_ssh_key")) - } else { - c.Data["HasError"] = true - c.Data["Err_Content"] = true - c.Flash.Error(c.Tr("form.invalid_ssh_key", err.Error())) - c.Redirect(c.Repo.RepoLink + "/settings/keys") - return - } - } - - key, err := models.AddDeployKey(c.Repo.Repository.ID, f.Title, content) - if err != nil { - c.Data["HasError"] = true - switch { - case models.IsErrKeyAlreadyExist(err): - c.Data["Err_Content"] = true - c.RenderWithErr(c.Tr("repo.settings.key_been_used"), SETTINGS_DEPLOY_KEYS, &f) - case models.IsErrKeyNameAlreadyUsed(err): - c.Data["Err_Title"] = true - c.RenderWithErr(c.Tr("repo.settings.key_name_used"), SETTINGS_DEPLOY_KEYS, &f) - default: - c.Handle(500, "AddDeployKey", err) - } - return - } - - log.Trace("Deploy key added: %d", c.Repo.Repository.ID) - c.Flash.Success(c.Tr("repo.settings.add_key_success", key.Name)) - c.Redirect(c.Repo.RepoLink + "/settings/keys") -} - -func DeleteDeployKey(c *context.Context) { - if err := models.DeleteDeployKey(c.User, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteDeployKey: " + err.Error()) - } else { - c.Flash.Success(c.Tr("repo.settings.deploy_key_deletion_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/settings/keys", - }) -} diff --git a/routers/repo/view.go b/routers/repo/view.go deleted file mode 100644 index 1ea25d51..00000000 --- a/routers/repo/view.go +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "bytes" - "fmt" - gotemplate "html/template" - "io/ioutil" - "path" - "strings" - - "github.com/Unknwon/paginater" - log "gopkg.in/clog.v1" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/markup" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/template" - "github.com/gogits/gogs/pkg/template/highlight" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - BARE = "repo/bare" - HOME = "repo/home" - WATCHERS = "repo/watchers" - FORKS = "repo/forks" -) - -func renderDirectory(c *context.Context, treeLink string) { - tree, err := c.Repo.Commit.SubTree(c.Repo.TreePath) - if err != nil { - c.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err) - return - } - - entries, err := tree.ListEntries() - if err != nil { - c.ServerError("ListEntries", err) - return - } - entries.Sort() - - c.Data["Files"], err = entries.GetCommitsInfoWithCustomConcurrency(c.Repo.Commit, c.Repo.TreePath, setting.Repository.CommitsFetchConcurrency) - if err != nil { - c.ServerError("GetCommitsInfoWithCustomConcurrency", err) - return - } - - var readmeFile *git.Blob - for _, entry := range entries { - if entry.IsDir() || !markup.IsReadmeFile(entry.Name()) { - continue - } - - // TODO: collect all possible README files and show with priority. - readmeFile = entry.Blob() - break - } - - if readmeFile != nil { - c.Data["RawFileLink"] = "" - c.Data["ReadmeInList"] = true - c.Data["ReadmeExist"] = true - - dataRc, err := readmeFile.Data() - if err != nil { - c.ServerError("readmeFile.Data", err) - return - } - - buf := make([]byte, 1024) - n, _ := dataRc.Read(buf) - buf = buf[:n] - - isTextFile := tool.IsTextFile(buf) - c.Data["IsTextFile"] = isTextFile - c.Data["FileName"] = readmeFile.Name() - if isTextFile { - d, _ := ioutil.ReadAll(dataRc) - buf = append(buf, d...) - - switch markup.Detect(readmeFile.Name()) { - case markup.MARKDOWN: - c.Data["IsMarkdown"] = true - buf = markup.Markdown(buf, treeLink, c.Repo.Repository.ComposeMetas()) - case markup.ORG_MODE: - c.Data["IsMarkdown"] = true - buf = markup.OrgMode(buf, treeLink, c.Repo.Repository.ComposeMetas()) - case markup.IPYTHON_NOTEBOOK: - c.Data["IsIPythonNotebook"] = true - c.Data["RawFileLink"] = c.Repo.RepoLink + "/raw/" + path.Join(c.Repo.BranchName, c.Repo.TreePath, readmeFile.Name()) - default: - buf = bytes.Replace(buf, []byte("\n"), []byte(`
`), -1) - } - c.Data["FileContent"] = string(buf) - } - } - - // Show latest commit info of repository in table header, - // or of directory if not in root directory. - latestCommit := c.Repo.Commit - if len(c.Repo.TreePath) > 0 { - latestCommit, err = c.Repo.Commit.GetCommitByPath(c.Repo.TreePath) - if err != nil { - c.ServerError("GetCommitByPath", err) - return - } - } - c.Data["LatestCommit"] = latestCommit - c.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) - - if c.Repo.CanEnableEditor() { - c.Data["CanAddFile"] = true - c.Data["CanUploadFile"] = setting.Repository.Upload.Enabled - } -} - -func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink string) { - c.Data["IsViewFile"] = true - - blob := entry.Blob() - dataRc, err := blob.Data() - if err != nil { - c.Handle(500, "Data", err) - return - } - - c.Data["FileSize"] = blob.Size() - c.Data["FileName"] = blob.Name() - c.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name()) - c.Data["RawFileLink"] = rawLink + "/" + c.Repo.TreePath - - buf := make([]byte, 1024) - n, _ := dataRc.Read(buf) - buf = buf[:n] - - isTextFile := tool.IsTextFile(buf) - c.Data["IsTextFile"] = isTextFile - - // Assume file is not editable first. - if !isTextFile { - c.Data["EditFileTooltip"] = c.Tr("repo.editor.cannot_edit_non_text_files") - } - - canEnableEditor := c.Repo.CanEnableEditor() - switch { - case isTextFile: - if blob.Size() >= setting.UI.MaxDisplayFileSize { - c.Data["IsFileTooLarge"] = true - break - } - - c.Data["ReadmeExist"] = markup.IsReadmeFile(blob.Name()) - - d, _ := ioutil.ReadAll(dataRc) - buf = append(buf, d...) - - switch markup.Detect(blob.Name()) { - case markup.MARKDOWN: - c.Data["IsMarkdown"] = true - c.Data["FileContent"] = string(markup.Markdown(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas())) - case markup.ORG_MODE: - c.Data["IsMarkdown"] = true - c.Data["FileContent"] = string(markup.OrgMode(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas())) - case markup.IPYTHON_NOTEBOOK: - c.Data["IsIPythonNotebook"] = true - default: - // Building code view blocks with line number on server side. - var fileContent string - if err, content := template.ToUTF8WithErr(buf); err != nil { - if err != nil { - log.Error(4, "ToUTF8WithErr: %s", err) - } - fileContent = string(buf) - } else { - fileContent = content - } - - var output bytes.Buffer - lines := strings.Split(fileContent, "\n") - for index, line := range lines { - output.WriteString(fmt.Sprintf(`
  • %s
  • `, index+1, index+1, gotemplate.HTMLEscapeString(strings.TrimRight(line, "\r"))) + "\n") - } - c.Data["FileContent"] = gotemplate.HTML(output.String()) - - output.Reset() - for i := 0; i < len(lines); i++ { - output.WriteString(fmt.Sprintf(`%d`, i+1, i+1)) - } - c.Data["LineNums"] = gotemplate.HTML(output.String()) - } - - if canEnableEditor { - c.Data["CanEditFile"] = true - c.Data["EditFileTooltip"] = c.Tr("repo.editor.edit_this_file") - } else if !c.Repo.IsViewBranch { - c.Data["EditFileTooltip"] = c.Tr("repo.editor.must_be_on_a_branch") - } else if !c.Repo.IsWriter() { - c.Data["EditFileTooltip"] = c.Tr("repo.editor.fork_before_edit") - } - - case tool.IsPDFFile(buf): - c.Data["IsPDFFile"] = true - case tool.IsVideoFile(buf): - c.Data["IsVideoFile"] = true - case tool.IsImageFile(buf): - c.Data["IsImageFile"] = true - } - - if canEnableEditor { - c.Data["CanDeleteFile"] = true - c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.delete_this_file") - } else if !c.Repo.IsViewBranch { - c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.must_be_on_a_branch") - } else if !c.Repo.IsWriter() { - c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.must_have_write_access") - } -} - -func setEditorconfigIfExists(c *context.Context) { - ec, err := c.Repo.GetEditorconfig() - if err != nil && !git.IsErrNotExist(err) { - log.Trace("setEditorconfigIfExists.GetEditorconfig [%d]: %v", c.Repo.Repository.ID, err) - return - } - c.Data["Editorconfig"] = ec -} - -func Home(c *context.Context) { - c.Data["PageIsViewFiles"] = true - - if c.Repo.Repository.IsBare { - c.HTML(200, BARE) - return - } - - title := c.Repo.Repository.Owner.Name + "/" + c.Repo.Repository.Name - if len(c.Repo.Repository.Description) > 0 { - title += ": " + c.Repo.Repository.Description - } - c.Data["Title"] = title - if c.Repo.BranchName != c.Repo.Repository.DefaultBranch { - c.Data["Title"] = title + " @ " + c.Repo.BranchName - } - c.Data["RequireHighlightJS"] = true - - branchLink := c.Repo.RepoLink + "/src/" + c.Repo.BranchName - treeLink := branchLink - rawLink := c.Repo.RepoLink + "/raw/" + c.Repo.BranchName - - isRootDir := false - if len(c.Repo.TreePath) > 0 { - treeLink += "/" + c.Repo.TreePath - } else { - isRootDir = true - - // Only show Git stats panel when view root directory - var err error - c.Repo.CommitsCount, err = c.Repo.Commit.CommitsCount() - if err != nil { - c.Handle(500, "CommitsCount", err) - return - } - c.Data["CommitsCount"] = c.Repo.CommitsCount - } - c.Data["PageIsRepoHome"] = isRootDir - - // Get current entry user currently looking at. - entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath) - if err != nil { - c.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err) - return - } - - if entry.IsDir() { - renderDirectory(c, treeLink) - } else { - renderFile(c, entry, treeLink, rawLink) - } - if c.Written() { - return - } - - setEditorconfigIfExists(c) - if c.Written() { - return - } - - var treeNames []string - paths := make([]string, 0, 5) - if len(c.Repo.TreePath) > 0 { - treeNames = strings.Split(c.Repo.TreePath, "/") - for i := range treeNames { - paths = append(paths, strings.Join(treeNames[:i+1], "/")) - } - - c.Data["HasParentPath"] = true - if len(paths)-2 >= 0 { - c.Data["ParentPath"] = "/" + paths[len(paths)-2] - } - } - - c.Data["Paths"] = paths - c.Data["TreeLink"] = treeLink - c.Data["TreeNames"] = treeNames - c.Data["BranchLink"] = branchLink - c.HTML(200, HOME) -} - -func RenderUserCards(c *context.Context, total int, getter func(page int) ([]*models.User, error), tpl string) { - page := c.QueryInt("page") - if page <= 0 { - page = 1 - } - pager := paginater.New(total, models.ItemsPerPage, page, 5) - c.Data["Page"] = pager - - items, err := getter(pager.Current()) - if err != nil { - c.Handle(500, "getter", err) - return - } - c.Data["Cards"] = items - - c.HTML(200, tpl) -} - -func Watchers(c *context.Context) { - c.Data["Title"] = c.Tr("repo.watchers") - c.Data["CardsTitle"] = c.Tr("repo.watchers") - c.Data["PageIsWatchers"] = true - RenderUserCards(c, c.Repo.Repository.NumWatches, c.Repo.Repository.GetWatchers, WATCHERS) -} - -func Stars(c *context.Context) { - c.Data["Title"] = c.Tr("repo.stargazers") - c.Data["CardsTitle"] = c.Tr("repo.stargazers") - c.Data["PageIsStargazers"] = true - RenderUserCards(c, c.Repo.Repository.NumStars, c.Repo.Repository.GetStargazers, WATCHERS) -} - -func Forks(c *context.Context) { - c.Data["Title"] = c.Tr("repos.forks") - - forks, err := c.Repo.Repository.GetForks() - if err != nil { - c.Handle(500, "GetForks", err) - return - } - - for _, fork := range forks { - if err = fork.GetOwner(); err != nil { - c.Handle(500, "GetOwner", err) - return - } - } - c.Data["Forks"] = forks - - c.HTML(200, FORKS) -} diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go deleted file mode 100644 index c572d446..00000000 --- a/routers/repo/webhook.go +++ /dev/null @@ -1,558 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/Unknwon/com" - - git "github.com/gogits/git-module" - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - WEBHOOKS = "repo/settings/webhook/base" - WEBHOOK_NEW = "repo/settings/webhook/new" - ORG_WEBHOOK_NEW = "org/settings/webhook_new" -) - -func Webhooks(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.hooks") - c.Data["PageIsSettingsHooks"] = true - c.Data["BaseLink"] = c.Repo.RepoLink - c.Data["Description"] = c.Tr("repo.settings.hooks_desc", "https://github.com/gogits/go-gogs-client/wiki/Repositories-Webhooks") - c.Data["Types"] = setting.Webhook.Types - - ws, err := models.GetWebhooksByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Handle(500, "GetWebhooksByRepoID", err) - return - } - c.Data["Webhooks"] = ws - - c.HTML(200, WEBHOOKS) -} - -type OrgRepoCtx struct { - OrgID int64 - RepoID int64 - Link string - NewTemplate string -} - -// getOrgRepoCtx determines whether this is a repo context or organization context. -func getOrgRepoCtx(c *context.Context) (*OrgRepoCtx, error) { - if len(c.Repo.RepoLink) > 0 { - c.Data["PageIsRepositoryContext"] = true - return &OrgRepoCtx{ - RepoID: c.Repo.Repository.ID, - Link: c.Repo.RepoLink, - NewTemplate: WEBHOOK_NEW, - }, nil - } - - if len(c.Org.OrgLink) > 0 { - c.Data["PageIsOrganizationContext"] = true - return &OrgRepoCtx{ - OrgID: c.Org.Organization.ID, - Link: c.Org.OrgLink, - NewTemplate: ORG_WEBHOOK_NEW, - }, nil - } - - return nil, errors.New("Unable to set OrgRepo context") -} - -func checkHookType(c *context.Context) string { - hookType := strings.ToLower(c.Params(":type")) - if !com.IsSliceContainsStr(setting.Webhook.Types, hookType) { - c.Handle(404, "checkHookType", nil) - return "" - } - return hookType -} - -func WebhooksNew(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.add_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Handle(500, "getOrgRepoCtx", err) - return - } - - c.Data["HookType"] = checkHookType(c) - if c.Written() { - return - } - c.Data["BaseLink"] = orCtx.Link - - c.HTML(200, orCtx.NewTemplate) -} - -func ParseHookEvent(f form.Webhook) *models.HookEvent { - return &models.HookEvent{ - PushOnly: f.PushOnly(), - SendEverything: f.SendEverything(), - ChooseEvents: f.ChooseEvents(), - HookEvents: models.HookEvents{ - Create: f.Create, - Delete: f.Delete, - Fork: f.Fork, - Push: f.Push, - Issues: f.Issues, - IssueComment: f.IssueComment, - PullRequest: f.PullRequest, - Release: f.Release, - }, - } -} - -func WebHooksNewPost(c *context.Context, f form.NewWebhook) { - c.Data["Title"] = c.Tr("repo.settings.add_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} - c.Data["HookType"] = "gogs" - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Handle(500, "getOrgRepoCtx", err) - return - } - c.Data["BaseLink"] = orCtx.Link - - if c.HasError() { - c.HTML(200, orCtx.NewTemplate) - return - } - - contentType := models.JSON - if models.HookContentType(f.ContentType) == models.FORM { - contentType = models.FORM - } - - w := &models.Webhook{ - RepoID: orCtx.RepoID, - URL: f.PayloadURL, - ContentType: contentType, - Secret: f.Secret, - HookEvent: ParseHookEvent(f.Webhook), - IsActive: f.Active, - HookTaskType: models.GOGS, - OrgID: orCtx.OrgID, - } - if err := w.UpdateEvent(); err != nil { - c.Handle(500, "UpdateEvent", err) - return - } else if err := models.CreateWebhook(w); err != nil { - c.Handle(500, "CreateWebhook", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.add_hook_success")) - c.Redirect(orCtx.Link + "/settings/hooks") -} - -func SlackHooksNewPost(c *context.Context, f form.NewSlackHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Handle(500, "getOrgRepoCtx", err) - return - } - - if c.HasError() { - c.HTML(200, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&models.SlackMeta{ - Channel: f.Channel, - Username: f.Username, - IconURL: f.IconURL, - Color: f.Color, - }) - if err != nil { - c.Handle(500, "Marshal", err) - return - } - - w := &models.Webhook{ - RepoID: orCtx.RepoID, - URL: f.PayloadURL, - ContentType: models.JSON, - HookEvent: ParseHookEvent(f.Webhook), - IsActive: f.Active, - HookTaskType: models.SLACK, - Meta: string(meta), - OrgID: orCtx.OrgID, - } - if err := w.UpdateEvent(); err != nil { - c.Handle(500, "UpdateEvent", err) - return - } else if err := models.CreateWebhook(w); err != nil { - c.Handle(500, "CreateWebhook", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.add_hook_success")) - c.Redirect(orCtx.Link + "/settings/hooks") -} - -// FIXME: merge logic to Slack -func DiscordHooksNewPost(c *context.Context, f form.NewDiscordHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Handle(500, "getOrgRepoCtx", err) - return - } - - if c.HasError() { - c.HTML(200, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&models.SlackMeta{ - Username: f.Username, - IconURL: f.IconURL, - Color: f.Color, - }) - if err != nil { - c.Handle(500, "Marshal", err) - return - } - - w := &models.Webhook{ - RepoID: orCtx.RepoID, - URL: f.PayloadURL, - ContentType: models.JSON, - HookEvent: ParseHookEvent(f.Webhook), - IsActive: f.Active, - HookTaskType: models.DISCORD, - Meta: string(meta), - OrgID: orCtx.OrgID, - } - if err := w.UpdateEvent(); err != nil { - c.Handle(500, "UpdateEvent", err) - return - } else if err := models.CreateWebhook(w); err != nil { - c.Handle(500, "CreateWebhook", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.add_hook_success")) - c.Redirect(orCtx.Link + "/settings/hooks") -} - -func checkWebhook(c *context.Context) (*OrgRepoCtx, *models.Webhook) { - c.Data["RequireHighlightJS"] = true - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Handle(500, "getOrgRepoCtx", err) - return nil, nil - } - c.Data["BaseLink"] = orCtx.Link - - var w *models.Webhook - if orCtx.RepoID > 0 { - w, err = models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - } else { - w, err = models.GetWebhookByOrgID(c.Org.Organization.ID, c.ParamsInt64(":id")) - } - if err != nil { - c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err) - return nil, nil - } - - switch w.HookTaskType { - case models.SLACK: - c.Data["SlackHook"] = w.GetSlackHook() - c.Data["HookType"] = "slack" - case models.DISCORD: - c.Data["SlackHook"] = w.GetSlackHook() - c.Data["HookType"] = "discord" - default: - c.Data["HookType"] = "gogs" - } - - c.Data["History"], err = w.History(1) - if err != nil { - c.Handle(500, "History", err) - } - return orCtx, w -} - -func WebHooksEdit(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.update_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(c) - if c.Written() { - return - } - c.Data["Webhook"] = w - - c.HTML(200, orCtx.NewTemplate) -} - -func WebHooksEditPost(c *context.Context, f form.NewWebhook) { - c.Data["Title"] = c.Tr("repo.settings.update_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(c) - if c.Written() { - return - } - c.Data["Webhook"] = w - - if c.HasError() { - c.HTML(200, orCtx.NewTemplate) - return - } - - contentType := models.JSON - if models.HookContentType(f.ContentType) == models.FORM { - contentType = models.FORM - } - - w.URL = f.PayloadURL - w.ContentType = contentType - w.Secret = f.Secret - w.HookEvent = ParseHookEvent(f.Webhook) - w.IsActive = f.Active - if err := w.UpdateEvent(); err != nil { - c.Handle(500, "UpdateEvent", err) - return - } else if err := models.UpdateWebhook(w); err != nil { - c.Handle(500, "WebHooksEditPost", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.update_hook_success")) - c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) -} - -func SlackHooksEditPost(c *context.Context, f form.NewSlackHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(c) - if c.Written() { - return - } - c.Data["Webhook"] = w - - if c.HasError() { - c.HTML(200, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&models.SlackMeta{ - Channel: f.Channel, - Username: f.Username, - IconURL: f.IconURL, - Color: f.Color, - }) - if err != nil { - c.Handle(500, "Marshal", err) - return - } - - w.URL = f.PayloadURL - w.Meta = string(meta) - w.HookEvent = ParseHookEvent(f.Webhook) - w.IsActive = f.Active - if err := w.UpdateEvent(); err != nil { - c.Handle(500, "UpdateEvent", err) - return - } else if err := models.UpdateWebhook(w); err != nil { - c.Handle(500, "UpdateWebhook", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.update_hook_success")) - c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) -} - -// FIXME: merge logic to Slack -func DiscordHooksEditPost(c *context.Context, f form.NewDiscordHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(c) - if c.Written() { - return - } - c.Data["Webhook"] = w - - if c.HasError() { - c.HTML(200, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&models.SlackMeta{ - Username: f.Username, - IconURL: f.IconURL, - Color: f.Color, - }) - if err != nil { - c.Handle(500, "Marshal", err) - return - } - - w.URL = f.PayloadURL - w.Meta = string(meta) - w.HookEvent = ParseHookEvent(f.Webhook) - w.IsActive = f.Active - if err := w.UpdateEvent(); err != nil { - c.Handle(500, "UpdateEvent", err) - return - } else if err := models.UpdateWebhook(w); err != nil { - c.Handle(500, "UpdateWebhook", err) - return - } - - c.Flash.Success(c.Tr("repo.settings.update_hook_success")) - c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) -} - -func TestWebhook(c *context.Context) { - var authorUsername, committerUsername string - - // Grab latest commit or fake one if it's empty repository. - commit := c.Repo.Commit - if commit == nil { - ghost := models.NewGhostUser() - commit = &git.Commit{ - ID: git.MustIDFromString(git.EMPTY_SHA), - Author: ghost.NewGitSig(), - Committer: ghost.NewGitSig(), - CommitMessage: "This is a fake commit", - } - authorUsername = ghost.Name - committerUsername = ghost.Name - } else { - // Try to match email with a real user. - author, err := models.GetUserByEmail(commit.Author.Email) - if err == nil { - authorUsername = author.Name - } else if !errors.IsUserNotExist(err) { - c.Handle(500, "GetUserByEmail.(author)", err) - return - } - - committer, err := models.GetUserByEmail(commit.Committer.Email) - if err == nil { - committerUsername = committer.Name - } else if !errors.IsUserNotExist(err) { - c.Handle(500, "GetUserByEmail.(committer)", err) - return - } - } - - fileStatus, err := commit.FileStatus() - if err != nil { - c.Handle(500, "FileStatus", err) - return - } - - apiUser := c.User.APIFormat() - p := &api.PushPayload{ - Ref: git.BRANCH_PREFIX + c.Repo.Repository.DefaultBranch, - Before: commit.ID.String(), - After: commit.ID.String(), - Commits: []*api.PayloadCommit{ - { - ID: commit.ID.String(), - Message: commit.Message(), - URL: c.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(), - Author: &api.PayloadUser{ - Name: commit.Author.Name, - Email: commit.Author.Email, - UserName: authorUsername, - }, - Committer: &api.PayloadUser{ - Name: commit.Committer.Name, - Email: commit.Committer.Email, - UserName: committerUsername, - }, - Added: fileStatus.Added, - Removed: fileStatus.Removed, - Modified: fileStatus.Modified, - }, - }, - Repo: c.Repo.Repository.APIFormat(nil), - Pusher: apiUser, - Sender: apiUser, - } - if err := models.TestWebhook(c.Repo.Repository, models.HOOK_EVENT_PUSH, p, c.ParamsInt64("id")); err != nil { - c.Handle(500, "TestWebhook", err) - } else { - c.Flash.Info(c.Tr("repo.settings.webhook.test_delivery_success")) - c.Status(200) - } -} - -func RedeliveryWebhook(c *context.Context) { - webhook, err := models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) - if err != nil { - c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err) - return - } - - hookTask, err := models.GetHookTaskOfWebhookByUUID(webhook.ID, c.Query("uuid")) - if err != nil { - c.NotFoundOrServerError("GetHookTaskOfWebhookByUUID/GetWebhookByOrgID", errors.IsHookTaskNotExist, err) - return - } - - hookTask.IsDelivered = false - if err = models.UpdateHookTask(hookTask); err != nil { - c.Handle(500, "UpdateHookTask", err) - } else { - go models.HookQueue.Add(c.Repo.Repository.ID) - c.Flash.Info(c.Tr("repo.settings.webhook.redelivery_success", hookTask.UUID)) - c.Status(200) - } -} - -func DeleteWebhook(c *context.Context) { - if err := models.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) - } else { - c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success")) - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/settings/hooks", - }) -} diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go deleted file mode 100644 index ad2cfbae..00000000 --- a/routers/repo/wiki.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "io/ioutil" - "strings" - "time" - - "github.com/gogits/git-module" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/markup" -) - -const ( - WIKI_START = "repo/wiki/start" - WIKI_VIEW = "repo/wiki/view" - WIKI_NEW = "repo/wiki/new" - WIKI_PAGES = "repo/wiki/pages" -) - -func MustEnableWiki(c *context.Context) { - if !c.Repo.Repository.EnableWiki { - c.Handle(404, "MustEnableWiki", nil) - return - } - - if c.Repo.Repository.EnableExternalWiki { - c.Redirect(c.Repo.Repository.ExternalWikiURL) - return - } -} - -type PageMeta struct { - Name string - URL string - Updated time.Time -} - -func renderWikiPage(c *context.Context, isViewPage bool) (*git.Repository, string) { - wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath()) - if err != nil { - c.Handle(500, "OpenRepository", err) - return nil, "" - } - commit, err := wikiRepo.GetBranchCommit("master") - if err != nil { - c.Handle(500, "GetBranchCommit", err) - return nil, "" - } - - // Get page list. - if isViewPage { - entries, err := commit.ListEntries() - if err != nil { - c.Handle(500, "ListEntries", err) - return nil, "" - } - pages := make([]PageMeta, 0, len(entries)) - for i := range entries { - if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") { - name := strings.TrimSuffix(entries[i].Name(), ".md") - pages = append(pages, PageMeta{ - Name: name, - URL: models.ToWikiPageURL(name), - }) - } - } - c.Data["Pages"] = pages - } - - pageURL := c.Params(":page") - if len(pageURL) == 0 { - pageURL = "Home" - } - c.Data["PageURL"] = pageURL - - pageName := models.ToWikiPageName(pageURL) - c.Data["old_title"] = pageName - c.Data["Title"] = pageName - c.Data["title"] = pageName - c.Data["RequireHighlightJS"] = true - - blob, err := commit.GetBlobByPath(pageName + ".md") - if err != nil { - if git.IsErrNotExist(err) { - c.Redirect(c.Repo.RepoLink + "/wiki/_pages") - } else { - c.Handle(500, "GetBlobByPath", err) - } - return nil, "" - } - r, err := blob.Data() - if err != nil { - c.Handle(500, "Data", err) - return nil, "" - } - data, err := ioutil.ReadAll(r) - if err != nil { - c.Handle(500, "ReadAll", err) - return nil, "" - } - if isViewPage { - c.Data["content"] = string(markup.Markdown(data, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) - } else { - c.Data["content"] = string(data) - } - - return wikiRepo, pageName -} - -func Wiki(c *context.Context) { - c.Data["PageIsWiki"] = true - - if !c.Repo.Repository.HasWiki() { - c.Data["Title"] = c.Tr("repo.wiki") - c.HTML(200, WIKI_START) - return - } - - wikiRepo, pageName := renderWikiPage(c, true) - if c.Written() { - return - } - - // Get last change information. - lastCommit, err := wikiRepo.GetCommitByPath(pageName + ".md") - if err != nil { - c.Handle(500, "GetCommitByPath", err) - return - } - c.Data["Author"] = lastCommit.Author - - c.HTML(200, WIKI_VIEW) -} - -func WikiPages(c *context.Context) { - c.Data["Title"] = c.Tr("repo.wiki.pages") - c.Data["PageIsWiki"] = true - - if !c.Repo.Repository.HasWiki() { - c.Redirect(c.Repo.RepoLink + "/wiki") - return - } - - wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath()) - if err != nil { - c.Handle(500, "OpenRepository", err) - return - } - commit, err := wikiRepo.GetBranchCommit("master") - if err != nil { - c.Handle(500, "GetBranchCommit", err) - return - } - - entries, err := commit.ListEntries() - if err != nil { - c.Handle(500, "ListEntries", err) - return - } - pages := make([]PageMeta, 0, len(entries)) - for i := range entries { - if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") { - commit, err := wikiRepo.GetCommitByPath(entries[i].Name()) - if err != nil { - c.ServerError("GetCommitByPath", err) - return - } - name := strings.TrimSuffix(entries[i].Name(), ".md") - pages = append(pages, PageMeta{ - Name: name, - URL: models.ToWikiPageURL(name), - Updated: commit.Author.When, - }) - } - } - c.Data["Pages"] = pages - - c.HTML(200, WIKI_PAGES) -} - -func NewWiki(c *context.Context) { - c.Data["Title"] = c.Tr("repo.wiki.new_page") - c.Data["PageIsWiki"] = true - c.Data["RequireSimpleMDE"] = true - - if !c.Repo.Repository.HasWiki() { - c.Data["title"] = "Home" - } - - c.HTML(200, WIKI_NEW) -} - -func NewWikiPost(c *context.Context, f form.NewWiki) { - c.Data["Title"] = c.Tr("repo.wiki.new_page") - c.Data["PageIsWiki"] = true - c.Data["RequireSimpleMDE"] = true - - if c.HasError() { - c.HTML(200, WIKI_NEW) - return - } - - if err := c.Repo.Repository.AddWikiPage(c.User, f.Title, f.Content, f.Message); err != nil { - if models.IsErrWikiAlreadyExist(err) { - c.Data["Err_Title"] = true - c.RenderWithErr(c.Tr("repo.wiki.page_already_exists"), WIKI_NEW, &f) - } else { - c.Handle(500, "AddWikiPage", err) - } - return - } - - c.Redirect(c.Repo.RepoLink + "/wiki/" + models.ToWikiPageURL(models.ToWikiPageName(f.Title))) -} - -func EditWiki(c *context.Context) { - c.Data["PageIsWiki"] = true - c.Data["PageIsWikiEdit"] = true - c.Data["RequireSimpleMDE"] = true - - if !c.Repo.Repository.HasWiki() { - c.Redirect(c.Repo.RepoLink + "/wiki") - return - } - - renderWikiPage(c, false) - if c.Written() { - return - } - - c.HTML(200, WIKI_NEW) -} - -func EditWikiPost(c *context.Context, f form.NewWiki) { - c.Data["Title"] = c.Tr("repo.wiki.new_page") - c.Data["PageIsWiki"] = true - c.Data["RequireSimpleMDE"] = true - - if c.HasError() { - c.HTML(200, WIKI_NEW) - return - } - - if err := c.Repo.Repository.EditWikiPage(c.User, f.OldTitle, f.Title, f.Content, f.Message); err != nil { - c.Handle(500, "EditWikiPage", err) - return - } - - c.Redirect(c.Repo.RepoLink + "/wiki/" + models.ToWikiPageURL(models.ToWikiPageName(f.Title))) -} - -func DeleteWikiPagePost(c *context.Context) { - pageURL := c.Params(":page") - if len(pageURL) == 0 { - pageURL = "Home" - } - - pageName := models.ToWikiPageName(pageURL) - if err := c.Repo.Repository.DeleteWikiPage(c.User, pageName); err != nil { - c.Handle(500, "DeleteWikiPage", err) - return - } - - c.JSON(200, map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/wiki/", - }) -} diff --git a/routers/user/auth.go b/routers/user/auth.go deleted file mode 100644 index 34fdbd85..00000000 --- a/routers/user/auth.go +++ /dev/null @@ -1,534 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - "fmt" - "net/url" - - "github.com/go-macaron/captcha" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/mailer" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - LOGIN = "user/auth/login" - TWO_FACTOR = "user/auth/two_factor" - TWO_FACTOR_RECOVERY_CODE = "user/auth/two_factor_recovery_code" - SIGNUP = "user/auth/signup" - ACTIVATE = "user/auth/activate" - FORGOT_PASSWORD = "user/auth/forgot_passwd" - RESET_PASSWORD = "user/auth/reset_passwd" -) - -// AutoLogin reads cookie and try to auto-login. -func AutoLogin(c *context.Context) (bool, error) { - if !models.HasEngine { - return false, nil - } - - uname := c.GetCookie(setting.CookieUserName) - if len(uname) == 0 { - return false, nil - } - - isSucceed := false - defer func() { - if !isSucceed { - log.Trace("auto-login cookie cleared: %s", uname) - c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) - c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) - c.SetCookie(setting.LoginStatusCookieName, "", -1, setting.AppSubURL) - } - }() - - u, err := models.GetUserByName(uname) - if err != nil { - if !errors.IsUserNotExist(err) { - return false, fmt.Errorf("GetUserByName: %v", err) - } - return false, nil - } - - if val, ok := c.GetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName); !ok || val != u.Name { - return false, nil - } - - isSucceed = true - c.Session.Set("uid", u.ID) - c.Session.Set("uname", u.Name) - c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) - if setting.EnableLoginStatusCookie { - c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL) - } - return true, nil -} - -// isValidRedirect returns false if the URL does not redirect to same site. -// False: //url, http://url -// True: /url -func isValidRedirect(url string) bool { - return len(url) >= 2 && url[0] == '/' && url[1] != '/' -} - -func Login(c *context.Context) { - c.Data["Title"] = c.Tr("sign_in") - - // Check auto-login. - isSucceed, err := AutoLogin(c) - if err != nil { - c.Handle(500, "AutoLogin", err) - return - } - - redirectTo := c.Query("redirect_to") - if len(redirectTo) > 0 { - c.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL) - } else { - redirectTo, _ = url.QueryUnescape(c.GetCookie("redirect_to")) - } - c.SetCookie("redirect_to", "", -1, setting.AppSubURL) - - if isSucceed { - if isValidRedirect(redirectTo) { - c.Redirect(redirectTo) - } else { - c.Redirect(setting.AppSubURL + "/") - } - return - } - - c.HTML(200, LOGIN) -} - -func afterLogin(c *context.Context, u *models.User, remember bool) { - if remember { - days := 86400 * setting.LoginRememberDays - c.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true) - c.SetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true) - } - - c.Session.Set("uid", u.ID) - c.Session.Set("uname", u.Name) - c.Session.Delete("twoFactorRemember") - c.Session.Delete("twoFactorUserID") - - // Clear whatever CSRF has right now, force to generate a new one - c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) - if setting.EnableLoginStatusCookie { - c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL) - } - - redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")) - c.SetCookie("redirect_to", "", -1, setting.AppSubURL) - if isValidRedirect(redirectTo) { - c.Redirect(redirectTo) - return - } - - c.Redirect(setting.AppSubURL + "/") -} - -func LoginPost(c *context.Context, f form.SignIn) { - c.Data["Title"] = c.Tr("sign_in") - - if c.HasError() { - c.Success(LOGIN) - return - } - - u, err := models.UserSignIn(f.UserName, f.Password) - if err != nil { - if errors.IsUserNotExist(err) { - c.RenderWithErr(c.Tr("form.username_password_incorrect"), LOGIN, &f) - } else { - c.ServerError("UserSignIn", err) - } - return - } - - if !u.IsEnabledTwoFactor() { - afterLogin(c, u, f.Remember) - return - } - - c.Session.Set("twoFactorRemember", f.Remember) - c.Session.Set("twoFactorUserID", u.ID) - c.Redirect(setting.AppSubURL + "/user/login/two_factor") -} - -func LoginTwoFactor(c *context.Context) { - _, ok := c.Session.Get("twoFactorUserID").(int64) - if !ok { - c.NotFound() - return - } - - c.Success(TWO_FACTOR) -} - -func LoginTwoFactorPost(c *context.Context) { - userID, ok := c.Session.Get("twoFactorUserID").(int64) - if !ok { - c.NotFound() - return - } - - t, err := models.GetTwoFactorByUserID(userID) - if err != nil { - c.ServerError("GetTwoFactorByUserID", err) - return - } - valid, err := t.ValidateTOTP(c.Query("passcode")) - if err != nil { - c.ServerError("ValidateTOTP", err) - return - } else if !valid { - c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode")) - c.Redirect(setting.AppSubURL + "/user/login/two_factor") - return - } - - u, err := models.GetUserByID(userID) - if err != nil { - c.ServerError("GetUserByID", err) - return - } - afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) -} - -func LoginTwoFactorRecoveryCode(c *context.Context) { - _, ok := c.Session.Get("twoFactorUserID").(int64) - if !ok { - c.NotFound() - return - } - - c.Success(TWO_FACTOR_RECOVERY_CODE) -} - -func LoginTwoFactorRecoveryCodePost(c *context.Context) { - userID, ok := c.Session.Get("twoFactorUserID").(int64) - if !ok { - c.NotFound() - return - } - - if err := models.UseRecoveryCode(userID, c.Query("recovery_code")); err != nil { - if errors.IsTwoFactorRecoveryCodeNotFound(err) { - c.Flash.Error(c.Tr("auth.login_two_factor_invalid_recovery_code")) - c.Redirect(setting.AppSubURL + "/user/login/two_factor_recovery_code") - } else { - c.ServerError("UseRecoveryCode", err) - } - return - } - - u, err := models.GetUserByID(userID) - if err != nil { - c.ServerError("GetUserByID", err) - return - } - afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) -} - -func SignOut(c *context.Context) { - c.Session.Delete("uid") - c.Session.Delete("uname") - c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) - c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) - c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) - c.Redirect(setting.AppSubURL + "/") -} - -func SignUp(c *context.Context) { - c.Data["Title"] = c.Tr("sign_up") - - c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha - - if setting.Service.DisableRegistration { - c.Data["DisableRegistration"] = true - c.HTML(200, SIGNUP) - return - } - - c.HTML(200, SIGNUP) -} - -func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) { - c.Data["Title"] = c.Tr("sign_up") - - c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha - - if setting.Service.DisableRegistration { - c.Error(403) - return - } - - if c.HasError() { - c.HTML(200, SIGNUP) - return - } - - if setting.Service.EnableCaptcha && !cpt.VerifyReq(c.Req) { - c.Data["Err_Captcha"] = true - c.RenderWithErr(c.Tr("form.captcha_incorrect"), SIGNUP, &f) - return - } - - if f.Password != f.Retype { - c.Data["Err_Password"] = true - c.RenderWithErr(c.Tr("form.password_not_match"), SIGNUP, &f) - return - } - - u := &models.User{ - Name: f.UserName, - Email: f.Email, - Passwd: f.Password, - IsActive: !setting.Service.RegisterEmailConfirm, - } - if err := models.CreateUser(u); err != nil { - switch { - case models.IsErrUserAlreadyExist(err): - c.Data["Err_UserName"] = true - c.RenderWithErr(c.Tr("form.username_been_taken"), SIGNUP, &f) - case models.IsErrEmailAlreadyUsed(err): - c.Data["Err_Email"] = true - c.RenderWithErr(c.Tr("form.email_been_used"), SIGNUP, &f) - case models.IsErrNameReserved(err): - c.Data["Err_UserName"] = true - c.RenderWithErr(c.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), SIGNUP, &f) - case models.IsErrNamePatternNotAllowed(err): - c.Data["Err_UserName"] = true - c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), SIGNUP, &f) - default: - c.Handle(500, "CreateUser", err) - } - return - } - log.Trace("Account created: %s", u.Name) - - // Auto-set admin for the only user. - if models.CountUsers() == 1 { - u.IsAdmin = true - u.IsActive = true - if err := models.UpdateUser(u); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - } - - // Send confirmation email, no need for social account. - if setting.Service.RegisterEmailConfirm && u.ID > 1 { - mailer.SendActivateAccountMail(c.Context, models.NewMailerUser(u)) - c.Data["IsSendRegisterMail"] = true - c.Data["Email"] = u.Email - c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 - c.HTML(200, ACTIVATE) - - if err := c.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { - log.Error(4, "Set cache(MailResendLimit) fail: %v", err) - } - return - } - - c.Redirect(setting.AppSubURL + "/user/login") -} - -func Activate(c *context.Context) { - code := c.Query("code") - if len(code) == 0 { - c.Data["IsActivatePage"] = true - if c.User.IsActive { - c.Error(404) - return - } - // Resend confirmation email. - if setting.Service.RegisterEmailConfirm { - if c.Cache.IsExist("MailResendLimit_" + c.User.LowerName) { - c.Data["ResendLimited"] = true - } else { - c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 - mailer.SendActivateAccountMail(c.Context, models.NewMailerUser(c.User)) - - keyName := "MailResendLimit_" + c.User.LowerName - if err := c.Cache.Put(keyName, c.User.LowerName, 180); err != nil { - log.Error(2, "Set cache '%s' fail: %v", keyName, err) - } - } - } else { - c.Data["ServiceNotEnabled"] = true - } - c.HTML(200, ACTIVATE) - return - } - - // Verify code. - if user := models.VerifyUserActiveCode(code); user != nil { - user.IsActive = true - var err error - if user.Rands, err = models.GetUserSalt(); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - if err := models.UpdateUser(user); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - - log.Trace("User activated: %s", user.Name) - - c.Session.Set("uid", user.ID) - c.Session.Set("uname", user.Name) - c.Redirect(setting.AppSubURL + "/") - return - } - - c.Data["IsActivateFailed"] = true - c.HTML(200, ACTIVATE) -} - -func ActivateEmail(c *context.Context) { - code := c.Query("code") - email_string := c.Query("email") - - // Verify code. - if email := models.VerifyActiveEmailCode(code, email_string); email != nil { - if err := email.Activate(); err != nil { - c.Handle(500, "ActivateEmail", err) - } - - log.Trace("Email activated: %s", email.Email) - c.Flash.Success(c.Tr("settings.add_email_success")) - } - - c.Redirect(setting.AppSubURL + "/user/settings/email") - return -} - -func ForgotPasswd(c *context.Context) { - c.Data["Title"] = c.Tr("auth.forgot_password") - - if setting.MailService == nil { - c.Data["IsResetDisable"] = true - c.HTML(200, FORGOT_PASSWORD) - return - } - - c.Data["IsResetRequest"] = true - c.HTML(200, FORGOT_PASSWORD) -} - -func ForgotPasswdPost(c *context.Context) { - c.Data["Title"] = c.Tr("auth.forgot_password") - - if setting.MailService == nil { - c.Handle(403, "ForgotPasswdPost", nil) - return - } - c.Data["IsResetRequest"] = true - - email := c.Query("email") - c.Data["Email"] = email - - u, err := models.GetUserByEmail(email) - if err != nil { - if errors.IsUserNotExist(err) { - c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 - c.Data["IsResetSent"] = true - c.HTML(200, FORGOT_PASSWORD) - return - } else { - c.Handle(500, "user.ResetPasswd(check existence)", err) - } - return - } - - if !u.IsLocal() { - c.Data["Err_Email"] = true - c.RenderWithErr(c.Tr("auth.non_local_account"), FORGOT_PASSWORD, nil) - return - } - - if c.Cache.IsExist("MailResendLimit_" + u.LowerName) { - c.Data["ResendLimited"] = true - c.HTML(200, FORGOT_PASSWORD) - return - } - - mailer.SendResetPasswordMail(c.Context, models.NewMailerUser(u)) - if err = c.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { - log.Error(4, "Set cache(MailResendLimit) fail: %v", err) - } - - c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 - c.Data["IsResetSent"] = true - c.HTML(200, FORGOT_PASSWORD) -} - -func ResetPasswd(c *context.Context) { - c.Data["Title"] = c.Tr("auth.reset_password") - - code := c.Query("code") - if len(code) == 0 { - c.Error(404) - return - } - c.Data["Code"] = code - c.Data["IsResetForm"] = true - c.HTML(200, RESET_PASSWORD) -} - -func ResetPasswdPost(c *context.Context) { - c.Data["Title"] = c.Tr("auth.reset_password") - - code := c.Query("code") - if len(code) == 0 { - c.Error(404) - return - } - c.Data["Code"] = code - - if u := models.VerifyUserActiveCode(code); u != nil { - // Validate password length. - passwd := c.Query("password") - if len(passwd) < 6 { - c.Data["IsResetForm"] = true - c.Data["Err_Password"] = true - c.RenderWithErr(c.Tr("auth.password_too_short"), RESET_PASSWORD, nil) - return - } - - u.Passwd = passwd - var err error - if u.Rands, err = models.GetUserSalt(); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - if u.Salt, err = models.GetUserSalt(); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - u.EncodePasswd() - if err := models.UpdateUser(u); err != nil { - c.Handle(500, "UpdateUser", err) - return - } - - log.Trace("User password reset: %s", u.Name) - c.Redirect(setting.AppSubURL + "/user/login") - return - } - - c.Data["IsResetFailed"] = true - c.HTML(200, RESET_PASSWORD) -} diff --git a/routers/user/home.go b/routers/user/home.go deleted file mode 100644 index c3b9b182..00000000 --- a/routers/user/home.go +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - "bytes" - "fmt" - - "github.com/Unknwon/com" - "github.com/Unknwon/paginater" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" -) - -const ( - DASHBOARD = "user/dashboard/dashboard" - NEWS_FEED = "user/dashboard/feeds" - ISSUES = "user/dashboard/issues" - PROFILE = "user/profile" - ORG_HOME = "org/home" -) - -// getDashboardContextUser finds out dashboard is viewing as which context user. -func getDashboardContextUser(c *context.Context) *models.User { - ctxUser := c.User - orgName := c.Params(":org") - if len(orgName) > 0 { - // Organization. - org, err := models.GetUserByName(orgName) - if err != nil { - c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) - return nil - } - ctxUser = org - } - c.Data["ContextUser"] = ctxUser - - if err := c.User.GetOrganizations(true); err != nil { - c.Handle(500, "GetOrganizations", err) - return nil - } - c.Data["Orgs"] = c.User.Orgs - - return ctxUser -} - -// retrieveFeeds loads feeds from database by given context user. -// The user could be organization so it is not always the logged in user, -// which is why we have to explicitly pass the context user ID. -func retrieveFeeds(c *context.Context, ctxUser *models.User, userID int64, isProfile bool) { - actions, err := models.GetFeeds(ctxUser, userID, c.QueryInt64("after_id"), isProfile) - if err != nil { - c.Handle(500, "GetFeeds", err) - return - } - - // Check access of private repositories. - feeds := make([]*models.Action, 0, len(actions)) - unameAvatars := make(map[string]string) - for _, act := range actions { - // Cache results to reduce queries. - _, ok := unameAvatars[act.ActUserName] - if !ok { - u, err := models.GetUserByName(act.ActUserName) - if err != nil { - if errors.IsUserNotExist(err) { - continue - } - c.Handle(500, "GetUserByName", err) - return - } - unameAvatars[act.ActUserName] = u.RelAvatarLink() - } - - act.ActAvatar = unameAvatars[act.ActUserName] - feeds = append(feeds, act) - } - c.Data["Feeds"] = feeds - if len(feeds) > 0 { - afterID := feeds[len(feeds)-1].ID - c.Data["AfterID"] = afterID - c.Header().Set("X-AJAX-URL", fmt.Sprintf("%s?after_id=%d", c.Data["Link"], afterID)) - } -} - -func Dashboard(c *context.Context) { - ctxUser := getDashboardContextUser(c) - if c.Written() { - return - } - - retrieveFeeds(c, ctxUser, c.User.ID, false) - if c.Written() { - return - } - - if c.Req.Header.Get("X-AJAX") == "true" { - c.HTML(200, NEWS_FEED) - return - } - - c.Data["Title"] = ctxUser.DisplayName() + " - " + c.Tr("dashboard") - c.Data["PageIsDashboard"] = true - c.Data["PageIsNews"] = true - - // Only user can have collaborative repositories. - if !ctxUser.IsOrganization() { - collaborateRepos, err := c.User.GetAccessibleRepositories(setting.UI.User.RepoPagingNum) - if err != nil { - c.Handle(500, "GetAccessibleRepositories", err) - return - } else if err = models.RepositoryList(collaborateRepos).LoadAttributes(); err != nil { - c.Handle(500, "RepositoryList.LoadAttributes", err) - return - } - c.Data["CollaborativeRepos"] = collaborateRepos - } - - var err error - var repos, mirrors []*models.Repository - var repoCount int64 - if ctxUser.IsOrganization() { - repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, setting.UI.User.RepoPagingNum) - if err != nil { - c.Handle(500, "GetUserRepositories", err) - return - } - - mirrors, err = ctxUser.GetUserMirrorRepositories(c.User.ID) - if err != nil { - c.Handle(500, "GetUserMirrorRepositories", err) - return - } - } else { - if err = ctxUser.GetRepositories(1, setting.UI.User.RepoPagingNum); err != nil { - c.Handle(500, "GetRepositories", err) - return - } - repos = ctxUser.Repos - repoCount = int64(ctxUser.NumRepos) - - mirrors, err = ctxUser.GetMirrorRepositories() - if err != nil { - c.Handle(500, "GetMirrorRepositories", err) - return - } - } - c.Data["Repos"] = repos - c.Data["RepoCount"] = repoCount - c.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum - - if err := models.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil { - c.Handle(500, "MirrorRepositoryList.LoadAttributes", err) - return - } - c.Data["MirrorCount"] = len(mirrors) - c.Data["Mirrors"] = mirrors - - c.HTML(200, DASHBOARD) -} - -func Issues(c *context.Context) { - isPullList := c.Params(":type") == "pulls" - if isPullList { - c.Data["Title"] = c.Tr("pull_requests") - c.Data["PageIsPulls"] = true - } else { - c.Data["Title"] = c.Tr("issues") - c.Data["PageIsIssues"] = true - } - - ctxUser := getDashboardContextUser(c) - if c.Written() { - return - } - - var ( - sortType = c.Query("sort") - filterMode = models.FILTER_MODE_YOUR_REPOS - ) - - // Note: Organization does not have view type and filter mode. - if !ctxUser.IsOrganization() { - viewType := c.Query("type") - types := []string{ - string(models.FILTER_MODE_YOUR_REPOS), - string(models.FILTER_MODE_ASSIGN), - string(models.FILTER_MODE_CREATE), - } - if !com.IsSliceContainsStr(types, viewType) { - viewType = string(models.FILTER_MODE_YOUR_REPOS) - } - filterMode = models.FilterMode(viewType) - } - - page := c.QueryInt("page") - if page <= 1 { - page = 1 - } - - repoID := c.QueryInt64("repo") - isShowClosed := c.Query("state") == "closed" - - // Get repositories. - var ( - err error - repos []*models.Repository - userRepoIDs []int64 - showRepos = make([]*models.Repository, 0, 10) - ) - if ctxUser.IsOrganization() { - repos, _, err = ctxUser.GetUserRepositories(c.User.ID, 1, ctxUser.NumRepos) - if err != nil { - c.Handle(500, "GetRepositories", err) - return - } - } else { - if err := ctxUser.GetRepositories(1, c.User.NumRepos); err != nil { - c.Handle(500, "GetRepositories", err) - return - } - repos = ctxUser.Repos - } - - userRepoIDs = make([]int64, 0, len(repos)) - for _, repo := range repos { - userRepoIDs = append(userRepoIDs, repo.ID) - - if filterMode != models.FILTER_MODE_YOUR_REPOS { - continue - } - - if isPullList { - if isShowClosed && repo.NumClosedPulls == 0 || - !isShowClosed && repo.NumOpenPulls == 0 { - continue - } - } else { - if !repo.EnableIssues || repo.EnableExternalTracker || - isShowClosed && repo.NumClosedIssues == 0 || - !isShowClosed && repo.NumOpenIssues == 0 { - continue - } - } - - showRepos = append(showRepos, repo) - } - - // Filter repositories if the page shows issues. - if !isPullList { - userRepoIDs, err = models.FilterRepositoryWithIssues(userRepoIDs) - if err != nil { - c.Handle(500, "FilterRepositoryWithIssues", err) - return - } - } - - issueOptions := &models.IssuesOptions{ - RepoID: repoID, - Page: page, - IsClosed: isShowClosed, - IsPull: isPullList, - SortType: sortType, - } - switch filterMode { - case models.FILTER_MODE_YOUR_REPOS: - // Get all issues from repositories from this user. - if userRepoIDs == nil { - issueOptions.RepoIDs = []int64{-1} - } else { - issueOptions.RepoIDs = userRepoIDs - } - - case models.FILTER_MODE_ASSIGN: - // Get all issues assigned to this user. - issueOptions.AssigneeID = ctxUser.ID - - case models.FILTER_MODE_CREATE: - // Get all issues created by this user. - issueOptions.PosterID = ctxUser.ID - } - - issues, err := models.Issues(issueOptions) - if err != nil { - c.Handle(500, "Issues", err) - return - } - - if repoID > 0 { - repo, err := models.GetRepositoryByID(repoID) - if err != nil { - c.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d] %v", repoID, err)) - return - } - - if err = repo.GetOwner(); err != nil { - c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", repoID, err)) - return - } - - // Check if user has access to given repository. - if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser.ID) { - c.Handle(404, "Issues", fmt.Errorf("#%d", repoID)) - return - } - } - - for _, issue := range issues { - if err = issue.Repo.GetOwner(); err != nil { - c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", issue.RepoID, err)) - return - } - } - - issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList) - - var total int - if !isShowClosed { - total = int(issueStats.OpenCount) - } else { - total = int(issueStats.ClosedCount) - } - - c.Data["Issues"] = issues - c.Data["Repos"] = showRepos - c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5) - c.Data["IssueStats"] = issueStats - c.Data["ViewType"] = string(filterMode) - c.Data["SortType"] = sortType - c.Data["RepoID"] = repoID - c.Data["IsShowClosed"] = isShowClosed - - if isShowClosed { - c.Data["State"] = "closed" - } else { - c.Data["State"] = "open" - } - - c.HTML(200, ISSUES) -} - -func ShowSSHKeys(c *context.Context, uid int64) { - keys, err := models.ListPublicKeys(uid) - if err != nil { - c.Handle(500, "ListPublicKeys", err) - return - } - - var buf bytes.Buffer - for i := range keys { - buf.WriteString(keys[i].OmitEmail()) - buf.WriteString("\n") - } - c.PlainText(200, buf.Bytes()) -} - -func showOrgProfile(c *context.Context) { - c.SetParams(":org", c.Params(":username")) - context.HandleOrgAssignment(c) - if c.Written() { - return - } - - org := c.Org.Organization - c.Data["Title"] = org.FullName - - page := c.QueryInt("page") - if page <= 0 { - page = 1 - } - - var ( - repos []*models.Repository - count int64 - err error - ) - if c.IsLogged && !c.User.IsAdmin { - repos, count, err = org.GetUserRepositories(c.User.ID, page, setting.UI.User.RepoPagingNum) - if err != nil { - c.Handle(500, "GetUserRepositories", err) - return - } - c.Data["Repos"] = repos - } else { - showPrivate := c.IsLogged && c.User.IsAdmin - repos, err = models.GetUserRepositories(&models.UserRepoOptions{ - UserID: org.ID, - Private: showPrivate, - Page: page, - PageSize: setting.UI.User.RepoPagingNum, - }) - if err != nil { - c.Handle(500, "GetRepositories", err) - return - } - c.Data["Repos"] = repos - count = models.CountUserRepositories(org.ID, showPrivate) - } - c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5) - - if err := org.GetMembers(); err != nil { - c.Handle(500, "GetMembers", err) - return - } - c.Data["Members"] = org.Members - - c.Data["Teams"] = org.Teams - - c.HTML(200, ORG_HOME) -} - -func Email2User(c *context.Context) { - u, err := models.GetUserByEmail(c.Query("email")) - if err != nil { - c.NotFoundOrServerError("GetUserByEmail", errors.IsUserNotExist, err) - return - } - c.Redirect(setting.AppSubURL + "/user/" + u.Name) -} diff --git a/routers/user/profile.go b/routers/user/profile.go deleted file mode 100644 index dc8ba6ae..00000000 --- a/routers/user/profile.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - "fmt" - "path" - "strings" - - "github.com/Unknwon/paginater" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/routers/repo" -) - -const ( - FOLLOWERS = "user/meta/followers" - STARS = "user/meta/stars" -) - -func GetUserByName(c *context.Context, name string) *models.User { - user, err := models.GetUserByName(name) - if err != nil { - c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) - return nil - } - return user -} - -// GetUserByParams returns user whose name is presented in URL paramenter. -func GetUserByParams(c *context.Context) *models.User { - return GetUserByName(c, c.Params(":username")) -} - -func Profile(c *context.Context) { - uname := c.Params(":username") - // Special handle for FireFox requests favicon.ico. - if uname == "favicon.ico" { - c.ServeFile(path.Join(setting.StaticRootPath, "public/img/favicon.png")) - return - } else if strings.HasSuffix(uname, ".png") { - c.Error(404) - return - } - - isShowKeys := false - if strings.HasSuffix(uname, ".keys") { - isShowKeys = true - } - - ctxUser := GetUserByName(c, strings.TrimSuffix(uname, ".keys")) - if c.Written() { - return - } - - // Show SSH keys. - if isShowKeys { - ShowSSHKeys(c, ctxUser.ID) - return - } - - if ctxUser.IsOrganization() { - showOrgProfile(c) - return - } - - c.Data["Title"] = ctxUser.DisplayName() - c.Data["PageIsUserProfile"] = true - c.Data["Owner"] = ctxUser - - orgs, err := models.GetOrgsByUserID(ctxUser.ID, c.IsLogged && (c.User.IsAdmin || c.User.ID == ctxUser.ID)) - if err != nil { - c.Handle(500, "GetOrgsByUserIDDesc", err) - return - } - - c.Data["Orgs"] = orgs - - tab := c.Query("tab") - c.Data["TabName"] = tab - switch tab { - case "activity": - retrieveFeeds(c, ctxUser, -1, true) - if c.Written() { - return - } - default: - page := c.QueryInt("page") - if page <= 0 { - page = 1 - } - - showPrivate := c.IsLogged && (ctxUser.ID == c.User.ID || c.User.IsAdmin) - c.Data["Repos"], err = models.GetUserRepositories(&models.UserRepoOptions{ - UserID: ctxUser.ID, - Private: showPrivate, - Page: page, - PageSize: setting.UI.User.RepoPagingNum, - }) - if err != nil { - c.Handle(500, "GetRepositories", err) - return - } - - count := models.CountUserRepositories(ctxUser.ID, showPrivate) - c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5) - } - - c.HTML(200, PROFILE) -} - -func Followers(c *context.Context) { - u := GetUserByParams(c) - if c.Written() { - return - } - c.Data["Title"] = u.DisplayName() - c.Data["CardsTitle"] = c.Tr("user.followers") - c.Data["PageIsFollowers"] = true - c.Data["Owner"] = u - repo.RenderUserCards(c, u.NumFollowers, u.GetFollowers, FOLLOWERS) -} - -func Following(c *context.Context) { - u := GetUserByParams(c) - if c.Written() { - return - } - c.Data["Title"] = u.DisplayName() - c.Data["CardsTitle"] = c.Tr("user.following") - c.Data["PageIsFollowing"] = true - c.Data["Owner"] = u - repo.RenderUserCards(c, u.NumFollowing, u.GetFollowing, FOLLOWERS) -} - -func Stars(c *context.Context) { - -} - -func Action(c *context.Context) { - u := GetUserByParams(c) - if c.Written() { - return - } - - var err error - switch c.Params(":action") { - case "follow": - err = models.FollowUser(c.User.ID, u.ID) - case "unfollow": - err = models.UnfollowUser(c.User.ID, u.ID) - } - - if err != nil { - c.Handle(500, fmt.Sprintf("Action (%s)", c.Params(":action")), err) - return - } - - redirectTo := c.Query("redirect_to") - if len(redirectTo) == 0 { - redirectTo = u.HomeLink() - } - c.Redirect(redirectTo) -} diff --git a/routers/user/setting.go b/routers/user/setting.go deleted file mode 100644 index 723b3da2..00000000 --- a/routers/user/setting.go +++ /dev/null @@ -1,664 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package user - -import ( - "bytes" - "encoding/base64" - "fmt" - "html/template" - "image/png" - "io/ioutil" - "strings" - - "github.com/Unknwon/com" - "github.com/pquerna/otp" - "github.com/pquerna/otp/totp" - log "gopkg.in/clog.v1" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/models/errors" - "github.com/gogits/gogs/pkg/context" - "github.com/gogits/gogs/pkg/form" - "github.com/gogits/gogs/pkg/mailer" - "github.com/gogits/gogs/pkg/setting" - "github.com/gogits/gogs/pkg/tool" -) - -const ( - SETTINGS_PROFILE = "user/settings/profile" - SETTINGS_AVATAR = "user/settings/avatar" - SETTINGS_PASSWORD = "user/settings/password" - SETTINGS_EMAILS = "user/settings/email" - SETTINGS_SSH_KEYS = "user/settings/sshkeys" - SETTINGS_SECURITY = "user/settings/security" - SETTINGS_TWO_FACTOR_ENABLE = "user/settings/two_factor_enable" - SETTINGS_TWO_FACTOR_RECOVERY_CODES = "user/settings/two_factor_recovery_codes" - SETTINGS_REPOSITORIES = "user/settings/repositories" - SETTINGS_ORGANIZATIONS = "user/settings/organizations" - SETTINGS_APPLICATIONS = "user/settings/applications" - SETTINGS_DELETE = "user/settings/delete" - NOTIFICATION = "user/notification" -) - -func Settings(c *context.Context) { - c.Title("settings.profile") - c.PageIs("SettingsProfile") - c.Data["origin_name"] = c.User.Name - c.Data["name"] = c.User.Name - c.Data["full_name"] = c.User.FullName - c.Data["email"] = c.User.Email - c.Data["website"] = c.User.Website - c.Data["location"] = c.User.Location - c.Success(SETTINGS_PROFILE) -} - -func SettingsPost(c *context.Context, f form.UpdateProfile) { - c.Title("settings.profile") - c.PageIs("SettingsProfile") - c.Data["origin_name"] = c.User.Name - - if c.HasError() { - c.Success(SETTINGS_PROFILE) - return - } - - // Non-local users are not allowed to change their username - if c.User.IsLocal() { - // Check if username characters have been changed - if c.User.LowerName != strings.ToLower(f.Name) { - if err := models.ChangeUserName(c.User, f.Name); err != nil { - c.FormErr("Name") - var msg string - switch { - case models.IsErrUserAlreadyExist(err): - msg = c.Tr("form.username_been_taken") - case models.IsErrEmailAlreadyUsed(err): - msg = c.Tr("form.email_been_used") - case models.IsErrNameReserved(err): - msg = c.Tr("form.name_reserved") - case models.IsErrNamePatternNotAllowed(err): - msg = c.Tr("form.name_pattern_not_allowed") - default: - c.ServerError("ChangeUserName", err) - return - } - - c.RenderWithErr(msg, SETTINGS_PROFILE, &f) - return - } - - log.Trace("Username changed: %s -> %s", c.User.Name, f.Name) - } - - // In case it's just a case change - c.User.Name = f.Name - c.User.LowerName = strings.ToLower(f.Name) - } - - c.User.FullName = f.FullName - c.User.Email = f.Email - c.User.Website = f.Website - c.User.Location = f.Location - if err := models.UpdateUser(c.User); err != nil { - c.ServerError("UpdateUser", err) - return - } - - c.Flash.Success(c.Tr("settings.update_profile_success")) - c.SubURLRedirect("/user/settings") -} - -// FIXME: limit size. -func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *models.User) error { - ctxUser.UseCustomAvatar = f.Source == form.AVATAR_LOCAL - if len(f.Gravatar) > 0 { - ctxUser.Avatar = tool.MD5(f.Gravatar) - ctxUser.AvatarEmail = f.Gravatar - } - - if f.Avatar != nil { - r, err := f.Avatar.Open() - if err != nil { - return fmt.Errorf("Avatar.Open: %v", err) - } - defer r.Close() - - data, err := ioutil.ReadAll(r) - if err != nil { - return fmt.Errorf("ioutil.ReadAll: %v", err) - } - if !tool.IsImageFile(data) { - return errors.New(c.Tr("settings.uploaded_avatar_not_a_image")) - } - if err = ctxUser.UploadAvatar(data); err != nil { - return fmt.Errorf("UploadAvatar: %v", err) - } - } else { - // No avatar is uploaded but setting has been changed to enable, - // generate a random one when needed. - if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) { - if err := ctxUser.GenerateRandomAvatar(); err != nil { - log.Error(4, "GenerateRandomAvatar[%d]: %v", ctxUser.ID, err) - } - } - } - - if err := models.UpdateUser(ctxUser); err != nil { - return fmt.Errorf("UpdateUser: %v", err) - } - - return nil -} - -func SettingsAvatar(c *context.Context) { - c.Title("settings.avatar") - c.PageIs("SettingsAvatar") - c.Success(SETTINGS_AVATAR) -} - -func SettingsAvatarPost(c *context.Context, f form.Avatar) { - if err := UpdateAvatarSetting(c, f, c.User); err != nil { - c.Flash.Error(err.Error()) - } else { - c.Flash.Success(c.Tr("settings.update_avatar_success")) - } - - c.SubURLRedirect("/user/settings/avatar") -} - -func SettingsDeleteAvatar(c *context.Context) { - if err := c.User.DeleteAvatar(); err != nil { - c.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err)) - } - - c.SubURLRedirect("/user/settings/avatar") -} - -func SettingsPassword(c *context.Context) { - c.Title("settings.password") - c.PageIs("SettingsPassword") - c.Success(SETTINGS_PASSWORD) -} - -func SettingsPasswordPost(c *context.Context, f form.ChangePassword) { - c.Title("settings.password") - c.PageIs("SettingsPassword") - - if c.HasError() { - c.Success(SETTINGS_PASSWORD) - return - } - - if !c.User.ValidatePassword(f.OldPassword) { - c.Flash.Error(c.Tr("settings.password_incorrect")) - } else if f.Password != f.Retype { - c.Flash.Error(c.Tr("form.password_not_match")) - } else { - c.User.Passwd = f.Password - var err error - if c.User.Salt, err = models.GetUserSalt(); err != nil { - c.ServerError("GetUserSalt", err) - return - } - c.User.EncodePasswd() - if err := models.UpdateUser(c.User); err != nil { - c.ServerError("UpdateUser", err) - return - } - c.Flash.Success(c.Tr("settings.change_password_success")) - } - - c.SubURLRedirect("/user/settings/password") -} - -func SettingsEmails(c *context.Context) { - c.Title("settings.emails") - c.PageIs("SettingsEmails") - - emails, err := models.GetEmailAddresses(c.User.ID) - if err != nil { - c.ServerError("GetEmailAddresses", err) - return - } - c.Data["Emails"] = emails - - c.Success(SETTINGS_EMAILS) -} - -func SettingsEmailPost(c *context.Context, f form.AddEmail) { - c.Title("settings.emails") - c.PageIs("SettingsEmails") - - // Make emailaddress primary. - if c.Query("_method") == "PRIMARY" { - if err := models.MakeEmailPrimary(&models.EmailAddress{ID: c.QueryInt64("id")}); err != nil { - c.ServerError("MakeEmailPrimary", err) - return - } - - c.SubURLRedirect("/user/settings/email") - return - } - - // Add Email address. - emails, err := models.GetEmailAddresses(c.User.ID) - if err != nil { - c.ServerError("GetEmailAddresses", err) - return - } - c.Data["Emails"] = emails - - if c.HasError() { - c.Success(SETTINGS_EMAILS) - return - } - - email := &models.EmailAddress{ - UID: c.User.ID, - Email: f.Email, - IsActivated: !setting.Service.RegisterEmailConfirm, - } - if err := models.AddEmailAddress(email); err != nil { - if models.IsErrEmailAlreadyUsed(err) { - c.RenderWithErr(c.Tr("form.email_been_used"), SETTINGS_EMAILS, &f) - } else { - c.ServerError("AddEmailAddress", err) - } - return - } - - // Send confirmation email - if setting.Service.RegisterEmailConfirm { - mailer.SendActivateEmailMail(c.Context, models.NewMailerUser(c.User), email.Email) - - if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil { - log.Error(2, "Set cache 'MailResendLimit' failed: %v", err) - } - c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, setting.Service.ActiveCodeLives/60)) - } else { - c.Flash.Success(c.Tr("settings.add_email_success")) - } - - c.SubURLRedirect("/user/settings/email") -} - -func DeleteEmail(c *context.Context) { - if err := models.DeleteEmailAddress(&models.EmailAddress{ - ID: c.QueryInt64("id"), - UID: c.User.ID, - }); err != nil { - c.ServerError("DeleteEmailAddress", err) - return - } - - c.Flash.Success(c.Tr("settings.email_deletion_success")) - c.JSONSuccess(map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/email", - }) -} - -func SettingsSSHKeys(c *context.Context) { - c.Title("settings.ssh_keys") - c.PageIs("SettingsSSHKeys") - - keys, err := models.ListPublicKeys(c.User.ID) - if err != nil { - c.ServerError("ListPublicKeys", err) - return - } - c.Data["Keys"] = keys - - c.Success(SETTINGS_SSH_KEYS) -} - -func SettingsSSHKeysPost(c *context.Context, f form.AddSSHKey) { - c.Title("settings.ssh_keys") - c.PageIs("SettingsSSHKeys") - - keys, err := models.ListPublicKeys(c.User.ID) - if err != nil { - c.ServerError("ListPublicKeys", err) - return - } - c.Data["Keys"] = keys - - if c.HasError() { - c.Success(SETTINGS_SSH_KEYS) - return - } - - content, err := models.CheckPublicKeyString(f.Content) - if err != nil { - if models.IsErrKeyUnableVerify(err) { - c.Flash.Info(c.Tr("form.unable_verify_ssh_key")) - } else { - c.Flash.Error(c.Tr("form.invalid_ssh_key", err.Error())) - c.SubURLRedirect("/user/settings/ssh") - return - } - } - - if _, err = models.AddPublicKey(c.User.ID, f.Title, content); err != nil { - c.Data["HasError"] = true - switch { - case models.IsErrKeyAlreadyExist(err): - c.FormErr("Content") - c.RenderWithErr(c.Tr("settings.ssh_key_been_used"), SETTINGS_SSH_KEYS, &f) - case models.IsErrKeyNameAlreadyUsed(err): - c.FormErr("Title") - c.RenderWithErr(c.Tr("settings.ssh_key_name_used"), SETTINGS_SSH_KEYS, &f) - default: - c.ServerError("AddPublicKey", err) - } - return - } - - c.Flash.Success(c.Tr("settings.add_key_success", f.Title)) - c.SubURLRedirect("/user/settings/ssh") -} - -func DeleteSSHKey(c *context.Context) { - if err := models.DeletePublicKey(c.User, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeletePublicKey: " + err.Error()) - } else { - c.Flash.Success(c.Tr("settings.ssh_key_deletion_success")) - } - - c.JSONSuccess(map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/ssh", - }) -} - -func SettingsSecurity(c *context.Context) { - c.Title("settings.security") - c.PageIs("SettingsSecurity") - - t, err := models.GetTwoFactorByUserID(c.UserID()) - if err != nil && !errors.IsTwoFactorNotFound(err) { - c.ServerError("GetTwoFactorByUserID", err) - return - } - c.Data["TwoFactor"] = t - - c.Success(SETTINGS_SECURITY) -} - -func SettingsTwoFactorEnable(c *context.Context) { - if c.User.IsEnabledTwoFactor() { - c.NotFound() - return - } - - c.Title("settings.two_factor_enable_title") - c.PageIs("SettingsSecurity") - - var key *otp.Key - var err error - keyURL := c.Session.Get("twoFactorURL") - if keyURL != nil { - key, _ = otp.NewKeyFromURL(keyURL.(string)) - } - if key == nil { - key, err = totp.Generate(totp.GenerateOpts{ - Issuer: setting.AppName, - AccountName: c.User.Email, - }) - if err != nil { - c.ServerError("Generate", err) - return - } - } - c.Data["TwoFactorSecret"] = key.Secret() - - img, err := key.Image(240, 240) - if err != nil { - c.ServerError("Image", err) - return - } - - var buf bytes.Buffer - if err = png.Encode(&buf, img); err != nil { - c.ServerError("Encode", err) - return - } - c.Data["QRCode"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())) - - c.Session.Set("twoFactorSecret", c.Data["TwoFactorSecret"]) - c.Session.Set("twoFactorURL", key.String()) - c.Success(SETTINGS_TWO_FACTOR_ENABLE) -} - -func SettingsTwoFactorEnablePost(c *context.Context) { - secret, ok := c.Session.Get("twoFactorSecret").(string) - if !ok { - c.NotFound() - return - } - - if !totp.Validate(c.Query("passcode"), secret) { - c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode")) - c.SubURLRedirect("/user/settings/security/two_factor_enable") - return - } - - if err := models.NewTwoFactor(c.UserID(), secret); err != nil { - c.Flash.Error(c.Tr("settings.two_factor_enable_error", err)) - c.SubURLRedirect("/user/settings/security/two_factor_enable") - return - } - - c.Session.Delete("twoFactorSecret") - c.Session.Delete("twoFactorURL") - c.Flash.Success(c.Tr("settings.two_factor_enable_success")) - c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes") -} - -func SettingsTwoFactorRecoveryCodes(c *context.Context) { - if !c.User.IsEnabledTwoFactor() { - c.NotFound() - return - } - - c.Title("settings.two_factor_recovery_codes_title") - c.PageIs("SettingsSecurity") - - recoveryCodes, err := models.GetRecoveryCodesByUserID(c.UserID()) - if err != nil { - c.ServerError("GetRecoveryCodesByUserID", err) - return - } - c.Data["RecoveryCodes"] = recoveryCodes - - c.Success(SETTINGS_TWO_FACTOR_RECOVERY_CODES) -} - -func SettingsTwoFactorRecoveryCodesPost(c *context.Context) { - if !c.User.IsEnabledTwoFactor() { - c.NotFound() - return - } - - if err := models.RegenerateRecoveryCodes(c.UserID()); err != nil { - c.Flash.Error(c.Tr("settings.two_factor_regenerate_recovery_codes_error", err)) - } else { - c.Flash.Success(c.Tr("settings.two_factor_regenerate_recovery_codes_success")) - } - - c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes") -} - -func SettingsTwoFactorDisable(c *context.Context) { - if !c.User.IsEnabledTwoFactor() { - c.NotFound() - return - } - - if err := models.DeleteTwoFactor(c.UserID()); err != nil { - c.ServerError("DeleteTwoFactor", err) - return - } - - c.Flash.Success(c.Tr("settings.two_factor_disable_success")) - c.JSONSuccess(map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/security", - }) -} - -func SettingsRepos(c *context.Context) { - c.Title("settings.repos") - c.PageIs("SettingsRepositories") - - repos, err := models.GetUserAndCollaborativeRepositories(c.User.ID) - if err != nil { - c.ServerError("GetUserAndCollaborativeRepositories", err) - return - } - if err = models.RepositoryList(repos).LoadAttributes(); err != nil { - c.ServerError("LoadAttributes", err) - return - } - c.Data["Repos"] = repos - - c.Success(SETTINGS_REPOSITORIES) -} - -func SettingsLeaveRepo(c *context.Context) { - repo, err := models.GetRepositoryByID(c.QueryInt64("id")) - if err != nil { - c.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err) - return - } - - if err = repo.DeleteCollaboration(c.User.ID); err != nil { - c.ServerError("DeleteCollaboration", err) - return - } - - c.Flash.Success(c.Tr("settings.repos.leave_success", repo.FullName())) - c.JSONSuccess(map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/repositories", - }) -} - -func SettingsOrganizations(c *context.Context) { - c.Title("settings.orgs") - c.PageIs("SettingsOrganizations") - - orgs, err := models.GetOrgsByUserID(c.User.ID, true) - if err != nil { - c.ServerError("GetOrgsByUserID", err) - return - } - c.Data["Orgs"] = orgs - - c.Success(SETTINGS_ORGANIZATIONS) -} - -func SettingsLeaveOrganization(c *context.Context) { - if err := models.RemoveOrgUser(c.QueryInt64("id"), c.User.ID); err != nil { - if models.IsErrLastOrgOwner(err) { - c.Flash.Error(c.Tr("form.last_org_owner")) - } else { - c.ServerError("RemoveOrgUser", err) - return - } - } - - c.JSONSuccess(map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/organizations", - }) -} - -func SettingsApplications(c *context.Context) { - c.Title("settings.applications") - c.PageIs("SettingsApplications") - - tokens, err := models.ListAccessTokens(c.User.ID) - if err != nil { - c.ServerError("ListAccessTokens", err) - return - } - c.Data["Tokens"] = tokens - - c.Success(SETTINGS_APPLICATIONS) -} - -func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) { - c.Title("settings.applications") - c.PageIs("SettingsApplications") - - if c.HasError() { - tokens, err := models.ListAccessTokens(c.User.ID) - if err != nil { - c.ServerError("ListAccessTokens", err) - return - } - - c.Data["Tokens"] = tokens - c.Success(SETTINGS_APPLICATIONS) - return - } - - t := &models.AccessToken{ - UID: c.User.ID, - Name: f.Name, - } - if err := models.NewAccessToken(t); err != nil { - c.ServerError("NewAccessToken", err) - return - } - - c.Flash.Success(c.Tr("settings.generate_token_succees")) - c.Flash.Info(t.Sha1) - c.SubURLRedirect("/user/settings/applications") -} - -func SettingsDeleteApplication(c *context.Context) { - if err := models.DeleteAccessTokenOfUserByID(c.User.ID, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteAccessTokenByID: " + err.Error()) - } else { - c.Flash.Success(c.Tr("settings.delete_token_success")) - } - - c.JSONSuccess(map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/applications", - }) -} - -func SettingsDelete(c *context.Context) { - c.Title("settings.delete") - c.PageIs("SettingsDelete") - - if c.Req.Method == "POST" { - if _, err := models.UserSignIn(c.User.Name, c.Query("password")); err != nil { - if errors.IsUserNotExist(err) { - c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil) - } else { - c.ServerError("UserSignIn", err) - } - return - } - - if err := models.DeleteUser(c.User); err != nil { - switch { - case models.IsErrUserOwnRepos(err): - c.Flash.Error(c.Tr("form.still_own_repo")) - c.Redirect(setting.AppSubURL + "/user/settings/delete") - case models.IsErrUserHasOrgs(err): - c.Flash.Error(c.Tr("form.still_has_org")) - c.Redirect(setting.AppSubURL + "/user/settings/delete") - default: - c.ServerError("DeleteUser", err) - } - } else { - log.Trace("Account deleted: %s", c.User.Name) - c.Redirect(setting.AppSubURL + "/") - } - return - } - - c.Success(SETTINGS_DELETE) -} diff --git a/routes/admin/admin.go b/routes/admin/admin.go new file mode 100644 index 00000000..0d5eb7a6 --- /dev/null +++ b/routes/admin/admin.go @@ -0,0 +1,258 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "encoding/json" + "fmt" + "runtime" + "strings" + "time" + + "github.com/Unknwon/com" + "gopkg.in/macaron.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/cron" + "github.com/gogits/gogs/pkg/mailer" + "github.com/gogits/gogs/pkg/process" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + DASHBOARD = "admin/dashboard" + CONFIG = "admin/config" + MONITOR = "admin/monitor" +) + +var ( + startTime = time.Now() +) + +var sysStatus struct { + Uptime string + NumGoroutine int + + // General statistics. + MemAllocated string // bytes allocated and still in use + MemTotal string // bytes allocated (even if freed) + MemSys string // bytes obtained from system (sum of XxxSys below) + Lookups uint64 // number of pointer lookups + MemMallocs uint64 // number of mallocs + MemFrees uint64 // number of frees + + // Main allocation heap statistics. + HeapAlloc string // bytes allocated and still in use + HeapSys string // bytes obtained from system + HeapIdle string // bytes in idle spans + HeapInuse string // bytes in non-idle span + HeapReleased string // bytes released to the OS + HeapObjects uint64 // total number of allocated objects + + // Low-level fixed-size structure allocator statistics. + // Inuse is bytes used now. + // Sys is bytes obtained from system. + StackInuse string // bootstrap stacks + StackSys string + MSpanInuse string // mspan structures + MSpanSys string + MCacheInuse string // mcache structures + MCacheSys string + BuckHashSys string // profiling bucket hash table + GCSys string // GC metadata + OtherSys string // other system allocations + + // Garbage collector statistics. + NextGC string // next run in HeapAlloc time (bytes) + LastGC string // last run in absolute time (ns) + PauseTotalNs string + PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] + NumGC uint32 +} + +func updateSystemStatus() { + sysStatus.Uptime = tool.TimeSincePro(startTime) + + m := new(runtime.MemStats) + runtime.ReadMemStats(m) + sysStatus.NumGoroutine = runtime.NumGoroutine() + + sysStatus.MemAllocated = tool.FileSize(int64(m.Alloc)) + sysStatus.MemTotal = tool.FileSize(int64(m.TotalAlloc)) + sysStatus.MemSys = tool.FileSize(int64(m.Sys)) + sysStatus.Lookups = m.Lookups + sysStatus.MemMallocs = m.Mallocs + sysStatus.MemFrees = m.Frees + + sysStatus.HeapAlloc = tool.FileSize(int64(m.HeapAlloc)) + sysStatus.HeapSys = tool.FileSize(int64(m.HeapSys)) + sysStatus.HeapIdle = tool.FileSize(int64(m.HeapIdle)) + sysStatus.HeapInuse = tool.FileSize(int64(m.HeapInuse)) + sysStatus.HeapReleased = tool.FileSize(int64(m.HeapReleased)) + sysStatus.HeapObjects = m.HeapObjects + + sysStatus.StackInuse = tool.FileSize(int64(m.StackInuse)) + sysStatus.StackSys = tool.FileSize(int64(m.StackSys)) + sysStatus.MSpanInuse = tool.FileSize(int64(m.MSpanInuse)) + sysStatus.MSpanSys = tool.FileSize(int64(m.MSpanSys)) + sysStatus.MCacheInuse = tool.FileSize(int64(m.MCacheInuse)) + sysStatus.MCacheSys = tool.FileSize(int64(m.MCacheSys)) + sysStatus.BuckHashSys = tool.FileSize(int64(m.BuckHashSys)) + sysStatus.GCSys = tool.FileSize(int64(m.GCSys)) + sysStatus.OtherSys = tool.FileSize(int64(m.OtherSys)) + + sysStatus.NextGC = tool.FileSize(int64(m.NextGC)) + sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) + sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) + sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) + sysStatus.NumGC = m.NumGC +} + +// Operation types. +type AdminOperation int + +const ( + CLEAN_INACTIVATE_USER AdminOperation = iota + 1 + CLEAN_REPO_ARCHIVES + CLEAN_MISSING_REPOS + GIT_GC_REPOS + SYNC_SSH_AUTHORIZED_KEY + SYNC_REPOSITORY_HOOKS + REINIT_MISSING_REPOSITORY +) + +func Dashboard(c *context.Context) { + c.Data["Title"] = c.Tr("admin.dashboard") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminDashboard"] = true + + // Run operation. + op, _ := com.StrTo(c.Query("op")).Int() + if op > 0 { + var err error + var success string + + switch AdminOperation(op) { + case CLEAN_INACTIVATE_USER: + success = c.Tr("admin.dashboard.delete_inactivate_accounts_success") + err = models.DeleteInactivateUsers() + case CLEAN_REPO_ARCHIVES: + success = c.Tr("admin.dashboard.delete_repo_archives_success") + err = models.DeleteRepositoryArchives() + case CLEAN_MISSING_REPOS: + success = c.Tr("admin.dashboard.delete_missing_repos_success") + err = models.DeleteMissingRepositories() + case GIT_GC_REPOS: + success = c.Tr("admin.dashboard.git_gc_repos_success") + err = models.GitGcRepos() + case SYNC_SSH_AUTHORIZED_KEY: + success = c.Tr("admin.dashboard.resync_all_sshkeys_success") + err = models.RewriteAllPublicKeys() + case SYNC_REPOSITORY_HOOKS: + success = c.Tr("admin.dashboard.resync_all_hooks_success") + err = models.SyncRepositoryHooks() + case REINIT_MISSING_REPOSITORY: + success = c.Tr("admin.dashboard.reinit_missing_repos_success") + err = models.ReinitMissingRepositories() + } + + if err != nil { + c.Flash.Error(err.Error()) + } else { + c.Flash.Success(success) + } + c.Redirect(setting.AppSubURL + "/admin") + return + } + + c.Data["Stats"] = models.GetStatistic() + // FIXME: update periodically + updateSystemStatus() + c.Data["SysStatus"] = sysStatus + c.HTML(200, DASHBOARD) +} + +func SendTestMail(c *context.Context) { + email := c.Query("email") + // Send a test email to the user's email address and redirect back to Config + if err := mailer.SendTestMail(email); err != nil { + c.Flash.Error(c.Tr("admin.config.test_mail_failed", email, err)) + } else { + c.Flash.Info(c.Tr("admin.config.test_mail_sent", email)) + } + + c.Redirect(setting.AppSubURL + "/admin/config") +} + +func Config(c *context.Context) { + c.Data["Title"] = c.Tr("admin.config") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminConfig"] = true + + c.Data["AppURL"] = setting.AppURL + c.Data["Domain"] = setting.Domain + c.Data["OfflineMode"] = setting.OfflineMode + c.Data["DisableRouterLog"] = setting.DisableRouterLog + c.Data["RunUser"] = setting.RunUser + c.Data["RunMode"] = strings.Title(macaron.Env) + c.Data["StaticRootPath"] = setting.StaticRootPath + c.Data["LogRootPath"] = setting.LogRootPath + c.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser + + c.Data["SSH"] = setting.SSH + + c.Data["RepoRootPath"] = setting.RepoRootPath + c.Data["ScriptType"] = setting.ScriptType + c.Data["Repository"] = setting.Repository + + c.Data["Service"] = setting.Service + c.Data["DbCfg"] = models.DbCfg + c.Data["Webhook"] = setting.Webhook + + c.Data["MailerEnabled"] = false + if setting.MailService != nil { + c.Data["MailerEnabled"] = true + c.Data["Mailer"] = setting.MailService + } + + c.Data["CacheAdapter"] = setting.CacheAdapter + c.Data["CacheInterval"] = setting.CacheInterval + c.Data["CacheConn"] = setting.CacheConn + + c.Data["SessionConfig"] = setting.SessionConfig + + c.Data["DisableGravatar"] = setting.DisableGravatar + c.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar + + c.Data["GitVersion"] = setting.Git.Version + c.Data["Git"] = setting.Git + + type logger struct { + Mode, Config string + } + loggers := make([]*logger, len(setting.LogModes)) + for i := range setting.LogModes { + loggers[i] = &logger{ + Mode: strings.Title(setting.LogModes[i]), + } + + result, _ := json.MarshalIndent(setting.LogConfigs[i], "", " ") + loggers[i].Config = string(result) + } + c.Data["Loggers"] = loggers + + c.HTML(200, CONFIG) +} + +func Monitor(c *context.Context) { + c.Data["Title"] = c.Tr("admin.monitor") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminMonitor"] = true + c.Data["Processes"] = process.Processes + c.Data["Entries"] = cron.ListTasks() + c.HTML(200, MONITOR) +} diff --git a/routes/admin/auths.go b/routes/admin/auths.go new file mode 100644 index 00000000..56a0aad6 --- /dev/null +++ b/routes/admin/auths.go @@ -0,0 +1,265 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "fmt" + + "github.com/Unknwon/com" + "github.com/go-xorm/core" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/auth/ldap" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + AUTHS = "admin/auth/list" + AUTH_NEW = "admin/auth/new" + AUTH_EDIT = "admin/auth/edit" +) + +func Authentications(c *context.Context) { + c.Data["Title"] = c.Tr("admin.authentication") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminAuthentications"] = true + + var err error + c.Data["Sources"], err = models.LoginSources() + if err != nil { + c.Handle(500, "LoginSources", err) + return + } + + c.Data["Total"] = models.CountLoginSources() + c.HTML(200, AUTHS) +} + +type dropdownItem struct { + Name string + Type interface{} +} + +var ( + authSources = []dropdownItem{ + {models.LoginNames[models.LOGIN_LDAP], models.LOGIN_LDAP}, + {models.LoginNames[models.LOGIN_DLDAP], models.LOGIN_DLDAP}, + {models.LoginNames[models.LOGIN_SMTP], models.LOGIN_SMTP}, + {models.LoginNames[models.LOGIN_PAM], models.LOGIN_PAM}, + } + securityProtocols = []dropdownItem{ + {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED}, + {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_LDAPS], ldap.SECURITY_PROTOCOL_LDAPS}, + {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_START_TLS], ldap.SECURITY_PROTOCOL_START_TLS}, + } +) + +func NewAuthSource(c *context.Context) { + c.Data["Title"] = c.Tr("admin.auths.new") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminAuthentications"] = true + + c.Data["type"] = models.LOGIN_LDAP + c.Data["CurrentTypeName"] = models.LoginNames[models.LOGIN_LDAP] + c.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED] + c.Data["smtp_auth"] = "PLAIN" + c.Data["is_active"] = true + c.Data["AuthSources"] = authSources + c.Data["SecurityProtocols"] = securityProtocols + c.Data["SMTPAuths"] = models.SMTPAuths + c.HTML(200, AUTH_NEW) +} + +func parseLDAPConfig(f form.Authentication) *models.LDAPConfig { + return &models.LDAPConfig{ + Source: &ldap.Source{ + Name: f.Name, + Host: f.Host, + Port: f.Port, + SecurityProtocol: ldap.SecurityProtocol(f.SecurityProtocol), + SkipVerify: f.SkipVerify, + BindDN: f.BindDN, + UserDN: f.UserDN, + BindPassword: f.BindPassword, + UserBase: f.UserBase, + AttributeUsername: f.AttributeUsername, + AttributeName: f.AttributeName, + AttributeSurname: f.AttributeSurname, + AttributeMail: f.AttributeMail, + AttributesInBind: f.AttributesInBind, + Filter: f.Filter, + GroupEnabled: f.GroupEnabled, + GroupDN: f.GroupDN, + GroupFilter: f.GroupFilter, + GroupMemberUID: f.GroupMemberUID, + UserUID: f.UserUID, + AdminFilter: f.AdminFilter, + Enabled: true, + }, + } +} + +func parseSMTPConfig(f form.Authentication) *models.SMTPConfig { + return &models.SMTPConfig{ + Auth: f.SMTPAuth, + Host: f.SMTPHost, + Port: f.SMTPPort, + AllowedDomains: f.AllowedDomains, + TLS: f.TLS, + SkipVerify: f.SkipVerify, + } +} + +func NewAuthSourcePost(c *context.Context, f form.Authentication) { + c.Data["Title"] = c.Tr("admin.auths.new") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminAuthentications"] = true + + c.Data["CurrentTypeName"] = models.LoginNames[models.LoginType(f.Type)] + c.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SecurityProtocol(f.SecurityProtocol)] + c.Data["AuthSources"] = authSources + c.Data["SecurityProtocols"] = securityProtocols + c.Data["SMTPAuths"] = models.SMTPAuths + + hasTLS := false + var config core.Conversion + switch models.LoginType(f.Type) { + case models.LOGIN_LDAP, models.LOGIN_DLDAP: + config = parseLDAPConfig(f) + hasTLS = ldap.SecurityProtocol(f.SecurityProtocol) > ldap.SECURITY_PROTOCOL_UNENCRYPTED + case models.LOGIN_SMTP: + config = parseSMTPConfig(f) + hasTLS = true + case models.LOGIN_PAM: + config = &models.PAMConfig{ + ServiceName: f.PAMServiceName, + } + default: + c.Error(400) + return + } + c.Data["HasTLS"] = hasTLS + + if c.HasError() { + c.HTML(200, AUTH_NEW) + return + } + + if err := models.CreateLoginSource(&models.LoginSource{ + Type: models.LoginType(f.Type), + Name: f.Name, + IsActived: f.IsActive, + Cfg: config, + }); err != nil { + if models.IsErrLoginSourceAlreadyExist(err) { + c.Data["Err_Name"] = true + c.RenderWithErr(c.Tr("admin.auths.login_source_exist", err.(models.ErrLoginSourceAlreadyExist).Name), AUTH_NEW, f) + } else { + c.Handle(500, "CreateSource", err) + } + return + } + + log.Trace("Authentication created by admin(%s): %s", c.User.Name, f.Name) + + c.Flash.Success(c.Tr("admin.auths.new_success", f.Name)) + c.Redirect(setting.AppSubURL + "/admin/auths") +} + +func EditAuthSource(c *context.Context) { + c.Data["Title"] = c.Tr("admin.auths.edit") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminAuthentications"] = true + + c.Data["SecurityProtocols"] = securityProtocols + c.Data["SMTPAuths"] = models.SMTPAuths + + source, err := models.GetLoginSourceByID(c.ParamsInt64(":authid")) + if err != nil { + c.Handle(500, "GetLoginSourceByID", err) + return + } + c.Data["Source"] = source + c.Data["HasTLS"] = source.HasTLS() + + c.HTML(200, AUTH_EDIT) +} + +func EditAuthSourcePost(c *context.Context, f form.Authentication) { + c.Data["Title"] = c.Tr("admin.auths.edit") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminAuthentications"] = true + + c.Data["SMTPAuths"] = models.SMTPAuths + + source, err := models.GetLoginSourceByID(c.ParamsInt64(":authid")) + if err != nil { + c.Handle(500, "GetLoginSourceByID", err) + return + } + c.Data["Source"] = source + c.Data["HasTLS"] = source.HasTLS() + + if c.HasError() { + c.HTML(200, AUTH_EDIT) + return + } + + var config core.Conversion + switch models.LoginType(f.Type) { + case models.LOGIN_LDAP, models.LOGIN_DLDAP: + config = parseLDAPConfig(f) + case models.LOGIN_SMTP: + config = parseSMTPConfig(f) + case models.LOGIN_PAM: + config = &models.PAMConfig{ + ServiceName: f.PAMServiceName, + } + default: + c.Error(400) + return + } + + source.Name = f.Name + source.IsActived = f.IsActive + source.Cfg = config + if err := models.UpdateSource(source); err != nil { + c.Handle(500, "UpdateSource", err) + return + } + log.Trace("Authentication changed by admin(%s): %d", c.User.Name, source.ID) + + c.Flash.Success(c.Tr("admin.auths.update_success")) + c.Redirect(setting.AppSubURL + "/admin/auths/" + com.ToStr(f.ID)) +} + +func DeleteAuthSource(c *context.Context) { + source, err := models.GetLoginSourceByID(c.ParamsInt64(":authid")) + if err != nil { + c.Handle(500, "GetLoginSourceByID", err) + return + } + + if err = models.DeleteSource(source); err != nil { + if models.IsErrLoginSourceInUse(err) { + c.Flash.Error(c.Tr("admin.auths.still_in_used")) + } else { + c.Flash.Error(fmt.Sprintf("DeleteSource: %v", err)) + } + c.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/auths/" + c.Params(":authid"), + }) + return + } + log.Trace("Authentication deleted by admin(%s): %d", c.User.Name, source.ID) + + c.Flash.Success(c.Tr("admin.auths.deletion_success")) + c.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/auths", + }) +} diff --git a/routes/admin/notice.go b/routes/admin/notice.go new file mode 100644 index 00000000..c743a1da --- /dev/null +++ b/routes/admin/notice.go @@ -0,0 +1,72 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "github.com/Unknwon/com" + "github.com/Unknwon/paginater" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + NOTICES = "admin/notice" +) + +func Notices(c *context.Context) { + c.Data["Title"] = c.Tr("admin.notices") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminNotices"] = true + + total := models.CountNotices() + page := c.QueryInt("page") + if page <= 1 { + page = 1 + } + c.Data["Page"] = paginater.New(int(total), setting.UI.Admin.NoticePagingNum, page, 5) + + notices, err := models.Notices(page, setting.UI.Admin.NoticePagingNum) + if err != nil { + c.Handle(500, "Notices", err) + return + } + c.Data["Notices"] = notices + + c.Data["Total"] = total + c.HTML(200, NOTICES) +} + +func DeleteNotices(c *context.Context) { + strs := c.QueryStrings("ids[]") + ids := make([]int64, 0, len(strs)) + for i := range strs { + id := com.StrTo(strs[i]).MustInt64() + if id > 0 { + ids = append(ids, id) + } + } + + if err := models.DeleteNoticesByIDs(ids); err != nil { + c.Flash.Error("DeleteNoticesByIDs: " + err.Error()) + c.Status(500) + } else { + c.Flash.Success(c.Tr("admin.notices.delete_success")) + c.Status(200) + } +} + +func EmptyNotices(c *context.Context) { + if err := models.DeleteNotices(0, 0); err != nil { + c.Handle(500, "DeleteNotices", err) + return + } + + log.Trace("System notices deleted by admin (%s): [start: %d]", c.User.Name, 0) + c.Flash.Success(c.Tr("admin.notices.delete_success")) + c.Redirect(setting.AppSubURL + "/admin/notices") +} diff --git a/routes/admin/orgs.go b/routes/admin/orgs.go new file mode 100644 index 00000000..f42e1fdf --- /dev/null +++ b/routes/admin/orgs.go @@ -0,0 +1,31 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes" +) + +const ( + ORGS = "admin/org/list" +) + +func Organizations(c *context.Context) { + c.Data["Title"] = c.Tr("admin.organizations") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminOrganizations"] = true + + routes.RenderUserSearch(c, &routes.UserSearchOptions{ + Type: models.USER_TYPE_ORGANIZATION, + Counter: models.CountOrganizations, + Ranger: models.Organizations, + PageSize: setting.UI.Admin.OrgPagingNum, + OrderBy: "id ASC", + TplName: ORGS, + }) +} diff --git a/routes/admin/repos.go b/routes/admin/repos.go new file mode 100644 index 00000000..b4fa2266 --- /dev/null +++ b/routes/admin/repos.go @@ -0,0 +1,87 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "github.com/Unknwon/paginater" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + REPOS = "admin/repo/list" +) + +func Repos(c *context.Context) { + c.Data["Title"] = c.Tr("admin.repositories") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminRepositories"] = true + + page := c.QueryInt("page") + if page <= 0 { + page = 1 + } + + var ( + repos []*models.Repository + count int64 + err error + ) + + keyword := c.Query("q") + if len(keyword) == 0 { + repos, err = models.Repositories(page, setting.UI.Admin.RepoPagingNum) + if err != nil { + c.Handle(500, "Repositories", err) + return + } + count = models.CountRepositories(true) + } else { + repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ + Keyword: keyword, + OrderBy: "id ASC", + Private: true, + Page: page, + PageSize: setting.UI.Admin.RepoPagingNum, + }) + if err != nil { + c.Handle(500, "SearchRepositoryByName", err) + return + } + } + c.Data["Keyword"] = keyword + c.Data["Total"] = count + c.Data["Page"] = paginater.New(int(count), setting.UI.Admin.RepoPagingNum, page, 5) + + if err = models.RepositoryList(repos).LoadAttributes(); err != nil { + c.Handle(500, "LoadAttributes", err) + return + } + c.Data["Repos"] = repos + + c.HTML(200, REPOS) +} + +func DeleteRepo(c *context.Context) { + repo, err := models.GetRepositoryByID(c.QueryInt64("id")) + if err != nil { + c.Handle(500, "GetRepositoryByID", err) + return + } + + if err := models.DeleteRepository(repo.MustOwner().ID, repo.ID); err != nil { + c.Handle(500, "DeleteRepository", err) + return + } + log.Trace("Repository deleted: %s/%s", repo.MustOwner().Name, repo.Name) + + c.Flash.Success(c.Tr("repo.settings.deletion_success")) + c.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/repos?page=" + c.Query("page"), + }) +} diff --git a/routes/admin/users.go b/routes/admin/users.go new file mode 100644 index 00000000..cfeb73de --- /dev/null +++ b/routes/admin/users.go @@ -0,0 +1,262 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "strings" + + "github.com/Unknwon/com" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/mailer" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes" +) + +const ( + USERS = "admin/user/list" + USER_NEW = "admin/user/new" + USER_EDIT = "admin/user/edit" +) + +func Users(c *context.Context) { + c.Data["Title"] = c.Tr("admin.users") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminUsers"] = true + + routes.RenderUserSearch(c, &routes.UserSearchOptions{ + Type: models.USER_TYPE_INDIVIDUAL, + Counter: models.CountUsers, + Ranger: models.Users, + PageSize: setting.UI.Admin.UserPagingNum, + OrderBy: "id ASC", + TplName: USERS, + }) +} + +func NewUser(c *context.Context) { + c.Data["Title"] = c.Tr("admin.users.new_account") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminUsers"] = true + + c.Data["login_type"] = "0-0" + + sources, err := models.LoginSources() + if err != nil { + c.Handle(500, "LoginSources", err) + return + } + c.Data["Sources"] = sources + + c.Data["CanSendEmail"] = setting.MailService != nil + c.HTML(200, USER_NEW) +} + +func NewUserPost(c *context.Context, f form.AdminCrateUser) { + c.Data["Title"] = c.Tr("admin.users.new_account") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminUsers"] = true + + sources, err := models.LoginSources() + if err != nil { + c.Handle(500, "LoginSources", err) + return + } + c.Data["Sources"] = sources + + c.Data["CanSendEmail"] = setting.MailService != nil + + if c.HasError() { + c.HTML(200, USER_NEW) + return + } + + u := &models.User{ + Name: f.UserName, + Email: f.Email, + Passwd: f.Password, + IsActive: true, + LoginType: models.LOGIN_PLAIN, + } + + if len(f.LoginType) > 0 { + fields := strings.Split(f.LoginType, "-") + if len(fields) == 2 { + u.LoginType = models.LoginType(com.StrTo(fields[0]).MustInt()) + u.LoginSource = com.StrTo(fields[1]).MustInt64() + u.LoginName = f.LoginName + } + } + + if err := models.CreateUser(u); err != nil { + switch { + case models.IsErrUserAlreadyExist(err): + c.Data["Err_UserName"] = true + c.RenderWithErr(c.Tr("form.username_been_taken"), USER_NEW, &f) + case models.IsErrEmailAlreadyUsed(err): + c.Data["Err_Email"] = true + c.RenderWithErr(c.Tr("form.email_been_used"), USER_NEW, &f) + case models.IsErrNameReserved(err): + c.Data["Err_UserName"] = true + c.RenderWithErr(c.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), USER_NEW, &f) + case models.IsErrNamePatternNotAllowed(err): + c.Data["Err_UserName"] = true + c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), USER_NEW, &f) + default: + c.Handle(500, "CreateUser", err) + } + return + } + log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name) + + // Send email notification. + if f.SendNotify && setting.MailService != nil { + mailer.SendRegisterNotifyMail(c.Context, models.NewMailerUser(u)) + } + + c.Flash.Success(c.Tr("admin.users.new_success", u.Name)) + c.Redirect(setting.AppSubURL + "/admin/users/" + com.ToStr(u.ID)) +} + +func prepareUserInfo(c *context.Context) *models.User { + u, err := models.GetUserByID(c.ParamsInt64(":userid")) + if err != nil { + c.Handle(500, "GetUserByID", err) + return nil + } + c.Data["User"] = u + + if u.LoginSource > 0 { + c.Data["LoginSource"], err = models.GetLoginSourceByID(u.LoginSource) + if err != nil { + c.Handle(500, "GetLoginSourceByID", err) + return nil + } + } else { + c.Data["LoginSource"] = &models.LoginSource{} + } + + sources, err := models.LoginSources() + if err != nil { + c.Handle(500, "LoginSources", err) + return nil + } + c.Data["Sources"] = sources + + return u +} + +func EditUser(c *context.Context) { + c.Data["Title"] = c.Tr("admin.users.edit_account") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminUsers"] = true + c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration + + prepareUserInfo(c) + if c.Written() { + return + } + + c.HTML(200, USER_EDIT) +} + +func EditUserPost(c *context.Context, f form.AdminEditUser) { + c.Data["Title"] = c.Tr("admin.users.edit_account") + c.Data["PageIsAdmin"] = true + c.Data["PageIsAdminUsers"] = true + c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration + + u := prepareUserInfo(c) + if c.Written() { + return + } + + if c.HasError() { + c.HTML(200, USER_EDIT) + return + } + + fields := strings.Split(f.LoginType, "-") + if len(fields) == 2 { + loginType := models.LoginType(com.StrTo(fields[0]).MustInt()) + loginSource := com.StrTo(fields[1]).MustInt64() + + if u.LoginSource != loginSource { + u.LoginSource = loginSource + u.LoginType = loginType + } + } + + if len(f.Password) > 0 { + u.Passwd = f.Password + var err error + if u.Salt, err = models.GetUserSalt(); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + u.EncodePasswd() + } + + u.LoginName = f.LoginName + u.FullName = f.FullName + u.Email = f.Email + u.Website = f.Website + u.Location = f.Location + u.MaxRepoCreation = f.MaxRepoCreation + u.IsActive = f.Active + u.IsAdmin = f.Admin + u.AllowGitHook = f.AllowGitHook + u.AllowImportLocal = f.AllowImportLocal + u.ProhibitLogin = f.ProhibitLogin + + if err := models.UpdateUser(u); err != nil { + if models.IsErrEmailAlreadyUsed(err) { + c.Data["Err_Email"] = true + c.RenderWithErr(c.Tr("form.email_been_used"), USER_EDIT, &f) + } else { + c.Handle(500, "UpdateUser", err) + } + return + } + log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name) + + c.Flash.Success(c.Tr("admin.users.update_profile_success")) + c.Redirect(setting.AppSubURL + "/admin/users/" + c.Params(":userid")) +} + +func DeleteUser(c *context.Context) { + u, err := models.GetUserByID(c.ParamsInt64(":userid")) + if err != nil { + c.Handle(500, "GetUserByID", err) + return + } + + if err = models.DeleteUser(u); err != nil { + switch { + case models.IsErrUserOwnRepos(err): + c.Flash.Error(c.Tr("admin.users.still_own_repo")) + c.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"), + }) + case models.IsErrUserHasOrgs(err): + c.Flash.Error(c.Tr("admin.users.still_has_org")) + c.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"), + }) + default: + c.Handle(500, "DeleteUser", err) + } + return + } + log.Trace("Account deleted by admin (%s): %s", c.User.Name, u.Name) + + c.Flash.Success(c.Tr("admin.users.deletion_success")) + c.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/users", + }) +} diff --git a/routes/api/v1/admin/org.go b/routes/api/v1/admin/org.go new file mode 100644 index 00000000..0f84ed2e --- /dev/null +++ b/routes/api/v1/admin/org.go @@ -0,0 +1,44 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/api/v1/convert" + "github.com/gogits/gogs/routes/api/v1/user" +) + +// https://github.com/gogits/go-gogs-client/wiki/Administration-Organizations#create-a-new-organization +func CreateOrg(c *context.APIContext, form api.CreateOrgOption) { + u := user.GetUserByParams(c) + if c.Written() { + return + } + + org := &models.User{ + Name: form.UserName, + FullName: form.FullName, + Description: form.Description, + Website: form.Website, + Location: form.Location, + IsActive: true, + Type: models.USER_TYPE_ORGANIZATION, + } + if err := models.CreateOrganization(org, u); err != nil { + if models.IsErrUserAlreadyExist(err) || + models.IsErrNameReserved(err) || + models.IsErrNamePatternNotAllowed(err) { + c.Error(422, "", err) + } else { + c.Error(500, "CreateOrganization", err) + } + return + } + + c.JSON(201, convert.ToOrganization(org)) +} diff --git a/routes/api/v1/admin/org_repo.go b/routes/api/v1/admin/org_repo.go new file mode 100644 index 00000000..7abad1a8 --- /dev/null +++ b/routes/api/v1/admin/org_repo.go @@ -0,0 +1,50 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" +) + +func GetRepositoryByParams(c *context.APIContext) *models.Repository { + repo, err := models.GetRepositoryByName(c.Org.Team.OrgID, c.Params(":reponame")) + if err != nil { + if errors.IsRepoNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetRepositoryByName", err) + } + return nil + } + return repo +} + +func AddTeamRepository(c *context.APIContext) { + repo := GetRepositoryByParams(c) + if c.Written() { + return + } + if err := c.Org.Team.AddRepository(repo); err != nil { + c.Error(500, "AddRepository", err) + return + } + + c.Status(204) +} + +func RemoveTeamRepository(c *context.APIContext) { + repo := GetRepositoryByParams(c) + if c.Written() { + return + } + if err := c.Org.Team.RemoveRepository(repo.ID); err != nil { + c.Error(500, "RemoveRepository", err) + return + } + + c.Status(204) +} diff --git a/routes/api/v1/admin/org_team.go b/routes/api/v1/admin/org_team.go new file mode 100644 index 00000000..ae748504 --- /dev/null +++ b/routes/api/v1/admin/org_team.go @@ -0,0 +1,60 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/api/v1/convert" + "github.com/gogits/gogs/routes/api/v1/user" +) + +func CreateTeam(c *context.APIContext, form api.CreateTeamOption) { + team := &models.Team{ + OrgID: c.Org.Organization.ID, + Name: form.Name, + Description: form.Description, + Authorize: models.ParseAccessMode(form.Permission), + } + if err := models.NewTeam(team); err != nil { + if models.IsErrTeamAlreadyExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "NewTeam", err) + } + return + } + + c.JSON(201, convert.ToTeam(team)) +} + +func AddTeamMember(c *context.APIContext) { + u := user.GetUserByParams(c) + if c.Written() { + return + } + if err := c.Org.Team.AddMember(u.ID); err != nil { + c.Error(500, "AddMember", err) + return + } + + c.Status(204) +} + +func RemoveTeamMember(c *context.APIContext) { + u := user.GetUserByParams(c) + if c.Written() { + return + } + + if err := c.Org.Team.RemoveMember(u.ID); err != nil { + c.Error(500, "RemoveMember", err) + return + } + + c.Status(204) +} diff --git a/routes/api/v1/admin/repo.go b/routes/api/v1/admin/repo.go new file mode 100644 index 00000000..920bac8d --- /dev/null +++ b/routes/api/v1/admin/repo.go @@ -0,0 +1,23 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/api/v1/repo" + "github.com/gogits/gogs/routes/api/v1/user" +) + +// https://github.com/gogits/go-gogs-client/wiki/Administration-Repositories#create-a-new-repository +func CreateRepo(c *context.APIContext, form api.CreateRepoOption) { + owner := user.GetUserByParams(c) + if c.Written() { + return + } + + repo.CreateUserRepo(c, owner, form) +} diff --git a/routes/api/v1/admin/user.go b/routes/api/v1/admin/user.go new file mode 100644 index 00000000..623911fd --- /dev/null +++ b/routes/api/v1/admin/user.go @@ -0,0 +1,160 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + log "gopkg.in/clog.v1" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/mailer" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/api/v1/user" +) + +func parseLoginSource(c *context.APIContext, u *models.User, sourceID int64, loginName string) { + if sourceID == 0 { + return + } + + source, err := models.GetLoginSourceByID(sourceID) + if err != nil { + if models.IsErrLoginSourceNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetLoginSourceByID", err) + } + return + } + + u.LoginType = source.Type + u.LoginSource = source.ID + u.LoginName = loginName +} + +// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#create-a-new-user +func CreateUser(c *context.APIContext, form api.CreateUserOption) { + u := &models.User{ + Name: form.Username, + FullName: form.FullName, + Email: form.Email, + Passwd: form.Password, + IsActive: true, + LoginType: models.LOGIN_PLAIN, + } + + parseLoginSource(c, u, form.SourceID, form.LoginName) + if c.Written() { + return + } + + if err := models.CreateUser(u); err != nil { + if models.IsErrUserAlreadyExist(err) || + models.IsErrEmailAlreadyUsed(err) || + models.IsErrNameReserved(err) || + models.IsErrNamePatternNotAllowed(err) { + c.Error(422, "", err) + } else { + c.Error(500, "CreateUser", err) + } + return + } + log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name) + + // Send email notification. + if form.SendNotify && setting.MailService != nil { + mailer.SendRegisterNotifyMail(c.Context.Context, models.NewMailerUser(u)) + } + + c.JSON(201, u.APIFormat()) +} + +// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#edit-an-existing-user +func EditUser(c *context.APIContext, form api.EditUserOption) { + u := user.GetUserByParams(c) + if c.Written() { + return + } + + parseLoginSource(c, u, form.SourceID, form.LoginName) + if c.Written() { + return + } + + if len(form.Password) > 0 { + u.Passwd = form.Password + var err error + if u.Salt, err = models.GetUserSalt(); err != nil { + c.Error(500, "UpdateUser", err) + return + } + u.EncodePasswd() + } + + u.LoginName = form.LoginName + u.FullName = form.FullName + u.Email = form.Email + u.Website = form.Website + u.Location = form.Location + if form.Active != nil { + u.IsActive = *form.Active + } + if form.Admin != nil { + u.IsAdmin = *form.Admin + } + if form.AllowGitHook != nil { + u.AllowGitHook = *form.AllowGitHook + } + if form.AllowImportLocal != nil { + u.AllowImportLocal = *form.AllowImportLocal + } + if form.MaxRepoCreation != nil { + u.MaxRepoCreation = *form.MaxRepoCreation + } + + if err := models.UpdateUser(u); err != nil { + if models.IsErrEmailAlreadyUsed(err) { + c.Error(422, "", err) + } else { + c.Error(500, "UpdateUser", err) + } + return + } + log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name) + + c.JSON(200, u.APIFormat()) +} + +// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#delete-a-user +func DeleteUser(c *context.APIContext) { + u := user.GetUserByParams(c) + if c.Written() { + return + } + + if err := models.DeleteUser(u); err != nil { + if models.IsErrUserOwnRepos(err) || + models.IsErrUserHasOrgs(err) { + c.Error(422, "", err) + } else { + c.Error(500, "DeleteUser", err) + } + return + } + log.Trace("Account deleted by admin(%s): %s", c.User.Name, u.Name) + + c.Status(204) +} + +// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#create-a-public-key-for-user +func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) { + u := user.GetUserByParams(c) + if c.Written() { + return + } + user.CreateUserPublicKey(c, form, u.ID) +} diff --git a/routes/api/v1/api.go b/routes/api/v1/api.go new file mode 100644 index 00000000..510c54cf --- /dev/null +++ b/routes/api/v1/api.go @@ -0,0 +1,356 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package v1 + +import ( + "strings" + + "github.com/go-macaron/binding" + "gopkg.in/macaron.v1" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/routes/api/v1/admin" + "github.com/gogits/gogs/routes/api/v1/misc" + "github.com/gogits/gogs/routes/api/v1/org" + "github.com/gogits/gogs/routes/api/v1/repo" + "github.com/gogits/gogs/routes/api/v1/user" +) + +func repoAssignment() macaron.Handler { + return func(c *context.APIContext) { + userName := c.Params(":username") + repoName := c.Params(":reponame") + + var ( + owner *models.User + err error + ) + + // Check if the user is the same as the repository owner. + if c.IsLogged && c.User.LowerName == strings.ToLower(userName) { + owner = c.User + } else { + owner, err = models.GetUserByName(userName) + if err != nil { + if errors.IsUserNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + } + c.Repo.Owner = owner + + // Get repository. + repo, err := models.GetRepositoryByName(owner.ID, repoName) + if err != nil { + if errors.IsRepoNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetRepositoryByName", err) + } + return + } else if err = repo.GetOwner(); err != nil { + c.Error(500, "GetOwner", err) + return + } + + if c.IsLogged && c.User.IsAdmin { + c.Repo.AccessMode = models.ACCESS_MODE_OWNER + } else { + mode, err := models.AccessLevel(c.User.ID, repo) + if err != nil { + c.Error(500, "AccessLevel", err) + return + } + c.Repo.AccessMode = mode + } + + if !c.Repo.HasAccess() { + c.Status(404) + return + } + + c.Repo.Repository = repo + } +} + +// Contexter middleware already checks token for user sign in process. +func reqToken() macaron.Handler { + return func(c *context.Context) { + if !c.IsLogged { + c.Error(401) + return + } + } +} + +func reqBasicAuth() macaron.Handler { + return func(c *context.Context) { + if !c.IsBasicAuth { + c.Error(401) + return + } + } +} + +func reqAdmin() macaron.Handler { + return func(c *context.Context) { + if !c.IsLogged || !c.User.IsAdmin { + c.Error(403) + return + } + } +} + +func reqRepoWriter() macaron.Handler { + return func(c *context.Context) { + if !c.Repo.IsWriter() { + c.Error(403) + return + } + } +} + +func orgAssignment(args ...bool) macaron.Handler { + var ( + assignOrg bool + assignTeam bool + ) + if len(args) > 0 { + assignOrg = args[0] + } + if len(args) > 1 { + assignTeam = args[1] + } + return func(c *context.APIContext) { + c.Org = new(context.APIOrganization) + + var err error + if assignOrg { + c.Org.Organization, err = models.GetUserByName(c.Params(":orgname")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + } + + if assignTeam { + c.Org.Team, err = models.GetTeamByID(c.ParamsInt64(":teamid")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetTeamById", err) + } + return + } + } + } +} + +func mustEnableIssues(c *context.APIContext) { + if !c.Repo.Repository.EnableIssues || c.Repo.Repository.EnableExternalTracker { + c.Status(404) + return + } +} + +// RegisterRoutes registers all v1 APIs routes to web application. +// FIXME: custom form error response +func RegisterRoutes(m *macaron.Macaron) { + bind := binding.Bind + + m.Group("/v1", func() { + // Handle preflight OPTIONS request + m.Options("/*", func() {}) + + // Miscellaneous + m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) + m.Post("/markdown/raw", misc.MarkdownRaw) + + // Users + m.Group("/users", func() { + m.Get("/search", user.Search) + + m.Group("/:username", func() { + m.Get("", user.GetInfo) + + m.Group("/tokens", func() { + m.Combo("").Get(user.ListAccessTokens). + Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) + }, reqBasicAuth()) + }) + }) + + m.Group("/users", func() { + m.Group("/:username", func() { + m.Get("/keys", user.ListPublicKeys) + + m.Get("/followers", user.ListFollowers) + m.Group("/following", func() { + m.Get("", user.ListFollowing) + m.Get("/:target", user.CheckFollowing) + }) + }) + }, reqToken()) + + m.Group("/user", func() { + m.Get("", user.GetAuthenticatedUser) + m.Combo("/emails").Get(user.ListEmails). + Post(bind(api.CreateEmailOption{}), user.AddEmail). + Delete(bind(api.CreateEmailOption{}), user.DeleteEmail) + + m.Get("/followers", user.ListMyFollowers) + m.Group("/following", func() { + m.Get("", user.ListMyFollowing) + m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow) + }) + + m.Group("/keys", func() { + m.Combo("").Get(user.ListMyPublicKeys). + Post(bind(api.CreateKeyOption{}), user.CreatePublicKey) + m.Combo("/:id").Get(user.GetPublicKey). + Delete(user.DeletePublicKey) + }) + + m.Combo("/issues").Get(repo.ListUserIssues) + }, reqToken()) + + // Repositories + m.Get("/users/:username/repos", reqToken(), repo.ListUserRepositories) + m.Get("/orgs/:org/repos", reqToken(), repo.ListOrgRepositories) + m.Combo("/user/repos", reqToken()).Get(repo.ListMyRepos). + Post(bind(api.CreateRepoOption{}), repo.Create) + m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo) + + m.Group("/repos", func() { + m.Get("/search", repo.Search) + }) + + m.Group("/repos", func() { + m.Post("/migrate", bind(form.MigrateRepo{}), repo.Migrate) + m.Combo("/:username/:reponame", repoAssignment()).Get(repo.Get). + Delete(repo.Delete) + + m.Group("/:username/:reponame", func() { + m.Group("/hooks", func() { + m.Combo("").Get(repo.ListHooks). + Post(bind(api.CreateHookOption{}), repo.CreateHook) + m.Combo("/:id").Patch(bind(api.EditHookOption{}), repo.EditHook). + Delete(repo.DeleteHook) + }) + m.Group("/collaborators", func() { + m.Get("", repo.ListCollaborators) + m.Combo("/:collaborator").Get(repo.IsCollaborator).Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator). + Delete(repo.DeleteCollaborator) + }) + m.Get("/raw/*", context.RepoRef(), repo.GetRawFile) + m.Get("/archive/*", repo.GetArchive) + m.Get("/forks", repo.ListForks) + m.Group("/branches", func() { + m.Get("", repo.ListBranches) + m.Get("/*", repo.GetBranch) + }) + m.Group("/keys", func() { + m.Combo("").Get(repo.ListDeployKeys). + Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) + m.Combo("/:id").Get(repo.GetDeployKey). + Delete(repo.DeleteDeploykey) + }) + m.Group("/issues", func() { + m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue) + m.Group("/comments", func() { + m.Get("", repo.ListRepoIssueComments) + m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment) + }) + m.Group("/:index", func() { + m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue) + + m.Group("/comments", func() { + m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment) + m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment). + Delete(repo.DeleteIssueComment) + }) + + m.Group("/labels", func() { + m.Combo("").Get(repo.ListIssueLabels). + Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels). + Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels). + Delete(repo.ClearIssueLabels) + m.Delete("/:id", repo.DeleteIssueLabel) + }) + + }) + }, mustEnableIssues) + m.Group("/labels", func() { + m.Combo("").Get(repo.ListLabels). + Post(bind(api.CreateLabelOption{}), repo.CreateLabel) + m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel). + Delete(repo.DeleteLabel) + }) + m.Group("/milestones", func() { + m.Combo("").Get(repo.ListMilestones). + Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) + m.Combo("/:id").Get(repo.GetMilestone). + Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone). + Delete(reqRepoWriter(), repo.DeleteMilestone) + }) + m.Post("/mirror-sync", repo.MirrorSync) + m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) + }, repoAssignment()) + }, reqToken()) + + m.Get("/issues", reqToken(), repo.ListUserIssues) + + // Organizations + m.Get("/user/orgs", reqToken(), org.ListMyOrgs) + m.Get("/users/:username/orgs", org.ListUserOrgs) + m.Group("/orgs/:orgname", func() { + m.Combo("").Get(org.Get).Patch(bind(api.EditOrgOption{}), org.Edit) + m.Combo("/teams").Get(org.ListTeams) + }, orgAssignment(true)) + + m.Any("/*", func(c *context.Context) { + c.Error(404) + }) + + m.Group("/admin", func() { + m.Group("/users", func() { + m.Post("", bind(api.CreateUserOption{}), admin.CreateUser) + + m.Group("/:username", func() { + m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser). + Delete(admin.DeleteUser) + m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey) + m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) + m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) + }) + }) + + m.Group("/orgs/:orgname", func() { + m.Group("/teams", func() { + m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin.CreateTeam) + }) + }) + m.Group("/teams", func() { + m.Group("/:teamid", func() { + m.Combo("/members/:username").Put(admin.AddTeamMember).Delete(admin.RemoveTeamMember) + m.Combo("/repos/:reponame").Put(admin.AddTeamRepository).Delete(admin.RemoveTeamRepository) + }, orgAssignment(false, true)) + }) + }, reqAdmin()) + }, context.APIContexter()) +} diff --git a/routes/api/v1/convert/convert.go b/routes/api/v1/convert/convert.go new file mode 100644 index 00000000..fcadb51f --- /dev/null +++ b/routes/api/v1/convert/convert.go @@ -0,0 +1,127 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "fmt" + + "github.com/Unknwon/com" + + "github.com/gogits/git-module" + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" +) + +func ToEmail(email *models.EmailAddress) *api.Email { + return &api.Email{ + Email: email.Email, + Verified: email.IsActivated, + Primary: email.IsPrimary, + } +} + +func ToBranch(b *models.Branch, c *git.Commit) *api.Branch { + return &api.Branch{ + Name: b.Name, + Commit: ToCommit(c), + } +} + +func ToCommit(c *git.Commit) *api.PayloadCommit { + authorUsername := "" + author, err := models.GetUserByEmail(c.Author.Email) + if err == nil { + authorUsername = author.Name + } + committerUsername := "" + committer, err := models.GetUserByEmail(c.Committer.Email) + if err == nil { + committerUsername = committer.Name + } + return &api.PayloadCommit{ + ID: c.ID.String(), + Message: c.Message(), + URL: "Not implemented", + Author: &api.PayloadUser{ + Name: c.Author.Name, + Email: c.Author.Email, + UserName: authorUsername, + }, + Committer: &api.PayloadUser{ + Name: c.Committer.Name, + Email: c.Committer.Email, + UserName: committerUsername, + }, + Timestamp: c.Author.When, + } +} + +func ToPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey { + return &api.PublicKey{ + ID: key.ID, + Key: key.Content, + URL: apiLink + com.ToStr(key.ID), + Title: key.Name, + Created: key.Created, + } +} + +func ToHook(repoLink string, w *models.Webhook) *api.Hook { + config := map[string]string{ + "url": w.URL, + "content_type": w.ContentType.Name(), + } + if w.HookTaskType == models.SLACK { + s := w.GetSlackHook() + config["channel"] = s.Channel + config["username"] = s.Username + config["icon_url"] = s.IconURL + config["color"] = s.Color + } + + return &api.Hook{ + ID: w.ID, + Type: w.HookTaskType.Name(), + URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), + Active: w.IsActive, + Config: config, + Events: w.EventsArray(), + Updated: w.Updated, + Created: w.Created, + } +} + +func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey { + return &api.DeployKey{ + ID: key.ID, + Key: key.Content, + URL: apiLink + com.ToStr(key.ID), + Title: key.Name, + Created: key.Created, + ReadOnly: true, // All deploy keys are read-only. + } +} + +func ToOrganization(org *models.User) *api.Organization { + return &api.Organization{ + ID: org.ID, + AvatarUrl: org.AvatarLink(), + UserName: org.Name, + FullName: org.FullName, + Description: org.Description, + Website: org.Website, + Location: org.Location, + } +} + +func ToTeam(team *models.Team) *api.Team { + return &api.Team{ + ID: team.ID, + Name: team.Name, + Description: team.Description, + Permission: team.Authorize.String(), + } +} diff --git a/routes/api/v1/convert/utils.go b/routes/api/v1/convert/utils.go new file mode 100644 index 00000000..d0beab3d --- /dev/null +++ b/routes/api/v1/convert/utils.go @@ -0,0 +1,19 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "github.com/gogits/gogs/pkg/setting" +) + +// ToCorrectPageSize makes sure page size is in allowed range. +func ToCorrectPageSize(size int) int { + if size <= 0 { + size = 10 + } else if size > setting.API.MaxResponseItems { + size = setting.API.MaxResponseItems + } + return size +} diff --git a/routes/api/v1/misc/markdown.go b/routes/api/v1/misc/markdown.go new file mode 100644 index 00000000..98bfd7d0 --- /dev/null +++ b/routes/api/v1/misc/markdown.go @@ -0,0 +1,42 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package misc + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/markup" +) + +// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-an-arbitrary-markdown-document +func Markdown(c *context.APIContext, form api.MarkdownOption) { + if c.HasApiError() { + c.Error(422, "", c.GetErrMsg()) + return + } + + if len(form.Text) == 0 { + c.Write([]byte("")) + return + } + + switch form.Mode { + case "gfm": + c.Write(markup.Markdown([]byte(form.Text), form.Context, nil)) + default: + c.Write(markup.RawMarkdown([]byte(form.Text), "")) + } +} + +// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-a-markdown-document-in-raw-mode +func MarkdownRaw(c *context.APIContext) { + body, err := c.Req.Body().Bytes() + if err != nil { + c.Error(422, "", err) + return + } + c.Write(markup.RawMarkdown(body, "")) +} diff --git a/routes/api/v1/org/org.go b/routes/api/v1/org/org.go new file mode 100644 index 00000000..2f8832ca --- /dev/null +++ b/routes/api/v1/org/org.go @@ -0,0 +1,66 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package org + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/api/v1/convert" + "github.com/gogits/gogs/routes/api/v1/user" +) + +func listUserOrgs(c *context.APIContext, u *models.User, all bool) { + if err := u.GetOrganizations(all); err != nil { + c.Error(500, "GetOrganizations", err) + return + } + + apiOrgs := make([]*api.Organization, len(u.Orgs)) + for i := range u.Orgs { + apiOrgs[i] = convert.ToOrganization(u.Orgs[i]) + } + c.JSON(200, &apiOrgs) +} + +// https://github.com/gogits/go-gogs-client/wiki/Organizations#list-your-organizations +func ListMyOrgs(c *context.APIContext) { + listUserOrgs(c, c.User, true) +} + +// https://github.com/gogits/go-gogs-client/wiki/Organizations#list-user-organizations +func ListUserOrgs(c *context.APIContext) { + u := user.GetUserByParams(c) + if c.Written() { + return + } + listUserOrgs(c, u, false) +} + +// https://github.com/gogits/go-gogs-client/wiki/Organizations#get-an-organization +func Get(c *context.APIContext) { + c.JSON(200, convert.ToOrganization(c.Org.Organization)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Organizations#edit-an-organization +func Edit(c *context.APIContext, form api.EditOrgOption) { + org := c.Org.Organization + if !org.IsOwnedBy(c.User.ID) { + c.Status(403) + return + } + + org.FullName = form.FullName + org.Description = form.Description + org.Website = form.Website + org.Location = form.Location + if err := models.UpdateUser(org); err != nil { + c.Error(500, "UpdateUser", err) + return + } + + c.JSON(200, convert.ToOrganization(org)) +} diff --git a/routes/api/v1/org/team.go b/routes/api/v1/org/team.go new file mode 100644 index 00000000..fd92e728 --- /dev/null +++ b/routes/api/v1/org/team.go @@ -0,0 +1,26 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package org + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/api/v1/convert" +) + +func ListTeams(c *context.APIContext) { + org := c.Org.Organization + if err := org.GetTeams(); err != nil { + c.Error(500, "GetTeams", err) + return + } + + apiTeams := make([]*api.Team, len(org.Teams)) + for i := range org.Teams { + apiTeams[i] = convert.ToTeam(org.Teams[i]) + } + c.JSON(200, apiTeams) +} diff --git a/routes/api/v1/repo/branch.go b/routes/api/v1/repo/branch.go new file mode 100644 index 00000000..d8c2697b --- /dev/null +++ b/routes/api/v1/repo/branch.go @@ -0,0 +1,55 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/api/v1/convert" +) + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#get-branch +func GetBranch(c *context.APIContext) { + branch, err := c.Repo.Repository.GetBranch(c.Params("*")) + if err != nil { + if models.IsErrBranchNotExist(err) { + c.Error(404, "GetBranch", err) + } else { + c.Error(500, "GetBranch", err) + } + return + } + + commit, err := branch.GetCommit() + if err != nil { + c.Error(500, "GetCommit", err) + return + } + + c.JSON(200, convert.ToBranch(branch, commit)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-branches +func ListBranches(c *context.APIContext) { + branches, err := c.Repo.Repository.GetBranches() + if err != nil { + c.Error(500, "GetBranches", err) + return + } + + apiBranches := make([]*api.Branch, len(branches)) + for i := range branches { + commit, err := branches[i].GetCommit() + if err != nil { + c.Error(500, "GetCommit", err) + return + } + apiBranches[i] = convert.ToBranch(branches[i], commit) + } + + c.JSON(200, &apiBranches) +} diff --git a/routes/api/v1/repo/collaborators.go b/routes/api/v1/repo/collaborators.go new file mode 100644 index 00000000..d295ac0f --- /dev/null +++ b/routes/api/v1/repo/collaborators.go @@ -0,0 +1,94 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" +) + +func ListCollaborators(c *context.APIContext) { + collaborators, err := c.Repo.Repository.GetCollaborators() + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetCollaborators", err) + } + return + } + + apiCollaborators := make([]*api.Collaborator, len(collaborators)) + for i := range collaborators { + apiCollaborators[i] = collaborators[i].APIFormat() + } + c.JSON(200, &apiCollaborators) +} + +func AddCollaborator(c *context.APIContext, form api.AddCollaboratorOption) { + collaborator, err := models.GetUserByName(c.Params(":collaborator")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + + if err := c.Repo.Repository.AddCollaborator(collaborator); err != nil { + c.Error(500, "AddCollaborator", err) + return + } + + if form.Permission != nil { + if err := c.Repo.Repository.ChangeCollaborationAccessMode(collaborator.ID, models.ParseAccessMode(*form.Permission)); err != nil { + c.Error(500, "ChangeCollaborationAccessMode", err) + return + } + } + + c.Status(204) +} + +func IsCollaborator(c *context.APIContext) { + collaborator, err := models.GetUserByName(c.Params(":collaborator")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + + if !c.Repo.Repository.IsCollaborator(collaborator.ID) { + c.Status(404) + } else { + c.Status(204) + } +} + +func DeleteCollaborator(c *context.APIContext) { + collaborator, err := models.GetUserByName(c.Params(":collaborator")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + + if err := c.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil { + c.Error(500, "DeleteCollaboration", err) + return + } + + c.Status(204) +} diff --git a/routes/api/v1/repo/file.go b/routes/api/v1/repo/file.go new file mode 100644 index 00000000..c783e81f --- /dev/null +++ b/routes/api/v1/repo/file.go @@ -0,0 +1,72 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/repo" +) + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-raw-content +func GetRawFile(c *context.APIContext) { + if !c.Repo.HasAccess() { + c.Status(404) + return + } + + if c.Repo.Repository.IsBare { + c.Status(404) + return + } + + blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath) + if err != nil { + if git.IsErrNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetBlobByPath", err) + } + return + } + if err = repo.ServeBlob(c.Context, blob); err != nil { + c.Error(500, "ServeBlob", err) + } +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-archive +func GetArchive(c *context.APIContext) { + repoPath := models.RepoPath(c.Params(":username"), c.Params(":reponame")) + gitRepo, err := git.OpenRepository(repoPath) + if err != nil { + c.Error(500, "OpenRepository", err) + return + } + c.Repo.GitRepo = gitRepo + + repo.Download(c.Context) +} + +func GetEditorconfig(c *context.APIContext) { + ec, err := c.Repo.GetEditorconfig() + if err != nil { + if git.IsErrNotExist(err) { + c.Error(404, "GetEditorconfig", err) + } else { + c.Error(500, "GetEditorconfig", err) + } + return + } + + fileName := c.Params("filename") + def := ec.GetDefinitionForFilename(fileName) + if def == nil { + c.Error(404, "GetDefinitionForFilename", err) + return + } + c.JSON(200, def) +} diff --git a/routes/api/v1/repo/hook.go b/routes/api/v1/repo/hook.go new file mode 100644 index 00000000..66125c50 --- /dev/null +++ b/routes/api/v1/repo/hook.go @@ -0,0 +1,186 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "encoding/json" + + "github.com/Unknwon/com" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/routes/api/v1/convert" +) + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-hooks +func ListHooks(c *context.APIContext) { + hooks, err := models.GetWebhooksByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Error(500, "GetWebhooksByRepoID", err) + return + } + + apiHooks := make([]*api.Hook, len(hooks)) + for i := range hooks { + apiHooks[i] = convert.ToHook(c.Repo.RepoLink, hooks[i]) + } + c.JSON(200, &apiHooks) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#create-a-hook +func CreateHook(c *context.APIContext, form api.CreateHookOption) { + if !models.IsValidHookTaskType(form.Type) { + c.Error(422, "", "Invalid hook type") + return + } + for _, name := range []string{"url", "content_type"} { + if _, ok := form.Config[name]; !ok { + c.Error(422, "", "Missing config option: "+name) + return + } + } + if !models.IsValidHookContentType(form.Config["content_type"]) { + c.Error(422, "", "Invalid content type") + return + } + + if len(form.Events) == 0 { + form.Events = []string{"push"} + } + w := &models.Webhook{ + RepoID: c.Repo.Repository.ID, + URL: form.Config["url"], + ContentType: models.ToHookContentType(form.Config["content_type"]), + Secret: form.Config["secret"], + HookEvent: &models.HookEvent{ + ChooseEvents: true, + HookEvents: models.HookEvents{ + Create: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)), + Delete: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_DELETE)), + Fork: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_FORK)), + Push: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)), + Issues: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUES)), + IssueComment: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUE_COMMENT)), + PullRequest: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PULL_REQUEST)), + Release: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_RELEASE)), + }, + }, + IsActive: form.Active, + HookTaskType: models.ToHookTaskType(form.Type), + } + if w.HookTaskType == models.SLACK { + channel, ok := form.Config["channel"] + if !ok { + c.Error(422, "", "Missing config option: channel") + return + } + meta, err := json.Marshal(&models.SlackMeta{ + Channel: channel, + Username: form.Config["username"], + IconURL: form.Config["icon_url"], + Color: form.Config["color"], + }) + if err != nil { + c.Error(500, "slack: JSON marshal failed", err) + return + } + w.Meta = string(meta) + } + + if err := w.UpdateEvent(); err != nil { + c.Error(500, "UpdateEvent", err) + return + } else if err := models.CreateWebhook(w); err != nil { + c.Error(500, "CreateWebhook", err) + return + } + + c.JSON(201, convert.ToHook(c.Repo.RepoLink, w)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook +func EditHook(c *context.APIContext, form api.EditHookOption) { + w, err := models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if errors.IsWebhookNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetWebhookOfRepoByID", err) + } + return + } + + if form.Config != nil { + if url, ok := form.Config["url"]; ok { + w.URL = url + } + if ct, ok := form.Config["content_type"]; ok { + if !models.IsValidHookContentType(ct) { + c.Error(422, "", "Invalid content type") + return + } + w.ContentType = models.ToHookContentType(ct) + } + + if w.HookTaskType == models.SLACK { + if channel, ok := form.Config["channel"]; ok { + meta, err := json.Marshal(&models.SlackMeta{ + Channel: channel, + Username: form.Config["username"], + IconURL: form.Config["icon_url"], + Color: form.Config["color"], + }) + if err != nil { + c.Error(500, "slack: JSON marshal failed", err) + return + } + w.Meta = string(meta) + } + } + } + + // Update events + if len(form.Events) == 0 { + form.Events = []string{"push"} + } + w.PushOnly = false + w.SendEverything = false + w.ChooseEvents = true + w.Create = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)) + w.Delete = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_DELETE)) + w.Fork = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_FORK)) + w.Push = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)) + w.Issues = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUES)) + w.IssueComment = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUE_COMMENT)) + w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PULL_REQUEST)) + w.Release = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_RELEASE)) + if err = w.UpdateEvent(); err != nil { + c.Error(500, "UpdateEvent", err) + return + } + + if form.Active != nil { + w.IsActive = *form.Active + } + + if err := models.UpdateWebhook(w); err != nil { + c.Error(500, "UpdateWebhook", err) + return + } + + c.JSON(200, convert.ToHook(c.Repo.RepoLink, w)) +} + +func DeleteHook(c *context.APIContext) { + if err := models.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil { + c.Error(500, "DeleteWebhookByRepoID", err) + return + } + + c.Status(204) +} diff --git a/routes/api/v1/repo/issue.go b/routes/api/v1/repo/issue.go new file mode 100644 index 00000000..d6ae7b4d --- /dev/null +++ b/routes/api/v1/repo/issue.go @@ -0,0 +1,201 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "fmt" + "strings" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" +) + +func listIssues(c *context.APIContext, opts *models.IssuesOptions) { + issues, err := models.Issues(opts) + if err != nil { + c.Error(500, "Issues", err) + return + } + + count, err := models.IssuesCount(opts) + if err != nil { + c.Error(500, "IssuesCount", err) + return + } + + // FIXME: use IssueList to improve performance. + apiIssues := make([]*api.Issue, len(issues)) + for i := range issues { + if err = issues[i].LoadAttributes(); err != nil { + c.Error(500, "LoadAttributes", err) + return + } + apiIssues[i] = issues[i].APIFormat() + } + + c.SetLinkHeader(int(count), setting.UI.IssuePagingNum) + c.JSON(200, &apiIssues) +} + +func ListUserIssues(c *context.APIContext) { + opts := models.IssuesOptions{ + AssigneeID: c.User.ID, + Page: c.QueryInt("page"), + IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED, + } + + listIssues(c, &opts) +} + +func ListIssues(c *context.APIContext) { + opts := models.IssuesOptions{ + RepoID: c.Repo.Repository.ID, + Page: c.QueryInt("page"), + IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED, + } + + listIssues(c, &opts) +} + +func GetIssue(c *context.APIContext) { + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + if errors.IsIssueNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetIssueByIndex", err) + } + return + } + c.JSON(200, issue.APIFormat()) +} + +func CreateIssue(c *context.APIContext, form api.CreateIssueOption) { + issue := &models.Issue{ + RepoID: c.Repo.Repository.ID, + Title: form.Title, + PosterID: c.User.ID, + Poster: c.User, + Content: form.Body, + } + + if c.Repo.IsWriter() { + if len(form.Assignee) > 0 { + assignee, err := models.GetUserByName(form.Assignee) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", form.Assignee)) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + issue.AssigneeID = assignee.ID + } + issue.MilestoneID = form.Milestone + } else { + form.Labels = nil + } + + if err := models.NewIssue(c.Repo.Repository, issue, form.Labels, nil); err != nil { + c.Error(500, "NewIssue", err) + return + } + + if form.Closed { + if err := issue.ChangeStatus(c.User, c.Repo.Repository, true); err != nil { + c.Error(500, "ChangeStatus", err) + return + } + } + + // Refetch from database to assign some automatic values + var err error + issue, err = models.GetIssueByID(issue.ID) + if err != nil { + c.Error(500, "GetIssueByID", err) + return + } + c.JSON(201, issue.APIFormat()) +} + +func EditIssue(c *context.APIContext, form api.EditIssueOption) { + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + if errors.IsIssueNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetIssueByIndex", err) + } + return + } + + if !issue.IsPoster(c.User.ID) && !c.Repo.IsWriter() { + c.Status(403) + return + } + + if len(form.Title) > 0 { + issue.Title = form.Title + } + if form.Body != nil { + issue.Content = *form.Body + } + + if c.Repo.IsWriter() && form.Assignee != nil && + (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) { + if len(*form.Assignee) == 0 { + issue.AssigneeID = 0 + } else { + assignee, err := models.GetUserByName(*form.Assignee) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee)) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + issue.AssigneeID = assignee.ID + } + + if err = models.UpdateIssueUserByAssignee(issue); err != nil { + c.Error(500, "UpdateIssueUserByAssignee", err) + return + } + } + if c.Repo.IsWriter() && form.Milestone != nil && + issue.MilestoneID != *form.Milestone { + oldMilestoneID := issue.MilestoneID + issue.MilestoneID = *form.Milestone + if err = models.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil { + c.Error(500, "ChangeMilestoneAssign", err) + return + } + } + + if err = models.UpdateIssue(issue); err != nil { + c.Error(500, "UpdateIssue", err) + return + } + if form.State != nil { + if err = issue.ChangeStatus(c.User, c.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil { + c.Error(500, "ChangeStatus", err) + return + } + } + + // Refetch from database to assign some automatic values + issue, err = models.GetIssueByID(issue.ID) + if err != nil { + c.Error(500, "GetIssueByID", err) + return + } + c.JSON(201, issue.APIFormat()) +} diff --git a/routes/api/v1/repo/issue_comment.go b/routes/api/v1/repo/issue_comment.go new file mode 100644 index 00000000..4a057d76 --- /dev/null +++ b/routes/api/v1/repo/issue_comment.go @@ -0,0 +1,128 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +package repo + +import ( + "time" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" +) + +func ListIssueComments(c *context.APIContext) { + var since time.Time + if len(c.Query("since")) > 0 { + since, _ = time.Parse(time.RFC3339, c.Query("since")) + } + + // comments,err:=models.GetCommentsByIssueIDSince(, since) + issue, err := models.GetRawIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + c.Error(500, "GetRawIssueByIndex", err) + return + } + + comments, err := models.GetCommentsByIssueIDSince(issue.ID, since.Unix()) + if err != nil { + c.Error(500, "GetCommentsByIssueIDSince", err) + return + } + + apiComments := make([]*api.Comment, len(comments)) + for i := range comments { + apiComments[i] = comments[i].APIFormat() + } + c.JSON(200, &apiComments) +} + +func ListRepoIssueComments(c *context.APIContext) { + var since time.Time + if len(c.Query("since")) > 0 { + since, _ = time.Parse(time.RFC3339, c.Query("since")) + } + + comments, err := models.GetCommentsByRepoIDSince(c.Repo.Repository.ID, since.Unix()) + if err != nil { + c.Error(500, "GetCommentsByRepoIDSince", err) + return + } + + apiComments := make([]*api.Comment, len(comments)) + for i := range comments { + apiComments[i] = comments[i].APIFormat() + } + c.JSON(200, &apiComments) +} + +func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption) { + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + c.Error(500, "GetIssueByIndex", err) + return + } + + comment, err := models.CreateIssueComment(c.User, c.Repo.Repository, issue, form.Body, nil) + if err != nil { + c.Error(500, "CreateIssueComment", err) + return + } + + c.JSON(201, comment.APIFormat()) +} + +func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) { + comment, err := models.GetCommentByID(c.ParamsInt64(":id")) + if err != nil { + if models.IsErrCommentNotExist(err) { + c.Error(404, "GetCommentByID", err) + } else { + c.Error(500, "GetCommentByID", err) + } + return + } + + if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() { + c.Status(403) + return + } else if comment.Type != models.COMMENT_TYPE_COMMENT { + c.Status(204) + return + } + + oldContent := comment.Content + comment.Content = form.Body + if err := models.UpdateComment(c.User, comment, oldContent); err != nil { + c.Error(500, "UpdateComment", err) + return + } + c.JSON(200, comment.APIFormat()) +} + +func DeleteIssueComment(c *context.APIContext) { + comment, err := models.GetCommentByID(c.ParamsInt64(":id")) + if err != nil { + if models.IsErrCommentNotExist(err) { + c.Error(404, "GetCommentByID", err) + } else { + c.Error(500, "GetCommentByID", err) + } + return + } + + if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() { + c.Status(403) + return + } else if comment.Type != models.COMMENT_TYPE_COMMENT { + c.Status(204) + return + } + + if err = models.DeleteCommentByID(c.User, comment.ID); err != nil { + c.Error(500, "DeleteCommentByID", err) + return + } + c.Status(204) +} diff --git a/routes/api/v1/repo/issue_label.go b/routes/api/v1/repo/issue_label.go new file mode 100644 index 00000000..f3f2d730 --- /dev/null +++ b/routes/api/v1/repo/issue_label.go @@ -0,0 +1,169 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" +) + +func ListIssueLabels(c *context.APIContext) { + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + if errors.IsIssueNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetIssueByIndex", err) + } + return + } + + apiLabels := make([]*api.Label, len(issue.Labels)) + for i := range issue.Labels { + apiLabels[i] = issue.Labels[i].APIFormat() + } + c.JSON(200, &apiLabels) +} + +func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) { + if !c.Repo.IsWriter() { + c.Status(403) + return + } + + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + if errors.IsIssueNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetIssueByIndex", err) + } + return + } + + labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels) + if err != nil { + c.Error(500, "GetLabelsInRepoByIDs", err) + return + } + + if err = issue.AddLabels(c.User, labels); err != nil { + c.Error(500, "AddLabels", err) + return + } + + labels, err = models.GetLabelsByIssueID(issue.ID) + if err != nil { + c.Error(500, "GetLabelsByIssueID", err) + return + } + + apiLabels := make([]*api.Label, len(labels)) + for i := range labels { + apiLabels[i] = issue.Labels[i].APIFormat() + } + c.JSON(200, &apiLabels) +} + +func DeleteIssueLabel(c *context.APIContext) { + if !c.Repo.IsWriter() { + c.Status(403) + return + } + + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + if errors.IsIssueNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetIssueByIndex", err) + } + return + } + + label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if models.IsErrLabelNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetLabelInRepoByID", err) + } + return + } + + if err := models.DeleteIssueLabel(issue, label); err != nil { + c.Error(500, "DeleteIssueLabel", err) + return + } + + c.Status(204) +} + +func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) { + if !c.Repo.IsWriter() { + c.Status(403) + return + } + + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + if errors.IsIssueNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetIssueByIndex", err) + } + return + } + + labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels) + if err != nil { + c.Error(500, "GetLabelsInRepoByIDs", err) + return + } + + if err := issue.ReplaceLabels(labels); err != nil { + c.Error(500, "ReplaceLabels", err) + return + } + + labels, err = models.GetLabelsByIssueID(issue.ID) + if err != nil { + c.Error(500, "GetLabelsByIssueID", err) + return + } + + apiLabels := make([]*api.Label, len(labels)) + for i := range labels { + apiLabels[i] = issue.Labels[i].APIFormat() + } + c.JSON(200, &apiLabels) +} + +func ClearIssueLabels(c *context.APIContext) { + if !c.Repo.IsWriter() { + c.Status(403) + return + } + + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + if errors.IsIssueNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetIssueByIndex", err) + } + return + } + + if err := issue.ClearLabels(c.User); err != nil { + c.Error(500, "ClearLabels", err) + return + } + + c.Status(204) +} diff --git a/routes/api/v1/repo/key.go b/routes/api/v1/repo/key.go new file mode 100644 index 00000000..405440d2 --- /dev/null +++ b/routes/api/v1/repo/key.go @@ -0,0 +1,114 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "fmt" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/api/v1/convert" +) + +func composeDeployKeysAPILink(repoPath string) string { + return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/" +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys +func ListDeployKeys(c *context.APIContext) { + keys, err := models.ListDeployKeys(c.Repo.Repository.ID) + if err != nil { + c.Error(500, "ListDeployKeys", err) + return + } + + apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name) + apiKeys := make([]*api.DeployKey, len(keys)) + for i := range keys { + if err = keys[i].GetContent(); err != nil { + c.Error(500, "GetContent", err) + return + } + apiKeys[i] = convert.ToDeployKey(apiLink, keys[i]) + } + + c.JSON(200, &apiKeys) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key +func GetDeployKey(c *context.APIContext) { + key, err := models.GetDeployKeyByID(c.ParamsInt64(":id")) + if err != nil { + if models.IsErrDeployKeyNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetDeployKeyByID", err) + } + return + } + + if err = key.GetContent(); err != nil { + c.Error(500, "GetContent", err) + return + } + + apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name) + c.JSON(200, convert.ToDeployKey(apiLink, key)) +} + +func HandleCheckKeyStringError(c *context.APIContext, err error) { + if models.IsErrKeyUnableVerify(err) { + c.Error(422, "", "Unable to verify key content") + } else { + c.Error(422, "", fmt.Errorf("Invalid key content: %v", err)) + } +} + +func HandleAddKeyError(c *context.APIContext, err error) { + switch { + case models.IsErrKeyAlreadyExist(err): + c.Error(422, "", "Key content has been used as non-deploy key") + case models.IsErrKeyNameAlreadyUsed(err): + c.Error(422, "", "Key title has been used") + default: + c.Error(500, "AddKey", err) + } +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key +func CreateDeployKey(c *context.APIContext, form api.CreateKeyOption) { + content, err := models.CheckPublicKeyString(form.Key) + if err != nil { + HandleCheckKeyStringError(c, err) + return + } + + key, err := models.AddDeployKey(c.Repo.Repository.ID, form.Title, content) + if err != nil { + HandleAddKeyError(c, err) + return + } + + key.Content = content + apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name) + c.JSON(201, convert.ToDeployKey(apiLink, key)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key +func DeleteDeploykey(c *context.APIContext) { + if err := models.DeleteDeployKey(c.User, c.ParamsInt64(":id")); err != nil { + if models.IsErrKeyAccessDenied(err) { + c.Error(403, "", "You do not have access to this key") + } else { + c.Error(500, "DeleteDeployKey", err) + } + return + } + + c.Status(204) +} diff --git a/routes/api/v1/repo/label.go b/routes/api/v1/repo/label.go new file mode 100644 index 00000000..1161d633 --- /dev/null +++ b/routes/api/v1/repo/label.go @@ -0,0 +1,110 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "github.com/Unknwon/com" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" +) + +func ListLabels(c *context.APIContext) { + labels, err := models.GetLabelsByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Error(500, "GetLabelsByRepoID", err) + return + } + + apiLabels := make([]*api.Label, len(labels)) + for i := range labels { + apiLabels[i] = labels[i].APIFormat() + } + c.JSON(200, &apiLabels) +} + +func GetLabel(c *context.APIContext) { + var label *models.Label + var err error + idStr := c.Params(":id") + if id := com.StrTo(idStr).MustInt64(); id > 0 { + label, err = models.GetLabelOfRepoByID(c.Repo.Repository.ID, id) + } else { + label, err = models.GetLabelOfRepoByName(c.Repo.Repository.ID, idStr) + } + if err != nil { + if models.IsErrLabelNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetLabelByRepoID", err) + } + return + } + + c.JSON(200, label.APIFormat()) +} + +func CreateLabel(c *context.APIContext, form api.CreateLabelOption) { + if !c.Repo.IsWriter() { + c.Status(403) + return + } + + label := &models.Label{ + Name: form.Name, + Color: form.Color, + RepoID: c.Repo.Repository.ID, + } + if err := models.NewLabels(label); err != nil { + c.Error(500, "NewLabel", err) + return + } + c.JSON(201, label.APIFormat()) +} + +func EditLabel(c *context.APIContext, form api.EditLabelOption) { + if !c.Repo.IsWriter() { + c.Status(403) + return + } + + label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if models.IsErrLabelNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetLabelByRepoID", err) + } + return + } + + if form.Name != nil { + label.Name = *form.Name + } + if form.Color != nil { + label.Color = *form.Color + } + if err := models.UpdateLabel(label); err != nil { + c.Handle(500, "UpdateLabel", err) + return + } + c.JSON(200, label.APIFormat()) +} + +func DeleteLabel(c *context.APIContext) { + if !c.Repo.IsWriter() { + c.Status(403) + return + } + + if err := models.DeleteLabel(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil { + c.Error(500, "DeleteLabel", err) + return + } + + c.Status(204) +} diff --git a/routes/api/v1/repo/milestone.go b/routes/api/v1/repo/milestone.go new file mode 100644 index 00000000..baf8eb2f --- /dev/null +++ b/routes/api/v1/repo/milestone.go @@ -0,0 +1,103 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "time" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" +) + +func ListMilestones(c *context.APIContext) { + milestones, err := models.GetMilestonesByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Error(500, "GetMilestonesByRepoID", err) + return + } + + apiMilestones := make([]*api.Milestone, len(milestones)) + for i := range milestones { + apiMilestones[i] = milestones[i].APIFormat() + } + c.JSON(200, &apiMilestones) +} + +func GetMilestone(c *context.APIContext) { + milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if models.IsErrMilestoneNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetMilestoneByRepoID", err) + } + return + } + c.JSON(200, milestone.APIFormat()) +} + +func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) { + if form.Deadline == nil { + defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local) + form.Deadline = &defaultDeadline + } + + milestone := &models.Milestone{ + RepoID: c.Repo.Repository.ID, + Name: form.Title, + Content: form.Description, + Deadline: *form.Deadline, + } + + if err := models.NewMilestone(milestone); err != nil { + c.Error(500, "NewMilestone", err) + return + } + c.JSON(201, milestone.APIFormat()) +} + +func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) { + milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if models.IsErrMilestoneNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetMilestoneByRepoID", err) + } + return + } + + if len(form.Title) > 0 { + milestone.Name = form.Title + } + if form.Description != nil { + milestone.Content = *form.Description + } + if form.Deadline != nil && !form.Deadline.IsZero() { + milestone.Deadline = *form.Deadline + } + + if form.State != nil { + if err = milestone.ChangeStatus(api.STATE_CLOSED == api.StateType(*form.State)); err != nil { + c.Error(500, "ChangeStatus", err) + return + } + } else if err = models.UpdateMilestone(milestone); err != nil { + c.Handle(500, "UpdateMilestone", err) + return + } + + c.JSON(200, milestone.APIFormat()) +} + +func DeleteMilestone(c *context.APIContext) { + if err := models.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil { + c.Error(500, "DeleteMilestoneByRepoID", err) + return + } + c.Status(204) +} diff --git a/routes/api/v1/repo/repo.go b/routes/api/v1/repo/repo.go new file mode 100644 index 00000000..8410dcca --- /dev/null +++ b/routes/api/v1/repo/repo.go @@ -0,0 +1,380 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "path" + + log "gopkg.in/clog.v1" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/api/v1/convert" +) + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#search-repositories +func Search(c *context.APIContext) { + opts := &models.SearchRepoOptions{ + Keyword: path.Base(c.Query("q")), + OwnerID: c.QueryInt64("uid"), + PageSize: convert.ToCorrectPageSize(c.QueryInt("limit")), + } + + // Check visibility. + if c.IsLogged && opts.OwnerID > 0 { + if c.User.ID == opts.OwnerID { + opts.Private = true + } else { + u, err := models.GetUserByID(opts.OwnerID) + if err != nil { + c.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + if u.IsOrganization() && u.IsOwnedBy(c.User.ID) { + opts.Private = true + } + // FIXME: how about collaborators? + } + } + + repos, count, err := models.SearchRepositoryByName(opts) + if err != nil { + c.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + + if err = models.RepositoryList(repos).LoadAttributes(); err != nil { + c.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + + results := make([]*api.Repository, len(repos)) + for i := range repos { + results[i] = repos[i].APIFormat(nil) + } + + c.SetLinkHeader(int(count), setting.API.MaxResponseItems) + c.JSON(200, map[string]interface{}{ + "ok": true, + "data": results, + }) +} + +func listUserRepositories(c *context.APIContext, username string) { + user, err := models.GetUserByName(username) + if err != nil { + c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) + return + } + + // Only list public repositories if user requests someone else's repository list, + // or an organization isn't a member of. + var ownRepos []*models.Repository + if user.IsOrganization() { + ownRepos, _, err = user.GetUserRepositories(c.User.ID, 1, user.NumRepos) + } else { + ownRepos, err = models.GetUserRepositories(&models.UserRepoOptions{ + UserID: user.ID, + Private: c.User.ID == user.ID, + Page: 1, + PageSize: user.NumRepos, + }) + } + if err != nil { + c.Error(500, "GetUserRepositories", err) + return + } + + if c.User.ID != user.ID { + repos := make([]*api.Repository, len(ownRepos)) + for i := range ownRepos { + repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true}) + } + c.JSON(200, &repos) + return + } + + accessibleRepos, err := user.GetRepositoryAccesses() + if err != nil { + c.Error(500, "GetRepositoryAccesses", err) + return + } + + numOwnRepos := len(ownRepos) + repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos)) + for i := range ownRepos { + repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true}) + } + + i := numOwnRepos + for repo, access := range accessibleRepos { + repos[i] = repo.APIFormat(&api.Permission{ + Admin: access >= models.ACCESS_MODE_ADMIN, + Push: access >= models.ACCESS_MODE_WRITE, + Pull: true, + }) + i++ + } + + c.JSON(200, &repos) +} + +func ListMyRepos(c *context.APIContext) { + listUserRepositories(c, c.User.Name) +} + +func ListUserRepositories(c *context.APIContext) { + listUserRepositories(c, c.Params(":username")) +} + +func ListOrgRepositories(c *context.APIContext) { + listUserRepositories(c, c.Params(":org")) +} + +func CreateUserRepo(c *context.APIContext, owner *models.User, opt api.CreateRepoOption) { + repo, err := models.CreateRepository(c.User, owner, models.CreateRepoOptions{ + Name: opt.Name, + Description: opt.Description, + Gitignores: opt.Gitignores, + License: opt.License, + Readme: opt.Readme, + IsPrivate: opt.Private, + AutoInit: opt.AutoInit, + }) + if err != nil { + if models.IsErrRepoAlreadyExist(err) || + models.IsErrNameReserved(err) || + models.IsErrNamePatternNotAllowed(err) { + c.Error(422, "", err) + } else { + if repo != nil { + if err = models.DeleteRepository(c.User.ID, repo.ID); err != nil { + log.Error(2, "DeleteRepository: %v", err) + } + } + c.Error(500, "CreateRepository", err) + } + return + } + + c.JSON(201, repo.APIFormat(&api.Permission{true, true, true})) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#create +func Create(c *context.APIContext, opt api.CreateRepoOption) { + // Shouldn't reach this condition, but just in case. + if c.User.IsOrganization() { + c.Error(422, "", "not allowed creating repository for organization") + return + } + CreateUserRepo(c, c.User, opt) +} + +func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) { + org, err := models.GetOrgByName(c.Params(":org")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetOrgByName", err) + } + return + } + + if !org.IsOwnedBy(c.User.ID) { + c.Error(403, "", "Given user is not owner of organization.") + return + } + CreateUserRepo(c, org, opt) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#migrate +func Migrate(c *context.APIContext, f form.MigrateRepo) { + ctxUser := c.User + // Not equal means context user is an organization, + // or is another user/organization if current user is admin. + if f.Uid != ctxUser.ID { + org, err := models.GetUserByID(f.Uid) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetUserByID", err) + } + return + } else if !org.IsOrganization() && !c.User.IsAdmin { + c.Error(403, "", "Given user is not an organization") + return + } + ctxUser = org + } + + if c.HasError() { + c.Error(422, "", c.GetErrMsg()) + return + } + + if ctxUser.IsOrganization() && !c.User.IsAdmin { + // Check ownership of organization. + if !ctxUser.IsOwnedBy(c.User.ID) { + c.Error(403, "", "Given user is not owner of organization") + return + } + } + + remoteAddr, err := f.ParseRemoteAddr(c.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + c.Error(422, "", err) + case addrErr.IsPermissionDenied: + c.Error(422, "", "You are not allowed to import local repositories") + case addrErr.IsInvalidPath: + c.Error(422, "", "Invalid local path, it does not exist or not a directory") + default: + c.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error()) + } + } else { + c.Error(500, "ParseRemoteAddr", err) + } + return + } + + repo, err := models.MigrateRepository(c.User, ctxUser, models.MigrateRepoOptions{ + Name: f.RepoName, + Description: f.Description, + IsPrivate: f.Private || setting.Repository.ForcePrivate, + IsMirror: f.Mirror, + RemoteAddr: remoteAddr, + }) + if err != nil { + if repo != nil { + if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { + log.Error(2, "DeleteRepository: %v", errDelete) + } + } + + if errors.IsReachLimitOfRepo(err) { + c.Error(422, "", err) + } else { + c.Error(500, "MigrateRepository", models.HandleMirrorCredentials(err.Error(), true)) + } + return + } + + log.Trace("Repository migrated: %s/%s", ctxUser.Name, f.RepoName) + c.JSON(201, repo.APIFormat(&api.Permission{true, true, true})) +} + +func parseOwnerAndRepo(c *context.APIContext) (*models.User, *models.Repository) { + owner, err := models.GetUserByName(c.Params(":username")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Error(422, "", err) + } else { + c.Error(500, "GetUserByName", err) + } + return nil, nil + } + + repo, err := models.GetRepositoryByName(owner.ID, c.Params(":reponame")) + if err != nil { + if errors.IsRepoNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetRepositoryByName", err) + } + return nil, nil + } + + return owner, repo +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#get +func Get(c *context.APIContext) { + _, repo := parseOwnerAndRepo(c) + if c.Written() { + return + } + + c.JSON(200, repo.APIFormat(&api.Permission{ + Admin: c.Repo.IsAdmin(), + Push: c.Repo.IsWriter(), + Pull: true, + })) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#delete +func Delete(c *context.APIContext) { + owner, repo := parseOwnerAndRepo(c) + if c.Written() { + return + } + + if owner.IsOrganization() && !owner.IsOwnedBy(c.User.ID) { + c.Error(403, "", "Given user is not owner of organization.") + return + } + + if err := models.DeleteRepository(owner.ID, repo.ID); err != nil { + c.Error(500, "DeleteRepository", err) + return + } + + log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name) + c.Status(204) +} + +func ListForks(c *context.APIContext) { + forks, err := c.Repo.Repository.GetForks() + if err != nil { + c.Error(500, "GetForks", err) + return + } + + apiForks := make([]*api.Repository, len(forks)) + for i := range forks { + if err := forks[i].GetOwner(); err != nil { + c.Error(500, "GetOwner", err) + return + } + apiForks[i] = forks[i].APIFormat(&api.Permission{ + Admin: c.User.IsAdminOfRepo(forks[i]), + Push: c.User.IsWriterOfRepo(forks[i]), + Pull: true, + }) + } + + c.JSON(200, &apiForks) +} + +func MirrorSync(c *context.APIContext) { + _, repo := parseOwnerAndRepo(c) + if c.Written() { + return + } else if !repo.IsMirror { + c.Status(404) + return + } + + go models.MirrorQueue.Add(repo.ID) + c.Status(202) +} diff --git a/routes/api/v1/user/app.go b/routes/api/v1/user/app.go new file mode 100644 index 00000000..bda1e23f --- /dev/null +++ b/routes/api/v1/user/app.go @@ -0,0 +1,40 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" +) + +// https://github.com/gogits/go-gogs-client/wiki/Users#list-access-tokens-for-a-user +func ListAccessTokens(c *context.APIContext) { + tokens, err := models.ListAccessTokens(c.User.ID) + if err != nil { + c.Error(500, "ListAccessTokens", err) + return + } + + apiTokens := make([]*api.AccessToken, len(tokens)) + for i := range tokens { + apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1} + } + c.JSON(200, &apiTokens) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users#create-a-access-token +func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) { + t := &models.AccessToken{ + UID: c.User.ID, + Name: form.Name, + } + if err := models.NewAccessToken(t); err != nil { + c.Error(500, "NewAccessToken", err) + return + } + c.JSON(201, &api.AccessToken{t.Name, t.Sha1}) +} diff --git a/routes/api/v1/user/email.go b/routes/api/v1/user/email.go new file mode 100644 index 00000000..bd1ea52b --- /dev/null +++ b/routes/api/v1/user/email.go @@ -0,0 +1,82 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/api/v1/convert" +) + +// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#list-email-addresses-for-a-user +func ListEmails(c *context.APIContext) { + emails, err := models.GetEmailAddresses(c.User.ID) + if err != nil { + c.Error(500, "GetEmailAddresses", err) + return + } + apiEmails := make([]*api.Email, len(emails)) + for i := range emails { + apiEmails[i] = convert.ToEmail(emails[i]) + } + c.JSON(200, &apiEmails) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#add-email-addresses +func AddEmail(c *context.APIContext, form api.CreateEmailOption) { + if len(form.Emails) == 0 { + c.Status(422) + return + } + + emails := make([]*models.EmailAddress, len(form.Emails)) + for i := range form.Emails { + emails[i] = &models.EmailAddress{ + UID: c.User.ID, + Email: form.Emails[i], + IsActivated: !setting.Service.RegisterEmailConfirm, + } + } + + if err := models.AddEmailAddresses(emails); err != nil { + if models.IsErrEmailAlreadyUsed(err) { + c.Error(422, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email) + } else { + c.Error(500, "AddEmailAddresses", err) + } + return + } + + apiEmails := make([]*api.Email, len(emails)) + for i := range emails { + apiEmails[i] = convert.ToEmail(emails[i]) + } + c.JSON(201, &apiEmails) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#delete-email-addresses +func DeleteEmail(c *context.APIContext, form api.CreateEmailOption) { + if len(form.Emails) == 0 { + c.Status(204) + return + } + + emails := make([]*models.EmailAddress, len(form.Emails)) + for i := range form.Emails { + emails[i] = &models.EmailAddress{ + UID: c.User.ID, + Email: form.Emails[i], + } + } + + if err := models.DeleteEmailAddresses(emails); err != nil { + c.Error(500, "DeleteEmailAddresses", err) + return + } + c.Status(204) +} diff --git a/routes/api/v1/user/follower.go b/routes/api/v1/user/follower.go new file mode 100644 index 00000000..6bbd4c7e --- /dev/null +++ b/routes/api/v1/user/follower.go @@ -0,0 +1,120 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" +) + +func responseApiUsers(c *context.APIContext, users []*models.User) { + apiUsers := make([]*api.User, len(users)) + for i := range users { + apiUsers[i] = users[i].APIFormat() + } + c.JSON(200, &apiUsers) +} + +func listUserFollowers(c *context.APIContext, u *models.User) { + users, err := u.GetFollowers(c.QueryInt("page")) + if err != nil { + c.Error(500, "GetUserFollowers", err) + return + } + responseApiUsers(c, users) +} + +func ListMyFollowers(c *context.APIContext) { + listUserFollowers(c, c.User) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-followers-of-a-user +func ListFollowers(c *context.APIContext) { + u := GetUserByParams(c) + if c.Written() { + return + } + listUserFollowers(c, u) +} + +func listUserFollowing(c *context.APIContext, u *models.User) { + users, err := u.GetFollowing(c.QueryInt("page")) + if err != nil { + c.Error(500, "GetFollowing", err) + return + } + responseApiUsers(c, users) +} + +func ListMyFollowing(c *context.APIContext) { + listUserFollowing(c, c.User) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-users-followed-by-another-user +func ListFollowing(c *context.APIContext) { + u := GetUserByParams(c) + if c.Written() { + return + } + listUserFollowing(c, u) +} + +func checkUserFollowing(c *context.APIContext, u *models.User, followID int64) { + if u.IsFollowing(followID) { + c.Status(204) + } else { + c.Status(404) + } +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-you-are-following-a-user +func CheckMyFollowing(c *context.APIContext) { + target := GetUserByParams(c) + if c.Written() { + return + } + checkUserFollowing(c, c.User, target.ID) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-one-user-follows-another +func CheckFollowing(c *context.APIContext) { + u := GetUserByParams(c) + if c.Written() { + return + } + target := GetUserByParamsName(c, ":target") + if c.Written() { + return + } + checkUserFollowing(c, u, target.ID) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#follow-a-user +func Follow(c *context.APIContext) { + target := GetUserByParams(c) + if c.Written() { + return + } + if err := models.FollowUser(c.User.ID, target.ID); err != nil { + c.Error(500, "FollowUser", err) + return + } + c.Status(204) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#unfollow-a-user +func Unfollow(c *context.APIContext) { + target := GetUserByParams(c) + if c.Written() { + return + } + if err := models.UnfollowUser(c.User.ID, target.ID); err != nil { + c.Error(500, "UnfollowUser", err) + return + } + c.Status(204) +} diff --git a/routes/api/v1/user/key.go b/routes/api/v1/user/key.go new file mode 100644 index 00000000..aef42afc --- /dev/null +++ b/routes/api/v1/user/key.go @@ -0,0 +1,120 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/api/v1/convert" + "github.com/gogits/gogs/routes/api/v1/repo" +) + +func GetUserByParamsName(c *context.APIContext, name string) *models.User { + user, err := models.GetUserByName(c.Params(name)) + if err != nil { + if errors.IsUserNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetUserByName", err) + } + return nil + } + return user +} + +// GetUserByParams returns user whose name is presented in URL paramenter. +func GetUserByParams(c *context.APIContext) *models.User { + return GetUserByParamsName(c, ":username") +} + +func composePublicKeysAPILink() string { + return setting.AppURL + "api/v1/user/keys/" +} + +func listPublicKeys(c *context.APIContext, uid int64) { + keys, err := models.ListPublicKeys(uid) + if err != nil { + c.Error(500, "ListPublicKeys", err) + return + } + + apiLink := composePublicKeysAPILink() + apiKeys := make([]*api.PublicKey, len(keys)) + for i := range keys { + apiKeys[i] = convert.ToPublicKey(apiLink, keys[i]) + } + + c.JSON(200, &apiKeys) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-your-public-keys +func ListMyPublicKeys(c *context.APIContext) { + listPublicKeys(c, c.User.ID) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-public-keys-for-a-user +func ListPublicKeys(c *context.APIContext) { + user := GetUserByParams(c) + if c.Written() { + return + } + listPublicKeys(c, user.ID) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#get-a-single-public-key +func GetPublicKey(c *context.APIContext) { + key, err := models.GetPublicKeyByID(c.ParamsInt64(":id")) + if err != nil { + if models.IsErrKeyNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetPublicKeyByID", err) + } + return + } + + apiLink := composePublicKeysAPILink() + c.JSON(200, convert.ToPublicKey(apiLink, key)) +} + +// CreateUserPublicKey creates new public key to given user by ID. +func CreateUserPublicKey(c *context.APIContext, form api.CreateKeyOption, uid int64) { + content, err := models.CheckPublicKeyString(form.Key) + if err != nil { + repo.HandleCheckKeyStringError(c, err) + return + } + + key, err := models.AddPublicKey(uid, form.Title, content) + if err != nil { + repo.HandleAddKeyError(c, err) + return + } + apiLink := composePublicKeysAPILink() + c.JSON(201, convert.ToPublicKey(apiLink, key)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key +func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) { + CreateUserPublicKey(c, form, c.User.ID) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#delete-a-public-key +func DeletePublicKey(c *context.APIContext) { + if err := models.DeletePublicKey(c.User, c.ParamsInt64(":id")); err != nil { + if models.IsErrKeyAccessDenied(err) { + c.Error(403, "", "You do not have access to this key") + } else { + c.Error(500, "DeletePublicKey", err) + } + return + } + + c.Status(204) +} diff --git a/routes/api/v1/user/user.go b/routes/api/v1/user/user.go new file mode 100644 index 00000000..dbf727de --- /dev/null +++ b/routes/api/v1/user/user.go @@ -0,0 +1,75 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "github.com/Unknwon/com" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" +) + +func Search(c *context.APIContext) { + opts := &models.SearchUserOptions{ + Keyword: c.Query("q"), + Type: models.USER_TYPE_INDIVIDUAL, + PageSize: com.StrTo(c.Query("limit")).MustInt(), + } + if opts.PageSize == 0 { + opts.PageSize = 10 + } + + users, _, err := models.SearchUserByName(opts) + if err != nil { + c.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + + results := make([]*api.User, len(users)) + for i := range users { + results[i] = &api.User{ + ID: users[i].ID, + UserName: users[i].Name, + AvatarUrl: users[i].AvatarLink(), + FullName: users[i].FullName, + } + if c.IsLogged { + results[i].Email = users[i].Email + } + } + + c.JSON(200, map[string]interface{}{ + "ok": true, + "data": results, + }) +} + +func GetInfo(c *context.APIContext) { + u, err := models.GetUserByName(c.Params(":username")) + if err != nil { + if errors.IsUserNotExist(err) { + c.Status(404) + } else { + c.Error(500, "GetUserByName", err) + } + return + } + + // Hide user e-mail when API caller isn't signed in. + if !c.IsLogged { + u.Email = "" + } + c.JSON(200, u.APIFormat()) +} + +func GetAuthenticatedUser(c *context.APIContext) { + c.JSON(200, c.User.APIFormat()) +} diff --git a/routes/dev/template.go b/routes/dev/template.go new file mode 100644 index 00000000..00afa5c4 --- /dev/null +++ b/routes/dev/template.go @@ -0,0 +1,24 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package dev + +import ( + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" +) + +func TemplatePreview(c *context.Context) { + c.Data["User"] = models.User{Name: "Unknown"} + c.Data["AppName"] = setting.AppName + c.Data["AppVer"] = setting.AppVer + c.Data["AppURL"] = setting.AppURL + c.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374" + c.Data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60 + c.Data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60 + c.Data["CurDbValue"] = "" + + c.HTML(200, (c.Params("*"))) +} diff --git a/routes/home.go b/routes/home.go new file mode 100644 index 00000000..9c713391 --- /dev/null +++ b/routes/home.go @@ -0,0 +1,163 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package routes + +import ( + "github.com/Unknwon/paginater" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/user" +) + +const ( + HOME = "home" + EXPLORE_REPOS = "explore/repos" + EXPLORE_USERS = "explore/users" + EXPLORE_ORGANIZATIONS = "explore/organizations" +) + +func Home(c *context.Context) { + if c.IsLogged { + if !c.User.IsActive && setting.Service.RegisterEmailConfirm { + c.Data["Title"] = c.Tr("auth.active_your_account") + c.HTML(200, user.ACTIVATE) + } else { + user.Dashboard(c) + } + return + } + + // Check auto-login. + uname := c.GetCookie(setting.CookieUserName) + if len(uname) != 0 { + c.Redirect(setting.AppSubURL + "/user/login") + return + } + + c.Data["PageIsHome"] = true + c.HTML(200, HOME) +} + +func ExploreRepos(c *context.Context) { + c.Data["Title"] = c.Tr("explore") + c.Data["PageIsExplore"] = true + c.Data["PageIsExploreRepositories"] = true + + page := c.QueryInt("page") + if page <= 0 { + page = 1 + } + + keyword := c.Query("q") + repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ + Keyword: keyword, + UserID: c.UserID(), + OrderBy: "updated_unix DESC", + Page: page, + PageSize: setting.UI.ExplorePagingNum, + }) + if err != nil { + c.Handle(500, "SearchRepositoryByName", err) + return + } + c.Data["Keyword"] = keyword + c.Data["Total"] = count + c.Data["Page"] = paginater.New(int(count), setting.UI.ExplorePagingNum, page, 5) + + if err = models.RepositoryList(repos).LoadAttributes(); err != nil { + c.Handle(500, "LoadAttributes", err) + return + } + c.Data["Repos"] = repos + + c.HTML(200, EXPLORE_REPOS) +} + +type UserSearchOptions struct { + Type models.UserType + Counter func() int64 + Ranger func(int, int) ([]*models.User, error) + PageSize int + OrderBy string + TplName string +} + +func RenderUserSearch(c *context.Context, opts *UserSearchOptions) { + page := c.QueryInt("page") + if page <= 1 { + page = 1 + } + + var ( + users []*models.User + count int64 + err error + ) + + keyword := c.Query("q") + if len(keyword) == 0 { + users, err = opts.Ranger(page, opts.PageSize) + if err != nil { + c.Handle(500, "opts.Ranger", err) + return + } + count = opts.Counter() + } else { + users, count, err = models.SearchUserByName(&models.SearchUserOptions{ + Keyword: keyword, + Type: opts.Type, + OrderBy: opts.OrderBy, + Page: page, + PageSize: opts.PageSize, + }) + if err != nil { + c.Handle(500, "SearchUserByName", err) + return + } + } + c.Data["Keyword"] = keyword + c.Data["Total"] = count + c.Data["Page"] = paginater.New(int(count), opts.PageSize, page, 5) + c.Data["Users"] = users + + c.HTML(200, opts.TplName) +} + +func ExploreUsers(c *context.Context) { + c.Data["Title"] = c.Tr("explore") + c.Data["PageIsExplore"] = true + c.Data["PageIsExploreUsers"] = true + + RenderUserSearch(c, &UserSearchOptions{ + Type: models.USER_TYPE_INDIVIDUAL, + Counter: models.CountUsers, + Ranger: models.Users, + PageSize: setting.UI.ExplorePagingNum, + OrderBy: "updated_unix DESC", + TplName: EXPLORE_USERS, + }) +} + +func ExploreOrganizations(c *context.Context) { + c.Data["Title"] = c.Tr("explore") + c.Data["PageIsExplore"] = true + c.Data["PageIsExploreOrganizations"] = true + + RenderUserSearch(c, &UserSearchOptions{ + Type: models.USER_TYPE_ORGANIZATION, + Counter: models.CountOrganizations, + Ranger: models.Organizations, + PageSize: setting.UI.ExplorePagingNum, + OrderBy: "updated_unix DESC", + TplName: EXPLORE_ORGANIZATIONS, + }) +} + +func NotFound(c *context.Context) { + c.Data["Title"] = "Page Not Found" + c.NotFound() +} diff --git a/routes/install.go b/routes/install.go new file mode 100644 index 00000000..948c67c2 --- /dev/null +++ b/routes/install.go @@ -0,0 +1,392 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package routes + +import ( + "net/mail" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/Unknwon/com" + "github.com/go-xorm/xorm" + log "gopkg.in/clog.v1" + "gopkg.in/ini.v1" + "gopkg.in/macaron.v1" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/cron" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/mailer" + "github.com/gogits/gogs/pkg/markup" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/ssh" + "github.com/gogits/gogs/pkg/template/highlight" + "github.com/gogits/gogs/pkg/tool" + "github.com/gogits/gogs/pkg/user" +) + +const ( + INSTALL = "install" +) + +func checkRunMode() { + if setting.ProdMode { + macaron.Env = macaron.PROD + macaron.ColorLog = false + } else { + git.Debug = true + } + log.Info("Run Mode: %s", strings.Title(macaron.Env)) +} + +func NewServices() { + setting.NewServices() + mailer.NewContext() +} + +// GlobalInit is for global configuration reload-able. +func GlobalInit() { + setting.NewContext() + log.Trace("Custom path: %s", setting.CustomPath) + log.Trace("Log path: %s", setting.LogRootPath) + models.LoadConfigs() + NewServices() + + if setting.InstallLock { + highlight.NewContext() + markup.NewSanitizer() + if err := models.NewEngine(); err != nil { + log.Fatal(2, "Fail to initialize ORM engine: %v", err) + } + models.HasEngine = true + + models.LoadRepoConfig() + models.NewRepoContext() + + // Booting long running goroutines. + cron.NewContext() + models.InitSyncMirrors() + models.InitDeliverHooks() + models.InitTestPullRequests() + } + if models.EnableSQLite3 { + log.Info("SQLite3 Supported") + } + if setting.SupportMiniWinService { + log.Info("Builtin Windows Service Supported") + } + checkRunMode() + + if setting.InstallLock && setting.SSH.StartBuiltinServer { + ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers) + log.Info("SSH server started on %s:%v", setting.SSH.ListenHost, setting.SSH.ListenPort) + log.Trace("SSH server cipher list: %v", setting.SSH.ServerCiphers) + } +} + +func InstallInit(c *context.Context) { + if setting.InstallLock { + c.NotFound() + return + } + + c.Title("install.install") + c.PageIs("Install") + + dbOpts := []string{"MySQL", "PostgreSQL", "MSSQL"} + if models.EnableSQLite3 { + dbOpts = append(dbOpts, "SQLite3") + } + c.Data["DbOptions"] = dbOpts +} + +func Install(c *context.Context) { + f := form.Install{} + + // Database settings + f.DbHost = models.DbCfg.Host + f.DbUser = models.DbCfg.User + f.DbName = models.DbCfg.Name + f.DbPath = models.DbCfg.Path + + c.Data["CurDbOption"] = "MySQL" + switch models.DbCfg.Type { + case "postgres": + c.Data["CurDbOption"] = "PostgreSQL" + case "mssql": + c.Data["CurDbOption"] = "MSSQL" + case "sqlite3": + if models.EnableSQLite3 { + c.Data["CurDbOption"] = "SQLite3" + } + } + + // Application general settings + f.AppName = setting.AppName + f.RepoRootPath = setting.RepoRootPath + + // Note(unknwon): it's hard for Windows users change a running user, + // so just use current one if config says default. + if setting.IsWindows && setting.RunUser == "git" { + f.RunUser = user.CurrentUsername() + } else { + f.RunUser = setting.RunUser + } + + f.Domain = setting.Domain + f.SSHPort = setting.SSH.Port + f.UseBuiltinSSHServer = setting.SSH.StartBuiltinServer + f.HTTPPort = setting.HTTPPort + f.AppUrl = setting.AppURL + f.LogRootPath = setting.LogRootPath + + // E-mail service settings + if setting.MailService != nil { + f.SMTPHost = setting.MailService.Host + f.SMTPFrom = setting.MailService.From + f.SMTPUser = setting.MailService.User + } + f.RegisterConfirm = setting.Service.RegisterEmailConfirm + f.MailNotify = setting.Service.EnableNotifyMail + + // Server and other services settings + f.OfflineMode = setting.OfflineMode + f.DisableGravatar = setting.DisableGravatar + f.EnableFederatedAvatar = setting.EnableFederatedAvatar + f.DisableRegistration = setting.Service.DisableRegistration + f.EnableCaptcha = setting.Service.EnableCaptcha + f.RequireSignInView = setting.Service.RequireSignInView + + form.Assign(f, c.Data) + c.Success(INSTALL) +} + +func InstallPost(c *context.Context, f form.Install) { + c.Data["CurDbOption"] = f.DbType + + if c.HasError() { + if c.HasValue("Err_SMTPEmail") { + c.FormErr("SMTP") + } + if c.HasValue("Err_AdminName") || + c.HasValue("Err_AdminPasswd") || + c.HasValue("Err_AdminEmail") { + c.FormErr("Admin") + } + + c.Success(INSTALL) + return + } + + if _, err := exec.LookPath("git"); err != nil { + c.RenderWithErr(c.Tr("install.test_git_failed", err), INSTALL, &f) + return + } + + // Pass basic check, now test configuration. + // Test database setting. + dbTypes := map[string]string{"MySQL": "mysql", "PostgreSQL": "postgres", "MSSQL": "mssql", "SQLite3": "sqlite3", "TiDB": "tidb"} + models.DbCfg.Type = dbTypes[f.DbType] + models.DbCfg.Host = f.DbHost + models.DbCfg.User = f.DbUser + models.DbCfg.Passwd = f.DbPasswd + models.DbCfg.Name = f.DbName + models.DbCfg.SSLMode = f.SSLMode + models.DbCfg.Path = f.DbPath + + if models.DbCfg.Type == "sqlite3" && len(models.DbCfg.Path) == 0 { + c.FormErr("DbPath") + c.RenderWithErr(c.Tr("install.err_empty_db_path"), INSTALL, &f) + return + } + + // Set test engine. + var x *xorm.Engine + if err := models.NewTestEngine(x); err != nil { + if strings.Contains(err.Error(), `Unknown database type: sqlite3`) { + c.FormErr("DbType") + c.RenderWithErr(c.Tr("install.sqlite3_not_available", "https://gogs.io/docs/installation/install_from_binary.html"), INSTALL, &f) + } else { + c.FormErr("DbSetting") + c.RenderWithErr(c.Tr("install.invalid_db_setting", err), INSTALL, &f) + } + return + } + + // Test repository root path. + f.RepoRootPath = strings.Replace(f.RepoRootPath, "\\", "/", -1) + if err := os.MkdirAll(f.RepoRootPath, os.ModePerm); err != nil { + c.FormErr("RepoRootPath") + c.RenderWithErr(c.Tr("install.invalid_repo_path", err), INSTALL, &f) + return + } + + // Test log root path. + f.LogRootPath = strings.Replace(f.LogRootPath, "\\", "/", -1) + if err := os.MkdirAll(f.LogRootPath, os.ModePerm); err != nil { + c.FormErr("LogRootPath") + c.RenderWithErr(c.Tr("install.invalid_log_root_path", err), INSTALL, &f) + return + } + + currentUser, match := setting.IsRunUserMatchCurrentUser(f.RunUser) + if !match { + c.FormErr("RunUser") + c.RenderWithErr(c.Tr("install.run_user_not_match", f.RunUser, currentUser), INSTALL, &f) + return + } + + // Check host address and port + if len(f.SMTPHost) > 0 && !strings.Contains(f.SMTPHost, ":") { + c.FormErr("SMTP", "SMTPHost") + c.RenderWithErr(c.Tr("install.smtp_host_missing_port"), INSTALL, &f) + return + } + + // Make sure FROM field is valid + if len(f.SMTPFrom) > 0 { + _, err := mail.ParseAddress(f.SMTPFrom) + if err != nil { + c.FormErr("SMTP", "SMTPFrom") + c.RenderWithErr(c.Tr("install.invalid_smtp_from", err), INSTALL, &f) + return + } + } + + // Check logic loophole between disable self-registration and no admin account. + if f.DisableRegistration && len(f.AdminName) == 0 { + c.FormErr("Services", "Admin") + c.RenderWithErr(c.Tr("install.no_admin_and_disable_registration"), INSTALL, f) + return + } + + // Check admin password. + if len(f.AdminName) > 0 && len(f.AdminPasswd) == 0 { + c.FormErr("Admin", "AdminPasswd") + c.RenderWithErr(c.Tr("install.err_empty_admin_password"), INSTALL, f) + return + } + if f.AdminPasswd != f.AdminConfirmPasswd { + c.FormErr("Admin", "AdminPasswd") + c.RenderWithErr(c.Tr("form.password_not_match"), INSTALL, f) + return + } + + if f.AppUrl[len(f.AppUrl)-1] != '/' { + f.AppUrl += "/" + } + + // Save settings. + cfg := ini.Empty() + if com.IsFile(setting.CustomConf) { + // Keeps custom settings if there is already something. + if err := cfg.Append(setting.CustomConf); err != nil { + log.Error(2, "Fail to load custom conf '%s': %v", setting.CustomConf, err) + } + } + cfg.Section("database").Key("DB_TYPE").SetValue(models.DbCfg.Type) + cfg.Section("database").Key("HOST").SetValue(models.DbCfg.Host) + cfg.Section("database").Key("NAME").SetValue(models.DbCfg.Name) + cfg.Section("database").Key("USER").SetValue(models.DbCfg.User) + cfg.Section("database").Key("PASSWD").SetValue(models.DbCfg.Passwd) + cfg.Section("database").Key("SSL_MODE").SetValue(models.DbCfg.SSLMode) + cfg.Section("database").Key("PATH").SetValue(models.DbCfg.Path) + + cfg.Section("").Key("APP_NAME").SetValue(f.AppName) + cfg.Section("repository").Key("ROOT").SetValue(f.RepoRootPath) + cfg.Section("").Key("RUN_USER").SetValue(f.RunUser) + cfg.Section("server").Key("DOMAIN").SetValue(f.Domain) + cfg.Section("server").Key("HTTP_PORT").SetValue(f.HTTPPort) + cfg.Section("server").Key("ROOT_URL").SetValue(f.AppUrl) + + if f.SSHPort == 0 { + cfg.Section("server").Key("DISABLE_SSH").SetValue("true") + } else { + cfg.Section("server").Key("DISABLE_SSH").SetValue("false") + cfg.Section("server").Key("SSH_PORT").SetValue(com.ToStr(f.SSHPort)) + cfg.Section("server").Key("START_SSH_SERVER").SetValue(com.ToStr(f.UseBuiltinSSHServer)) + } + + if len(strings.TrimSpace(f.SMTPHost)) > 0 { + cfg.Section("mailer").Key("ENABLED").SetValue("true") + cfg.Section("mailer").Key("HOST").SetValue(f.SMTPHost) + cfg.Section("mailer").Key("FROM").SetValue(f.SMTPFrom) + cfg.Section("mailer").Key("USER").SetValue(f.SMTPUser) + cfg.Section("mailer").Key("PASSWD").SetValue(f.SMTPPasswd) + } else { + cfg.Section("mailer").Key("ENABLED").SetValue("false") + } + cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(com.ToStr(f.RegisterConfirm)) + cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(com.ToStr(f.MailNotify)) + + cfg.Section("server").Key("OFFLINE_MODE").SetValue(com.ToStr(f.OfflineMode)) + cfg.Section("picture").Key("DISABLE_GRAVATAR").SetValue(com.ToStr(f.DisableGravatar)) + cfg.Section("picture").Key("ENABLE_FEDERATED_AVATAR").SetValue(com.ToStr(f.EnableFederatedAvatar)) + cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(com.ToStr(f.DisableRegistration)) + cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(com.ToStr(f.EnableCaptcha)) + cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(f.RequireSignInView)) + + cfg.Section("").Key("RUN_MODE").SetValue("prod") + + cfg.Section("session").Key("PROVIDER").SetValue("file") + + mode := "file" + if f.EnableConsoleMode { + mode = "console, file" + } + cfg.Section("log").Key("MODE").SetValue(mode) + cfg.Section("log").Key("LEVEL").SetValue("Info") + cfg.Section("log").Key("ROOT_PATH").SetValue(f.LogRootPath) + + cfg.Section("security").Key("INSTALL_LOCK").SetValue("true") + secretKey, err := tool.RandomString(15) + if err != nil { + c.RenderWithErr(c.Tr("install.secret_key_failed", err), INSTALL, &f) + return + } + cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey) + + os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm) + if err := cfg.SaveTo(setting.CustomConf); err != nil { + c.RenderWithErr(c.Tr("install.save_config_failed", err), INSTALL, &f) + return + } + + GlobalInit() + + // Create admin account + if len(f.AdminName) > 0 { + u := &models.User{ + Name: f.AdminName, + Email: f.AdminEmail, + Passwd: f.AdminPasswd, + IsAdmin: true, + IsActive: true, + } + if err := models.CreateUser(u); err != nil { + if !models.IsErrUserAlreadyExist(err) { + setting.InstallLock = false + c.FormErr("AdminName", "AdminEmail") + c.RenderWithErr(c.Tr("install.invalid_admin_setting", err), INSTALL, &f) + return + } + log.Info("Admin account already exist") + u, _ = models.GetUserByName(u.Name) + } + + // Auto-login for admin + c.Session.Set("uid", u.ID) + c.Session.Set("uname", u.Name) + } + + log.Info("First-time run install finished!") + c.Flash.Success(c.Tr("install.install_success")) + c.Redirect(f.AppUrl + "user/login") +} diff --git a/routes/org/members.go b/routes/org/members.go new file mode 100644 index 00000000..b529748c --- /dev/null +++ b/routes/org/members.go @@ -0,0 +1,123 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package org + +import ( + "github.com/Unknwon/com" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + MEMBERS = "org/member/members" + MEMBER_INVITE = "org/member/invite" +) + +func Members(c *context.Context) { + org := c.Org.Organization + c.Data["Title"] = org.FullName + c.Data["PageIsOrgMembers"] = true + + if err := org.GetMembers(); err != nil { + c.Handle(500, "GetMembers", err) + return + } + c.Data["Members"] = org.Members + + c.HTML(200, MEMBERS) +} + +func MembersAction(c *context.Context) { + uid := com.StrTo(c.Query("uid")).MustInt64() + if uid == 0 { + c.Redirect(c.Org.OrgLink + "/members") + return + } + + org := c.Org.Organization + var err error + switch c.Params(":action") { + case "private": + if c.User.ID != uid && !c.Org.IsOwner { + c.Error(404) + return + } + err = models.ChangeOrgUserStatus(org.ID, uid, false) + case "public": + if c.User.ID != uid && !c.Org.IsOwner { + c.Error(404) + return + } + err = models.ChangeOrgUserStatus(org.ID, uid, true) + case "remove": + if !c.Org.IsOwner { + c.Error(404) + return + } + err = org.RemoveMember(uid) + if models.IsErrLastOrgOwner(err) { + c.Flash.Error(c.Tr("form.last_org_owner")) + c.Redirect(c.Org.OrgLink + "/members") + return + } + case "leave": + err = org.RemoveMember(c.User.ID) + if models.IsErrLastOrgOwner(err) { + c.Flash.Error(c.Tr("form.last_org_owner")) + c.Redirect(c.Org.OrgLink + "/members") + return + } + } + + if err != nil { + log.Error(4, "Action(%s): %v", c.Params(":action"), err) + c.JSON(200, map[string]interface{}{ + "ok": false, + "err": err.Error(), + }) + return + } + + if c.Params(":action") != "leave" { + c.Redirect(c.Org.OrgLink + "/members") + } else { + c.Redirect(setting.AppSubURL + "/") + } +} + +func Invitation(c *context.Context) { + org := c.Org.Organization + c.Data["Title"] = org.FullName + c.Data["PageIsOrgMembers"] = true + + if c.Req.Method == "POST" { + uname := c.Query("uname") + u, err := models.GetUserByName(uname) + if err != nil { + if errors.IsUserNotExist(err) { + c.Flash.Error(c.Tr("form.user_not_exist")) + c.Redirect(c.Org.OrgLink + "/invitations/new") + } else { + c.Handle(500, " GetUserByName", err) + } + return + } + + if err = org.AddMember(u.ID); err != nil { + c.Handle(500, " AddMember", err) + return + } + + log.Trace("New member added(%s): %s", org.Name, u.Name) + c.Redirect(c.Org.OrgLink + "/members") + return + } + + c.HTML(200, MEMBER_INVITE) +} diff --git a/routes/org/org.go b/routes/org/org.go new file mode 100644 index 00000000..775e9915 --- /dev/null +++ b/routes/org/org.go @@ -0,0 +1,56 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package org + +import ( + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + CREATE = "org/create" +) + +func Create(c *context.Context) { + c.Data["Title"] = c.Tr("new_org") + c.HTML(200, CREATE) +} + +func CreatePost(c *context.Context, f form.CreateOrg) { + c.Data["Title"] = c.Tr("new_org") + + if c.HasError() { + c.HTML(200, CREATE) + return + } + + org := &models.User{ + Name: f.OrgName, + IsActive: true, + Type: models.USER_TYPE_ORGANIZATION, + } + + if err := models.CreateOrganization(org, c.User); err != nil { + c.Data["Err_OrgName"] = true + switch { + case models.IsErrUserAlreadyExist(err): + c.RenderWithErr(c.Tr("form.org_name_been_taken"), CREATE, &f) + case models.IsErrNameReserved(err): + c.RenderWithErr(c.Tr("org.form.name_reserved", err.(models.ErrNameReserved).Name), CREATE, &f) + case models.IsErrNamePatternNotAllowed(err): + c.RenderWithErr(c.Tr("org.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), CREATE, &f) + default: + c.Handle(500, "CreateOrganization", err) + } + return + } + log.Trace("Organization created: %s", org.Name) + + c.Redirect(setting.AppSubURL + "/org/" + f.OrgName + "/dashboard") +} diff --git a/routes/org/setting.go b/routes/org/setting.go new file mode 100644 index 00000000..397ffa8f --- /dev/null +++ b/routes/org/setting.go @@ -0,0 +1,168 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package org + +import ( + "strings" + + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/user" +) + +const ( + SETTINGS_OPTIONS = "org/settings/options" + SETTINGS_DELETE = "org/settings/delete" + SETTINGS_WEBHOOKS = "org/settings/webhooks" +) + +func Settings(c *context.Context) { + c.Data["Title"] = c.Tr("org.settings") + c.Data["PageIsSettingsOptions"] = true + c.HTML(200, SETTINGS_OPTIONS) +} + +func SettingsPost(c *context.Context, f form.UpdateOrgSetting) { + c.Data["Title"] = c.Tr("org.settings") + c.Data["PageIsSettingsOptions"] = true + + if c.HasError() { + c.HTML(200, SETTINGS_OPTIONS) + return + } + + org := c.Org.Organization + + // Check if organization name has been changed. + if org.LowerName != strings.ToLower(f.Name) { + isExist, err := models.IsUserExist(org.ID, f.Name) + if err != nil { + c.Handle(500, "IsUserExist", err) + return + } else if isExist { + c.Data["OrgName"] = true + c.RenderWithErr(c.Tr("form.username_been_taken"), SETTINGS_OPTIONS, &f) + return + } else if err = models.ChangeUserName(org, f.Name); err != nil { + c.Data["OrgName"] = true + switch { + case models.IsErrNameReserved(err): + c.RenderWithErr(c.Tr("user.form.name_reserved"), SETTINGS_OPTIONS, &f) + case models.IsErrNamePatternNotAllowed(err): + c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed"), SETTINGS_OPTIONS, &f) + default: + c.Handle(500, "ChangeUserName", err) + } + return + } + // reset c.org.OrgLink with new name + c.Org.OrgLink = setting.AppSubURL + "/org/" + f.Name + log.Trace("Organization name changed: %s -> %s", org.Name, f.Name) + } + // In case it's just a case change. + org.Name = f.Name + org.LowerName = strings.ToLower(f.Name) + + if c.User.IsAdmin { + org.MaxRepoCreation = f.MaxRepoCreation + } + + org.FullName = f.FullName + org.Description = f.Description + org.Website = f.Website + org.Location = f.Location + if err := models.UpdateUser(org); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + log.Trace("Organization setting updated: %s", org.Name) + c.Flash.Success(c.Tr("org.settings.update_setting_success")) + c.Redirect(c.Org.OrgLink + "/settings") +} + +func SettingsAvatar(c *context.Context, f form.Avatar) { + f.Source = form.AVATAR_LOCAL + if err := user.UpdateAvatarSetting(c, f, c.Org.Organization); err != nil { + c.Flash.Error(err.Error()) + } else { + c.Flash.Success(c.Tr("org.settings.update_avatar_success")) + } + + c.Redirect(c.Org.OrgLink + "/settings") +} + +func SettingsDeleteAvatar(c *context.Context) { + if err := c.Org.Organization.DeleteAvatar(); err != nil { + c.Flash.Error(err.Error()) + } + + c.Redirect(c.Org.OrgLink + "/settings") +} + +func SettingsDelete(c *context.Context) { + c.Data["Title"] = c.Tr("org.settings") + c.Data["PageIsSettingsDelete"] = true + + org := c.Org.Organization + if c.Req.Method == "POST" { + if _, err := models.UserSignIn(c.User.Name, c.Query("password")); err != nil { + if errors.IsUserNotExist(err) { + c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil) + } else { + c.Handle(500, "UserSignIn", err) + } + return + } + + if err := models.DeleteOrganization(org); err != nil { + if models.IsErrUserOwnRepos(err) { + c.Flash.Error(c.Tr("form.org_still_own_repo")) + c.Redirect(c.Org.OrgLink + "/settings/delete") + } else { + c.Handle(500, "DeleteOrganization", err) + } + } else { + log.Trace("Organization deleted: %s", org.Name) + c.Redirect(setting.AppSubURL + "/") + } + return + } + + c.HTML(200, SETTINGS_DELETE) +} + +func Webhooks(c *context.Context) { + c.Data["Title"] = c.Tr("org.settings") + c.Data["PageIsSettingsHooks"] = true + c.Data["BaseLink"] = c.Org.OrgLink + c.Data["Description"] = c.Tr("org.settings.hooks_desc") + c.Data["Types"] = setting.Webhook.Types + + ws, err := models.GetWebhooksByOrgID(c.Org.Organization.ID) + if err != nil { + c.Handle(500, "GetWebhooksByOrgId", err) + return + } + + c.Data["Webhooks"] = ws + c.HTML(200, SETTINGS_WEBHOOKS) +} + +func DeleteWebhook(c *context.Context) { + if err := models.DeleteWebhookOfOrgByID(c.Org.Organization.ID, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteWebhookByOrgID: " + err.Error()) + } else { + c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Org.OrgLink + "/settings/hooks", + }) +} diff --git a/routes/org/teams.go b/routes/org/teams.go new file mode 100644 index 00000000..c97d470d --- /dev/null +++ b/routes/org/teams.go @@ -0,0 +1,271 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package org + +import ( + "path" + + "github.com/Unknwon/com" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" +) + +const ( + TEAMS = "org/team/teams" + TEAM_NEW = "org/team/new" + TEAM_MEMBERS = "org/team/members" + TEAM_REPOSITORIES = "org/team/repositories" +) + +func Teams(c *context.Context) { + org := c.Org.Organization + c.Data["Title"] = org.FullName + c.Data["PageIsOrgTeams"] = true + + for _, t := range org.Teams { + if err := t.GetMembers(); err != nil { + c.Handle(500, "GetMembers", err) + return + } + } + c.Data["Teams"] = org.Teams + + c.HTML(200, TEAMS) +} + +func TeamsAction(c *context.Context) { + uid := com.StrTo(c.Query("uid")).MustInt64() + if uid == 0 { + c.Redirect(c.Org.OrgLink + "/teams") + return + } + + page := c.Query("page") + var err error + switch c.Params(":action") { + case "join": + if !c.Org.IsOwner { + c.Error(404) + return + } + err = c.Org.Team.AddMember(c.User.ID) + case "leave": + err = c.Org.Team.RemoveMember(c.User.ID) + case "remove": + if !c.Org.IsOwner { + c.Error(404) + return + } + err = c.Org.Team.RemoveMember(uid) + page = "team" + case "add": + if !c.Org.IsOwner { + c.Error(404) + return + } + uname := c.Query("uname") + var u *models.User + u, err = models.GetUserByName(uname) + if err != nil { + if errors.IsUserNotExist(err) { + c.Flash.Error(c.Tr("form.user_not_exist")) + c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName) + } else { + c.Handle(500, " GetUserByName", err) + } + return + } + + err = c.Org.Team.AddMember(u.ID) + page = "team" + } + + if err != nil { + if models.IsErrLastOrgOwner(err) { + c.Flash.Error(c.Tr("form.last_org_owner")) + } else { + log.Error(3, "Action(%s): %v", c.Params(":action"), err) + c.JSON(200, map[string]interface{}{ + "ok": false, + "err": err.Error(), + }) + return + } + } + + switch page { + case "team": + c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName) + default: + c.Redirect(c.Org.OrgLink + "/teams") + } +} + +func TeamsRepoAction(c *context.Context) { + if !c.Org.IsOwner { + c.Error(404) + return + } + + var err error + switch c.Params(":action") { + case "add": + repoName := path.Base(c.Query("repo_name")) + var repo *models.Repository + repo, err = models.GetRepositoryByName(c.Org.Organization.ID, repoName) + if err != nil { + if errors.IsRepoNotExist(err) { + c.Flash.Error(c.Tr("org.teams.add_nonexistent_repo")) + c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName + "/repositories") + return + } + c.Handle(500, "GetRepositoryByName", err) + return + } + err = c.Org.Team.AddRepository(repo) + case "remove": + err = c.Org.Team.RemoveRepository(com.StrTo(c.Query("repoid")).MustInt64()) + } + + if err != nil { + log.Error(3, "Action(%s): '%s' %v", c.Params(":action"), c.Org.Team.Name, err) + c.Handle(500, "TeamsRepoAction", err) + return + } + c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName + "/repositories") +} + +func NewTeam(c *context.Context) { + c.Data["Title"] = c.Org.Organization.FullName + c.Data["PageIsOrgTeams"] = true + c.Data["PageIsOrgTeamsNew"] = true + c.Data["Team"] = &models.Team{} + c.HTML(200, TEAM_NEW) +} + +func NewTeamPost(c *context.Context, f form.CreateTeam) { + c.Data["Title"] = c.Org.Organization.FullName + c.Data["PageIsOrgTeams"] = true + c.Data["PageIsOrgTeamsNew"] = true + + t := &models.Team{ + OrgID: c.Org.Organization.ID, + Name: f.TeamName, + Description: f.Description, + Authorize: models.ParseAccessMode(f.Permission), + } + c.Data["Team"] = t + + if c.HasError() { + c.HTML(200, TEAM_NEW) + return + } + + if err := models.NewTeam(t); err != nil { + c.Data["Err_TeamName"] = true + switch { + case models.IsErrTeamAlreadyExist(err): + c.RenderWithErr(c.Tr("form.team_name_been_taken"), TEAM_NEW, &f) + case models.IsErrNameReserved(err): + c.RenderWithErr(c.Tr("org.form.team_name_reserved", err.(models.ErrNameReserved).Name), TEAM_NEW, &f) + default: + c.Handle(500, "NewTeam", err) + } + return + } + log.Trace("Team created: %s/%s", c.Org.Organization.Name, t.Name) + c.Redirect(c.Org.OrgLink + "/teams/" + t.LowerName) +} + +func TeamMembers(c *context.Context) { + c.Data["Title"] = c.Org.Team.Name + c.Data["PageIsOrgTeams"] = true + if err := c.Org.Team.GetMembers(); err != nil { + c.Handle(500, "GetMembers", err) + return + } + c.HTML(200, TEAM_MEMBERS) +} + +func TeamRepositories(c *context.Context) { + c.Data["Title"] = c.Org.Team.Name + c.Data["PageIsOrgTeams"] = true + if err := c.Org.Team.GetRepositories(); err != nil { + c.Handle(500, "GetRepositories", err) + return + } + c.HTML(200, TEAM_REPOSITORIES) +} + +func EditTeam(c *context.Context) { + c.Data["Title"] = c.Org.Organization.FullName + c.Data["PageIsOrgTeams"] = true + c.Data["team_name"] = c.Org.Team.Name + c.Data["desc"] = c.Org.Team.Description + c.HTML(200, TEAM_NEW) +} + +func EditTeamPost(c *context.Context, f form.CreateTeam) { + t := c.Org.Team + c.Data["Title"] = c.Org.Organization.FullName + c.Data["PageIsOrgTeams"] = true + c.Data["Team"] = t + + if c.HasError() { + c.HTML(200, TEAM_NEW) + return + } + + isAuthChanged := false + if !t.IsOwnerTeam() { + // Validate permission level. + var auth models.AccessMode + switch f.Permission { + case "read": + auth = models.ACCESS_MODE_READ + case "write": + auth = models.ACCESS_MODE_WRITE + case "admin": + auth = models.ACCESS_MODE_ADMIN + default: + c.Error(401) + return + } + + t.Name = f.TeamName + if t.Authorize != auth { + isAuthChanged = true + t.Authorize = auth + } + } + t.Description = f.Description + if err := models.UpdateTeam(t, isAuthChanged); err != nil { + c.Data["Err_TeamName"] = true + switch { + case models.IsErrTeamAlreadyExist(err): + c.RenderWithErr(c.Tr("form.team_name_been_taken"), TEAM_NEW, &f) + default: + c.Handle(500, "UpdateTeam", err) + } + return + } + c.Redirect(c.Org.OrgLink + "/teams/" + t.LowerName) +} + +func DeleteTeam(c *context.Context) { + if err := models.DeleteTeam(c.Org.Team); err != nil { + c.Flash.Error("DeleteTeam: " + err.Error()) + } else { + c.Flash.Success(c.Tr("org.teams.delete_team_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Org.OrgLink + "/teams", + }) +} diff --git a/routes/repo/branch.go b/routes/repo/branch.go new file mode 100644 index 00000000..685df2e7 --- /dev/null +++ b/routes/repo/branch.go @@ -0,0 +1,142 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "time" + + log "gopkg.in/clog.v1" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" +) + +const ( + BRANCHES_OVERVIEW = "repo/branches/overview" + BRANCHES_ALL = "repo/branches/all" +) + +type Branch struct { + Name string + Commit *git.Commit + IsProtected bool +} + +func loadBranches(c *context.Context) []*Branch { + rawBranches, err := c.Repo.Repository.GetBranches() + if err != nil { + c.Handle(500, "GetBranches", err) + return nil + } + + protectBranches, err := models.GetProtectBranchesByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Handle(500, "GetProtectBranchesByRepoID", err) + return nil + } + + branches := make([]*Branch, len(rawBranches)) + for i := range rawBranches { + commit, err := rawBranches[i].GetCommit() + if err != nil { + c.Handle(500, "GetCommit", err) + return nil + } + + branches[i] = &Branch{ + Name: rawBranches[i].Name, + Commit: commit, + } + + for j := range protectBranches { + if branches[i].Name == protectBranches[j].Name { + branches[i].IsProtected = true + break + } + } + } + + c.Data["AllowPullRequest"] = c.Repo.Repository.AllowsPulls() + return branches +} + +func Branches(c *context.Context) { + c.Data["Title"] = c.Tr("repo.git_branches") + c.Data["PageIsBranchesOverview"] = true + + branches := loadBranches(c) + if c.Written() { + return + } + + now := time.Now() + activeBranches := make([]*Branch, 0, 3) + staleBranches := make([]*Branch, 0, 3) + for i := range branches { + switch { + case branches[i].Name == c.Repo.BranchName: + c.Data["DefaultBranch"] = branches[i] + case branches[i].Commit.Committer.When.Add(30 * 24 * time.Hour).After(now): // 30 days + activeBranches = append(activeBranches, branches[i]) + case branches[i].Commit.Committer.When.Add(3 * 30 * 24 * time.Hour).Before(now): // 90 days + staleBranches = append(staleBranches, branches[i]) + } + } + + c.Data["ActiveBranches"] = activeBranches + c.Data["StaleBranches"] = staleBranches + c.HTML(200, BRANCHES_OVERVIEW) +} + +func AllBranches(c *context.Context) { + c.Data["Title"] = c.Tr("repo.git_branches") + c.Data["PageIsBranchesAll"] = true + + branches := loadBranches(c) + if c.Written() { + return + } + c.Data["Branches"] = branches + + c.HTML(200, BRANCHES_ALL) +} + +func DeleteBranchPost(c *context.Context) { + branchName := c.Params("*") + commitID := c.Query("commit") + + defer func() { + redirectTo := c.Query("redirect_to") + if len(redirectTo) == 0 { + redirectTo = c.Repo.RepoLink + } + c.Redirect(redirectTo) + }() + + if !c.Repo.GitRepo.IsBranchExist(branchName) { + return + } + if len(commitID) > 0 { + branchCommitID, err := c.Repo.GitRepo.GetBranchCommitID(branchName) + if err != nil { + log.Error(2, "GetBranchCommitID: %v", err) + return + } + + if branchCommitID != commitID { + c.Flash.Error(c.Tr("repo.pulls.delete_branch_has_new_commits")) + return + } + } + + if err := c.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ + Force: true, + }); err != nil { + log.Error(2, "DeleteBranch '%s': %v", branchName, err) + return + } +} diff --git a/routes/repo/commit.go b/routes/repo/commit.go new file mode 100644 index 00000000..17ea5dbe --- /dev/null +++ b/routes/repo/commit.go @@ -0,0 +1,239 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "container/list" + "path" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + COMMITS = "repo/commits" + DIFF = "repo/diff/page" +) + +func RefCommits(c *context.Context) { + c.Data["PageIsViewFiles"] = true + switch { + case len(c.Repo.TreePath) == 0: + Commits(c) + case c.Repo.TreePath == "search": + SearchCommits(c) + default: + FileHistory(c) + } +} + +func RenderIssueLinks(oldCommits *list.List, repoLink string) *list.List { + newCommits := list.New() + for e := oldCommits.Front(); e != nil; e = e.Next() { + c := e.Value.(*git.Commit) + newCommits.PushBack(c) + } + return newCommits +} + +func renderCommits(c *context.Context, filename string) { + c.Data["Title"] = c.Tr("repo.commits.commit_history") + " · " + c.Repo.Repository.FullName() + c.Data["PageIsCommits"] = true + + page := c.QueryInt("page") + if page < 1 { + page = 1 + } + pageSize := c.QueryInt("pageSize") + if pageSize < 1 { + pageSize = git.DefaultCommitsPageSize + } + + // Both 'git log branchName' and 'git log commitID' work. + var err error + var commits *list.List + if len(filename) == 0 { + commits, err = c.Repo.Commit.CommitsByRangeSize(page, pageSize) + } else { + commits, err = c.Repo.GitRepo.CommitsByFileAndRangeSize(c.Repo.BranchName, filename, page, pageSize) + } + if err != nil { + c.Handle(500, "CommitsByRangeSize/CommitsByFileAndRangeSize", err) + return + } + commits = RenderIssueLinks(commits, c.Repo.RepoLink) + commits = models.ValidateCommitsWithEmails(commits) + c.Data["Commits"] = commits + + if page > 1 { + c.Data["HasPrevious"] = true + c.Data["PreviousPage"] = page - 1 + } + if commits.Len() == pageSize { + c.Data["HasNext"] = true + c.Data["NextPage"] = page + 1 + } + c.Data["PageSize"] = pageSize + + c.Data["Username"] = c.Repo.Owner.Name + c.Data["Reponame"] = c.Repo.Repository.Name + c.HTML(200, COMMITS) +} + +func Commits(c *context.Context) { + renderCommits(c, "") +} + +func SearchCommits(c *context.Context) { + c.Data["PageIsCommits"] = true + + keyword := c.Query("q") + if len(keyword) == 0 { + c.Redirect(c.Repo.RepoLink + "/commits/" + c.Repo.BranchName) + return + } + + commits, err := c.Repo.Commit.SearchCommits(keyword) + if err != nil { + c.Handle(500, "SearchCommits", err) + return + } + commits = RenderIssueLinks(commits, c.Repo.RepoLink) + commits = models.ValidateCommitsWithEmails(commits) + c.Data["Commits"] = commits + + c.Data["Keyword"] = keyword + c.Data["Username"] = c.Repo.Owner.Name + c.Data["Reponame"] = c.Repo.Repository.Name + c.Data["Branch"] = c.Repo.BranchName + c.HTML(200, COMMITS) +} + +func FileHistory(c *context.Context) { + renderCommits(c, c.Repo.TreePath) +} + +func Diff(c *context.Context) { + c.Data["PageIsDiff"] = true + c.Data["RequireHighlightJS"] = true + + userName := c.Repo.Owner.Name + repoName := c.Repo.Repository.Name + commitID := c.Params(":sha") + + commit, err := c.Repo.GitRepo.GetCommit(commitID) + if err != nil { + if git.IsErrNotExist(err) { + c.Handle(404, "Repo.GitRepo.GetCommit", err) + } else { + c.Handle(500, "Repo.GitRepo.GetCommit", err) + } + return + } + + diff, err := models.GetDiffCommit(models.RepoPath(userName, repoName), + commitID, setting.Git.MaxGitDiffLines, + setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) + if err != nil { + c.NotFoundOrServerError("GetDiffCommit", git.IsErrNotExist, 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 { + c.Handle(404, "repo.Diff", err) + return + } + } + + setEditorconfigIfExists(c) + if c.Written() { + return + } + + c.Data["CommitID"] = commitID + c.Data["IsSplitStyle"] = c.Query("style") == "split" + c.Data["Username"] = userName + c.Data["Reponame"] = repoName + c.Data["IsImageFile"] = commit.IsImageFile + c.Data["Title"] = commit.Summary() + " · " + tool.ShortSHA1(commitID) + c.Data["Commit"] = commit + c.Data["Author"] = models.ValidateCommitWithEmail(commit) + c.Data["Diff"] = diff + c.Data["Parents"] = parents + c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 + c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", commitID) + if commit.ParentCount() > 0 { + c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", parents[0]) + } + c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", commitID) + c.HTML(200, DIFF) +} + +func RawDiff(c *context.Context) { + if err := git.GetRawDiff( + models.RepoPath(c.Repo.Owner.Name, c.Repo.Repository.Name), + c.Params(":sha"), + git.RawDiffType(c.Params(":ext")), + c.Resp, + ); err != nil { + c.NotFoundOrServerError("GetRawDiff", git.IsErrNotExist, err) + return + } +} + +func CompareDiff(c *context.Context) { + c.Data["IsDiffCompare"] = true + userName := c.Repo.Owner.Name + repoName := c.Repo.Repository.Name + beforeCommitID := c.Params(":before") + afterCommitID := c.Params(":after") + + commit, err := c.Repo.GitRepo.GetCommit(afterCommitID) + if err != nil { + c.Handle(404, "GetCommit", err) + return + } + + diff, err := models.GetDiffRange(models.RepoPath(userName, repoName), beforeCommitID, + afterCommitID, setting.Git.MaxGitDiffLines, + setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) + if err != nil { + c.Handle(404, "GetDiffRange", err) + return + } + + commits, err := commit.CommitsBeforeUntil(beforeCommitID) + if err != nil { + c.Handle(500, "CommitsBeforeUntil", err) + return + } + commits = models.ValidateCommitsWithEmails(commits) + + c.Data["IsSplitStyle"] = c.Query("style") == "split" + c.Data["CommitRepoLink"] = c.Repo.RepoLink + c.Data["Commits"] = commits + c.Data["CommitsCount"] = commits.Len() + c.Data["BeforeCommitID"] = beforeCommitID + c.Data["AfterCommitID"] = afterCommitID + c.Data["Username"] = userName + c.Data["Reponame"] = repoName + c.Data["IsImageFile"] = commit.IsImageFile + c.Data["Title"] = "Comparing " + tool.ShortSHA1(beforeCommitID) + "..." + tool.ShortSHA1(afterCommitID) + " · " + userName + "/" + repoName + c.Data["Commit"] = commit + c.Data["Diff"] = diff + c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 + c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", afterCommitID) + c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", beforeCommitID) + c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", afterCommitID) + c.HTML(200, DIFF) +} diff --git a/routes/repo/download.go b/routes/repo/download.go new file mode 100644 index 00000000..e9a29989 --- /dev/null +++ b/routes/repo/download.go @@ -0,0 +1,60 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "io" + "path" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/pkg/tool" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" +) + +func ServeData(c *context.Context, name string, reader io.Reader) error { + buf := make([]byte, 1024) + n, _ := reader.Read(buf) + if n >= 0 { + buf = buf[:n] + } + + if !tool.IsTextFile(buf) { + if !tool.IsImageFile(buf) { + c.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"") + c.Resp.Header().Set("Content-Transfer-Encoding", "binary") + } + } else if !setting.Repository.EnableRawFileRenderMode || !c.QueryBool("render") { + c.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") + } + c.Resp.Write(buf) + _, err := io.Copy(c.Resp, reader) + return err +} + +func ServeBlob(c *context.Context, blob *git.Blob) error { + dataRc, err := blob.Data() + if err != nil { + return err + } + + return ServeData(c, path.Base(c.Repo.TreePath), dataRc) +} + +func SingleDownload(c *context.Context) { + blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath) + if err != nil { + if git.IsErrNotExist(err) { + c.Handle(404, "GetBlobByPath", nil) + } else { + c.Handle(500, "GetBlobByPath", err) + } + return + } + if err = ServeBlob(c, blob); err != nil { + c.Handle(500, "ServeBlob", err) + } +} diff --git a/routes/repo/editor.go b/routes/repo/editor.go new file mode 100644 index 00000000..4cd78d70 --- /dev/null +++ b/routes/repo/editor.go @@ -0,0 +1,571 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "fmt" + "io/ioutil" + "net/http" + "path" + "strings" + + log "gopkg.in/clog.v1" + + "github.com/gogits/git-module" + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/template" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + EDIT_FILE = "repo/editor/edit" + EDIT_DIFF_PREVIEW = "repo/editor/diff_preview" + DELETE_FILE = "repo/editor/delete" + UPLOAD_FILE = "repo/editor/upload" +) + +// getParentTreeFields returns list of parent tree names and corresponding tree paths +// based on given tree path. +func getParentTreeFields(treePath string) (treeNames []string, treePaths []string) { + if len(treePath) == 0 { + return treeNames, treePaths + } + + treeNames = strings.Split(treePath, "/") + treePaths = make([]string, len(treeNames)) + for i := range treeNames { + treePaths[i] = strings.Join(treeNames[:i+1], "/") + } + return treeNames, treePaths +} + +func editFile(c *context.Context, isNewFile bool) { + c.PageIs("Edit") + c.RequireHighlightJS() + c.RequireSimpleMDE() + c.Data["IsNewFile"] = isNewFile + + treeNames, treePaths := getParentTreeFields(c.Repo.TreePath) + + if !isNewFile { + entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath) + if err != nil { + c.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err) + return + } + + // No way to edit a directory online. + if entry.IsDir() { + c.NotFound() + return + } + + blob := entry.Blob() + dataRc, err := blob.Data() + if err != nil { + c.ServerError("blob.Data", err) + return + } + + c.Data["FileSize"] = blob.Size() + c.Data["FileName"] = blob.Name() + + buf := make([]byte, 1024) + n, _ := dataRc.Read(buf) + buf = buf[:n] + + // Only text file are editable online. + if !tool.IsTextFile(buf) { + c.NotFound() + return + } + + d, _ := ioutil.ReadAll(dataRc) + buf = append(buf, d...) + if err, content := template.ToUTF8WithErr(buf); err != nil { + if err != nil { + log.Error(2, "ToUTF8WithErr: %v", err) + } + c.Data["FileContent"] = string(buf) + } else { + c.Data["FileContent"] = content + } + } else { + treeNames = append(treeNames, "") // Append empty string to allow user name the new file. + } + + c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath) + c.Data["TreeNames"] = treeNames + c.Data["TreePaths"] = treePaths + c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName + c.Data["commit_summary"] = "" + c.Data["commit_message"] = "" + c.Data["commit_choice"] = "direct" + c.Data["new_branch_name"] = "" + c.Data["last_commit"] = c.Repo.Commit.ID + c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",") + c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") + c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",") + c.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", setting.AppSubURL, c.Repo.Repository.FullName()) + + c.Success(EDIT_FILE) +} + +func EditFile(c *context.Context) { + editFile(c, false) +} + +func NewFile(c *context.Context) { + editFile(c, true) +} + +func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) { + c.PageIs("Edit") + c.RequireHighlightJS() + c.RequireSimpleMDE() + c.Data["IsNewFile"] = isNewFile + + oldBranchName := c.Repo.BranchName + branchName := oldBranchName + oldTreePath := c.Repo.TreePath + lastCommit := f.LastCommit + f.LastCommit = c.Repo.Commit.ID.String() + + if f.IsNewBrnach() { + branchName = f.NewBranchName + } + + f.TreePath = strings.Trim(f.TreePath, " /") + treeNames, treePaths := getParentTreeFields(f.TreePath) + + c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath) + c.Data["TreePath"] = f.TreePath + c.Data["TreeNames"] = treeNames + c.Data["TreePaths"] = treePaths + c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName + c.Data["FileContent"] = f.Content + c.Data["commit_summary"] = f.CommitSummary + c.Data["commit_message"] = f.CommitMessage + c.Data["commit_choice"] = f.CommitChoice + c.Data["new_branch_name"] = branchName + c.Data["last_commit"] = f.LastCommit + c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",") + c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") + c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",") + + if c.HasError() { + c.Success(EDIT_FILE) + return + } + + if len(f.TreePath) == 0 { + c.FormErr("TreePath") + c.RenderWithErr(c.Tr("repo.editor.filename_cannot_be_empty"), EDIT_FILE, &f) + return + } + + if oldBranchName != branchName { + if _, err := c.Repo.Repository.GetBranch(branchName); err == nil { + c.FormErr("NewBranchName") + c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), EDIT_FILE, &f) + return + } + } + + var newTreePath string + for index, part := range treeNames { + newTreePath = path.Join(newTreePath, part) + entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath) + if err != nil { + if git.IsErrNotExist(err) { + // Means there is no item with that name, so we're good + break + } + + c.ServerError("Repo.Commit.GetTreeEntryByPath", err) + return + } + if index != len(treeNames)-1 { + if !entry.IsDir() { + c.FormErr("TreePath") + c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), EDIT_FILE, &f) + return + } + } else { + if entry.IsLink() { + c.FormErr("TreePath") + c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), EDIT_FILE, &f) + return + } else if entry.IsDir() { + c.FormErr("TreePath") + c.RenderWithErr(c.Tr("repo.editor.filename_is_a_directory", part), EDIT_FILE, &f) + return + } + } + } + + if !isNewFile { + _, err := c.Repo.Commit.GetTreeEntryByPath(oldTreePath) + if err != nil { + if git.IsErrNotExist(err) { + c.FormErr("TreePath") + c.RenderWithErr(c.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), EDIT_FILE, &f) + } else { + c.ServerError("GetTreeEntryByPath", err) + } + return + } + if lastCommit != c.Repo.CommitID { + files, err := c.Repo.Commit.GetFilesChangedSinceCommit(lastCommit) + if err != nil { + c.ServerError("GetFilesChangedSinceCommit", err) + return + } + + for _, file := range files { + if file == f.TreePath { + c.RenderWithErr(c.Tr("repo.editor.file_changed_while_editing", c.Repo.RepoLink+"/compare/"+lastCommit+"..."+c.Repo.CommitID), EDIT_FILE, &f) + return + } + } + } + } + + if oldTreePath != f.TreePath { + // We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber. + entry, err := c.Repo.Commit.GetTreeEntryByPath(f.TreePath) + if err != nil { + if !git.IsErrNotExist(err) { + c.ServerError("GetTreeEntryByPath", err) + return + } + } + if entry != nil { + c.FormErr("TreePath") + c.RenderWithErr(c.Tr("repo.editor.file_already_exists", f.TreePath), EDIT_FILE, &f) + return + } + } + + message := strings.TrimSpace(f.CommitSummary) + if len(message) == 0 { + if isNewFile { + message = c.Tr("repo.editor.add", f.TreePath) + } else { + message = c.Tr("repo.editor.update", f.TreePath) + } + } + + f.CommitMessage = strings.TrimSpace(f.CommitMessage) + if len(f.CommitMessage) > 0 { + message += "\n\n" + f.CommitMessage + } + + if err := c.Repo.Repository.UpdateRepoFile(c.User, models.UpdateRepoFileOptions{ + LastCommitID: lastCommit, + OldBranch: oldBranchName, + NewBranch: branchName, + OldTreeName: oldTreePath, + NewTreeName: f.TreePath, + Message: message, + Content: strings.Replace(f.Content, "\r", "", -1), + IsNewFile: isNewFile, + }); err != nil { + c.FormErr("TreePath") + c.RenderWithErr(c.Tr("repo.editor.fail_to_update_file", f.TreePath, err), EDIT_FILE, &f) + return + } + + if f.IsNewBrnach() && c.Repo.PullRequest.Allowed { + c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName)) + } else { + c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + template.EscapePound(f.TreePath)) + } +} + +func EditFilePost(c *context.Context, f form.EditRepoFile) { + editFilePost(c, f, false) +} + +func NewFilePost(c *context.Context, f form.EditRepoFile) { + editFilePost(c, f, true) +} + +func DiffPreviewPost(c *context.Context, f form.EditPreviewDiff) { + treePath := c.Repo.TreePath + + entry, err := c.Repo.Commit.GetTreeEntryByPath(treePath) + if err != nil { + c.Error(500, "GetTreeEntryByPath: "+err.Error()) + return + } else if entry.IsDir() { + c.Error(422) + return + } + + diff, err := c.Repo.Repository.GetDiffPreview(c.Repo.BranchName, treePath, f.Content) + if err != nil { + c.Error(500, "GetDiffPreview: "+err.Error()) + return + } + + if diff.NumFiles() == 0 { + c.PlainText(200, []byte(c.Tr("repo.editor.no_changes_to_show"))) + return + } + c.Data["File"] = diff.Files[0] + + c.HTML(200, EDIT_DIFF_PREVIEW) +} + +func DeleteFile(c *context.Context) { + c.Data["PageIsDelete"] = true + c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName + c.Data["TreePath"] = c.Repo.TreePath + c.Data["commit_summary"] = "" + c.Data["commit_message"] = "" + c.Data["commit_choice"] = "direct" + c.Data["new_branch_name"] = "" + c.HTML(200, DELETE_FILE) +} + +func DeleteFilePost(c *context.Context, f form.DeleteRepoFile) { + c.Data["PageIsDelete"] = true + c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName + c.Data["TreePath"] = c.Repo.TreePath + + oldBranchName := c.Repo.BranchName + branchName := oldBranchName + + if f.IsNewBrnach() { + branchName = f.NewBranchName + } + c.Data["commit_summary"] = f.CommitSummary + c.Data["commit_message"] = f.CommitMessage + c.Data["commit_choice"] = f.CommitChoice + c.Data["new_branch_name"] = branchName + + if c.HasError() { + c.HTML(200, DELETE_FILE) + return + } + + if oldBranchName != branchName { + if _, err := c.Repo.Repository.GetBranch(branchName); err == nil { + c.Data["Err_NewBranchName"] = true + c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), DELETE_FILE, &f) + return + } + } + + message := strings.TrimSpace(f.CommitSummary) + if len(message) == 0 { + message = c.Tr("repo.editor.delete", c.Repo.TreePath) + } + + f.CommitMessage = strings.TrimSpace(f.CommitMessage) + if len(f.CommitMessage) > 0 { + message += "\n\n" + f.CommitMessage + } + + if err := c.Repo.Repository.DeleteRepoFile(c.User, models.DeleteRepoFileOptions{ + LastCommitID: c.Repo.CommitID, + OldBranch: oldBranchName, + NewBranch: branchName, + TreePath: c.Repo.TreePath, + Message: message, + }); err != nil { + c.Handle(500, "DeleteRepoFile", err) + return + } + + if f.IsNewBrnach() && c.Repo.PullRequest.Allowed { + c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName)) + } else { + c.Flash.Success(c.Tr("repo.editor.file_delete_success", c.Repo.TreePath)) + c.Redirect(c.Repo.RepoLink + "/src/" + branchName) + } +} + +func renderUploadSettings(c *context.Context) { + c.Data["RequireDropzone"] = true + c.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",") + c.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize + c.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles +} + +func UploadFile(c *context.Context) { + c.Data["PageIsUpload"] = true + renderUploadSettings(c) + + treeNames, treePaths := getParentTreeFields(c.Repo.TreePath) + if len(treeNames) == 0 { + // We must at least have one element for user to input. + treeNames = []string{""} + } + + c.Data["TreeNames"] = treeNames + c.Data["TreePaths"] = treePaths + c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName + c.Data["commit_summary"] = "" + c.Data["commit_message"] = "" + c.Data["commit_choice"] = "direct" + c.Data["new_branch_name"] = "" + + c.HTML(200, UPLOAD_FILE) +} + +func UploadFilePost(c *context.Context, f form.UploadRepoFile) { + c.Data["PageIsUpload"] = true + renderUploadSettings(c) + + oldBranchName := c.Repo.BranchName + branchName := oldBranchName + + if f.IsNewBrnach() { + branchName = f.NewBranchName + } + + f.TreePath = strings.Trim(f.TreePath, " /") + treeNames, treePaths := getParentTreeFields(f.TreePath) + if len(treeNames) == 0 { + // We must at least have one element for user to input. + treeNames = []string{""} + } + + c.Data["TreePath"] = f.TreePath + c.Data["TreeNames"] = treeNames + c.Data["TreePaths"] = treePaths + c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName + c.Data["commit_summary"] = f.CommitSummary + c.Data["commit_message"] = f.CommitMessage + c.Data["commit_choice"] = f.CommitChoice + c.Data["new_branch_name"] = branchName + + if c.HasError() { + c.HTML(200, UPLOAD_FILE) + return + } + + if oldBranchName != branchName { + if _, err := c.Repo.Repository.GetBranch(branchName); err == nil { + c.Data["Err_NewBranchName"] = true + c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), UPLOAD_FILE, &f) + return + } + } + + var newTreePath string + for _, part := range treeNames { + newTreePath = path.Join(newTreePath, part) + entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath) + if err != nil { + if git.IsErrNotExist(err) { + // Means there is no item with that name, so we're good + break + } + + c.Handle(500, "Repo.Commit.GetTreeEntryByPath", err) + return + } + + // User can only upload files to a directory. + if !entry.IsDir() { + c.Data["Err_TreePath"] = true + c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), UPLOAD_FILE, &f) + return + } + } + + message := strings.TrimSpace(f.CommitSummary) + if len(message) == 0 { + message = c.Tr("repo.editor.upload_files_to_dir", f.TreePath) + } + + f.CommitMessage = strings.TrimSpace(f.CommitMessage) + if len(f.CommitMessage) > 0 { + message += "\n\n" + f.CommitMessage + } + + if err := c.Repo.Repository.UploadRepoFiles(c.User, models.UploadRepoFileOptions{ + LastCommitID: c.Repo.CommitID, + OldBranch: oldBranchName, + NewBranch: branchName, + TreePath: f.TreePath, + Message: message, + Files: f.Files, + }); err != nil { + c.Data["Err_TreePath"] = true + c.RenderWithErr(c.Tr("repo.editor.unable_to_upload_files", f.TreePath, err), UPLOAD_FILE, &f) + return + } + + if f.IsNewBrnach() && c.Repo.PullRequest.Allowed { + c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName)) + } else { + c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath) + } +} + +func UploadFileToServer(c *context.Context) { + file, header, err := c.Req.FormFile("file") + if err != nil { + c.Error(500, fmt.Sprintf("FormFile: %v", err)) + return + } + defer file.Close() + + buf := make([]byte, 1024) + n, _ := file.Read(buf) + if n > 0 { + buf = buf[:n] + } + fileType := http.DetectContentType(buf) + + if len(setting.Repository.Upload.AllowedTypes) > 0 { + allowed := false + for _, t := range setting.Repository.Upload.AllowedTypes { + t := strings.Trim(t, " ") + if t == "*/*" || t == fileType { + allowed = true + break + } + } + + if !allowed { + c.Error(400, ErrFileTypeForbidden.Error()) + return + } + } + + upload, err := models.NewUpload(header.Filename, buf, file) + if err != nil { + c.Error(500, fmt.Sprintf("NewUpload: %v", err)) + return + } + + log.Trace("New file uploaded: %s", upload.UUID) + c.JSON(200, map[string]string{ + "uuid": upload.UUID, + }) +} + +func RemoveUploadFileFromServer(c *context.Context, f form.RemoveUploadFile) { + if len(f.File) == 0 { + c.Status(204) + return + } + + if err := models.DeleteUploadByUUID(f.File); err != nil { + c.Error(500, fmt.Sprintf("DeleteUploadByUUID: %v", err)) + return + } + + log.Trace("Upload file removed: %s", f.File) + c.Status(204) +} diff --git a/routes/repo/http.go b/routes/repo/http.go new file mode 100644 index 00000000..b8f519ba --- /dev/null +++ b/routes/repo/http.go @@ -0,0 +1,447 @@ +// Copyright 2017 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "bytes" + "compress/gzip" + "fmt" + "net/http" + "os" + "os/exec" + "path" + "regexp" + "strconv" + "strings" + "time" + + "github.com/Unknwon/com" + log "gopkg.in/clog.v1" + "gopkg.in/macaron.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + ENV_AUTH_USER_ID = "GOGS_AUTH_USER_ID" + ENV_AUTH_USER_NAME = "GOGS_AUTH_USER_NAME" + ENV_AUTH_USER_EMAIL = "GOGS_AUTH_USER_EMAIL" + ENV_REPO_OWNER_NAME = "GOGS_REPO_OWNER_NAME" + ENV_REPO_OWNER_SALT_MD5 = "GOGS_REPO_OWNER_SALT_MD5" + ENV_REPO_ID = "GOGS_REPO_ID" + ENV_REPO_NAME = "GOGS_REPO_NAME" + ENV_REPO_CUSTOM_HOOKS_PATH = "GOGS_REPO_CUSTOM_HOOKS_PATH" +) + +type HTTPContext struct { + *context.Context + OwnerName string + OwnerSalt string + RepoID int64 + RepoName string + AuthUser *models.User +} + +// askCredentials responses HTTP header and status which informs client to provide credentials. +func askCredentials(c *context.Context, status int, text string) { + c.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"") + c.HandleText(status, text) +} + +func HTTPContexter() macaron.Handler { + return func(c *context.Context) { + ownerName := c.Params(":username") + repoName := strings.TrimSuffix(c.Params(":reponame"), ".git") + repoName = strings.TrimSuffix(repoName, ".wiki") + + isPull := c.Query("service") == "git-upload-pack" || + strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") || + c.Req.Method == "GET" + + owner, err := models.GetUserByName(ownerName) + if err != nil { + c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) + return + } + + repo, err := models.GetRepositoryByName(owner.ID, repoName) + if err != nil { + c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err) + return + } + + // Authentication is not required for pulling from public repositories. + if isPull && !repo.IsPrivate && !setting.Service.RequireSignInView { + c.Map(&HTTPContext{ + Context: c, + }) + return + } + + // In case user requested a wrong URL and not intended to access Git objects. + action := c.Params("*") + if !strings.Contains(action, "git-") && + !strings.Contains(action, "info/") && + !strings.Contains(action, "HEAD") && + !strings.Contains(action, "objects/") { + c.NotFound() + return + } + + // Handle HTTP Basic Authentication + authHead := c.Req.Header.Get("Authorization") + if len(authHead) == 0 { + askCredentials(c, http.StatusUnauthorized, "") + return + } + + auths := strings.Fields(authHead) + if len(auths) != 2 || auths[0] != "Basic" { + askCredentials(c, http.StatusUnauthorized, "") + return + } + authUsername, authPassword, err := tool.BasicAuthDecode(auths[1]) + if err != nil { + askCredentials(c, http.StatusUnauthorized, "") + return + } + + authUser, err := models.UserSignIn(authUsername, authPassword) + if err != nil && !errors.IsUserNotExist(err) { + c.Handle(http.StatusInternalServerError, "UserSignIn", err) + return + } + + // If username and password combination failed, try again using username as a token. + if authUser == nil { + token, err := models.GetAccessTokenBySHA(authUsername) + if err != nil { + if models.IsErrAccessTokenEmpty(err) || models.IsErrAccessTokenNotExist(err) { + askCredentials(c, http.StatusUnauthorized, "") + } else { + c.Handle(http.StatusInternalServerError, "GetAccessTokenBySHA", err) + } + return + } + token.Updated = time.Now() + + authUser, err = models.GetUserByID(token.UID) + if err != nil { + // Once we found token, we're supposed to find its related user, + // thus any error is unexpected. + c.Handle(http.StatusInternalServerError, "GetUserByID", err) + return + } + } else if authUser.IsEnabledTwoFactor() { + askCredentials(c, http.StatusUnauthorized, `User with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password +Please create and use personal access token on user settings page`) + return + } + + log.Trace("HTTPGit - Authenticated user: %s", authUser.Name) + + mode := models.ACCESS_MODE_WRITE + if isPull { + mode = models.ACCESS_MODE_READ + } + has, err := models.HasAccess(authUser.ID, repo, mode) + if err != nil { + c.Handle(http.StatusInternalServerError, "HasAccess", err) + return + } else if !has { + askCredentials(c, http.StatusForbidden, "User permission denied") + return + } + + if !isPull && repo.IsMirror { + c.HandleText(http.StatusForbidden, "Mirror repository is read-only") + return + } + + c.Map(&HTTPContext{ + Context: c, + OwnerName: ownerName, + OwnerSalt: owner.Salt, + RepoID: repo.ID, + RepoName: repoName, + AuthUser: authUser, + }) + } +} + +type serviceHandler struct { + w http.ResponseWriter + r *http.Request + dir string + file string + + authUser *models.User + ownerName string + ownerSalt string + repoID int64 + repoName string +} + +func (h *serviceHandler) setHeaderNoCache() { + h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT") + h.w.Header().Set("Pragma", "no-cache") + h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") +} + +func (h *serviceHandler) setHeaderCacheForever() { + now := time.Now().Unix() + expires := now + 31536000 + h.w.Header().Set("Date", fmt.Sprintf("%d", now)) + h.w.Header().Set("Expires", fmt.Sprintf("%d", expires)) + h.w.Header().Set("Cache-Control", "public, max-age=31536000") +} + +func (h *serviceHandler) sendFile(contentType string) { + reqFile := path.Join(h.dir, h.file) + fi, err := os.Stat(reqFile) + if os.IsNotExist(err) { + h.w.WriteHeader(http.StatusNotFound) + return + } + + h.w.Header().Set("Content-Type", contentType) + h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) + h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat)) + http.ServeFile(h.w, h.r, reqFile) +} + +type ComposeHookEnvsOptions struct { + AuthUser *models.User + OwnerName string + OwnerSalt string + RepoID int64 + RepoName string + RepoPath string +} + +func ComposeHookEnvs(opts ComposeHookEnvsOptions) []string { + envs := []string{ + "SSH_ORIGINAL_COMMAND=1", + ENV_AUTH_USER_ID + "=" + com.ToStr(opts.AuthUser.ID), + ENV_AUTH_USER_NAME + "=" + opts.AuthUser.Name, + ENV_AUTH_USER_EMAIL + "=" + opts.AuthUser.Email, + ENV_REPO_OWNER_NAME + "=" + opts.OwnerName, + ENV_REPO_OWNER_SALT_MD5 + "=" + tool.MD5(opts.OwnerSalt), + ENV_REPO_ID + "=" + com.ToStr(opts.RepoID), + ENV_REPO_NAME + "=" + opts.RepoName, + ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(opts.RepoPath, "custom_hooks"), + } + return envs +} + +func serviceRPC(h serviceHandler, service string) { + defer h.r.Body.Close() + + if h.r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", service) { + h.w.WriteHeader(http.StatusUnauthorized) + return + } + h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service)) + + var ( + reqBody = h.r.Body + err error + ) + + // Handle GZIP + if h.r.Header.Get("Content-Encoding") == "gzip" { + reqBody, err = gzip.NewReader(reqBody) + if err != nil { + log.Error(2, "HTTP.Get: fail to create gzip reader: %v", err) + h.w.WriteHeader(http.StatusInternalServerError) + return + } + } + + var stderr bytes.Buffer + cmd := exec.Command("git", service, "--stateless-rpc", h.dir) + if service == "receive-pack" { + cmd.Env = append(os.Environ(), ComposeHookEnvs(ComposeHookEnvsOptions{ + AuthUser: h.authUser, + OwnerName: h.ownerName, + OwnerSalt: h.ownerSalt, + RepoID: h.repoID, + RepoName: h.repoName, + RepoPath: h.dir, + })...) + } + cmd.Dir = h.dir + cmd.Stdout = h.w + cmd.Stderr = &stderr + cmd.Stdin = reqBody + if err = cmd.Run(); err != nil { + log.Error(2, "HTTP.serviceRPC: fail to serve RPC '%s': %v - %s", service, err, stderr) + h.w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func serviceUploadPack(h serviceHandler) { + serviceRPC(h, "upload-pack") +} + +func serviceReceivePack(h serviceHandler) { + serviceRPC(h, "receive-pack") +} + +func getServiceType(r *http.Request) string { + serviceType := r.FormValue("service") + if !strings.HasPrefix(serviceType, "git-") { + return "" + } + return strings.TrimPrefix(serviceType, "git-") +} + +// FIXME: use process module +func gitCommand(dir string, args ...string) []byte { + cmd := exec.Command("git", args...) + cmd.Dir = dir + out, err := cmd.Output() + if err != nil { + log.Error(2, fmt.Sprintf("Git: %v - %s", err, out)) + } + return out +} + +func updateServerInfo(dir string) []byte { + return gitCommand(dir, "update-server-info") +} + +func packetWrite(str string) []byte { + s := strconv.FormatInt(int64(len(str)+4), 16) + if len(s)%4 != 0 { + s = strings.Repeat("0", 4-len(s)%4) + s + } + return []byte(s + str) +} + +func getInfoRefs(h serviceHandler) { + h.setHeaderNoCache() + service := getServiceType(h.r) + if service != "upload-pack" && service != "receive-pack" { + updateServerInfo(h.dir) + h.sendFile("text/plain; charset=utf-8") + return + } + + refs := gitCommand(h.dir, service, "--stateless-rpc", "--advertise-refs", ".") + h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service)) + h.w.WriteHeader(http.StatusOK) + h.w.Write(packetWrite("# service=git-" + service + "\n")) + h.w.Write([]byte("0000")) + h.w.Write(refs) +} + +func getTextFile(h serviceHandler) { + h.setHeaderNoCache() + h.sendFile("text/plain") +} + +func getInfoPacks(h serviceHandler) { + h.setHeaderCacheForever() + h.sendFile("text/plain; charset=utf-8") +} + +func getLooseObject(h serviceHandler) { + h.setHeaderCacheForever() + h.sendFile("application/x-git-loose-object") +} + +func getPackFile(h serviceHandler) { + h.setHeaderCacheForever() + h.sendFile("application/x-git-packed-objects") +} + +func getIdxFile(h serviceHandler) { + h.setHeaderCacheForever() + h.sendFile("application/x-git-packed-objects-toc") +} + +var routes = []struct { + reg *regexp.Regexp + method string + handler func(serviceHandler) +}{ + {regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack}, + {regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack}, + {regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs}, + {regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile}, + {regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile}, + {regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile}, + {regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks}, + {regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile}, + {regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject}, + {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile}, + {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile}, +} + +func getGitRepoPath(dir string) (string, error) { + if !strings.HasSuffix(dir, ".git") { + dir += ".git" + } + + filename := path.Join(setting.RepoRootPath, dir) + if _, err := os.Stat(filename); os.IsNotExist(err) { + return "", err + } + + return filename, nil +} + +func HTTP(c *HTTPContext) { + for _, route := range routes { + reqPath := strings.ToLower(c.Req.URL.Path) + m := route.reg.FindStringSubmatch(reqPath) + if m == nil { + continue + } + + // We perform check here because routes matched in cmd/web.go is wider than needed, + // but we only want to output this message only if user is really trying to access + // Git HTTP endpoints. + if setting.Repository.DisableHTTPGit { + c.HandleText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is not disabled") + return + } + + if route.method != c.Req.Method { + c.NotFound() + return + } + + file := strings.TrimPrefix(reqPath, m[1]+"/") + dir, err := getGitRepoPath(m[1]) + if err != nil { + log.Warn("HTTP.getGitRepoPath: %v", err) + c.NotFound() + return + } + + route.handler(serviceHandler{ + w: c.Resp, + r: c.Req.Request, + dir: dir, + file: file, + + authUser: c.AuthUser, + ownerName: c.OwnerName, + ownerSalt: c.OwnerSalt, + repoID: c.RepoID, + repoName: c.RepoName, + }) + return + } + + c.NotFound() +} diff --git a/routes/repo/issue.go b/routes/repo/issue.go new file mode 100644 index 00000000..8920bc32 --- /dev/null +++ b/routes/repo/issue.go @@ -0,0 +1,1263 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/Unknwon/com" + "github.com/Unknwon/paginater" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/markup" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + ISSUES = "repo/issue/list" + ISSUE_NEW = "repo/issue/new" + ISSUE_VIEW = "repo/issue/view" + + LABELS = "repo/issue/labels" + + MILESTONE = "repo/issue/milestones" + MILESTONE_NEW = "repo/issue/milestone_new" + MILESTONE_EDIT = "repo/issue/milestone_edit" + + ISSUE_TEMPLATE_KEY = "IssueTemplate" +) + +var ( + ErrFileTypeForbidden = errors.New("File type is not allowed") + ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded") + + IssueTemplateCandidates = []string{ + "ISSUE_TEMPLATE.md", + ".gogs/ISSUE_TEMPLATE.md", + ".github/ISSUE_TEMPLATE.md", + } +) + +func MustEnableIssues(c *context.Context) { + if !c.Repo.Repository.EnableIssues { + c.Handle(404, "MustEnableIssues", nil) + return + } + + if c.Repo.Repository.EnableExternalTracker { + c.Redirect(c.Repo.Repository.ExternalTrackerURL) + return + } +} + +func MustAllowPulls(c *context.Context) { + if !c.Repo.Repository.AllowsPulls() { + c.Handle(404, "MustAllowPulls", nil) + return + } + + // User can send pull request if owns a forked repository. + if c.IsLogged && c.User.HasForkedRepo(c.Repo.Repository.ID) { + c.Repo.PullRequest.Allowed = true + c.Repo.PullRequest.HeadInfo = c.User.Name + ":" + c.Repo.BranchName + } +} + +func RetrieveLabels(c *context.Context) { + labels, err := models.GetLabelsByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Handle(500, "RetrieveLabels.GetLabels", err) + return + } + for _, l := range labels { + l.CalOpenIssues() + } + c.Data["Labels"] = labels + c.Data["NumLabels"] = len(labels) +} + +func issues(c *context.Context, isPullList bool) { + if isPullList { + MustAllowPulls(c) + if c.Written() { + return + } + c.Data["Title"] = c.Tr("repo.pulls") + c.Data["PageIsPullList"] = true + + } else { + MustEnableIssues(c) + if c.Written() { + return + } + c.Data["Title"] = c.Tr("repo.issues") + c.Data["PageIsIssueList"] = true + } + + viewType := c.Query("type") + sortType := c.Query("sort") + types := []string{"assigned", "created_by", "mentioned"} + if !com.IsSliceContainsStr(types, viewType) { + viewType = "all" + } + + // Must sign in to see issues about you. + if viewType != "all" && !c.IsLogged { + c.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubURL+c.Req.RequestURI), 0, setting.AppSubURL) + c.Redirect(setting.AppSubURL + "/user/login") + return + } + + var ( + assigneeID = c.QueryInt64("assignee") + posterID int64 + ) + filterMode := models.FILTER_MODE_YOUR_REPOS + switch viewType { + case "assigned": + filterMode = models.FILTER_MODE_ASSIGN + assigneeID = c.User.ID + case "created_by": + filterMode = models.FILTER_MODE_CREATE + posterID = c.User.ID + case "mentioned": + filterMode = models.FILTER_MODE_MENTION + } + + var uid int64 = -1 + if c.IsLogged { + uid = c.User.ID + } + + repo := c.Repo.Repository + selectLabels := c.Query("labels") + milestoneID := c.QueryInt64("milestone") + isShowClosed := c.Query("state") == "closed" + issueStats := models.GetIssueStats(&models.IssueStatsOptions{ + RepoID: repo.ID, + UserID: uid, + Labels: selectLabels, + MilestoneID: milestoneID, + AssigneeID: assigneeID, + FilterMode: filterMode, + IsPull: isPullList, + }) + + page := c.QueryInt("page") + if page <= 1 { + page = 1 + } + + var total int + if !isShowClosed { + total = int(issueStats.OpenCount) + } else { + total = int(issueStats.ClosedCount) + } + pager := paginater.New(total, setting.UI.IssuePagingNum, page, 5) + c.Data["Page"] = pager + + issues, err := models.Issues(&models.IssuesOptions{ + UserID: uid, + AssigneeID: assigneeID, + RepoID: repo.ID, + PosterID: posterID, + MilestoneID: milestoneID, + Page: pager.Current(), + IsClosed: isShowClosed, + IsMention: filterMode == models.FILTER_MODE_MENTION, + IsPull: isPullList, + Labels: selectLabels, + SortType: sortType, + }) + if err != nil { + c.Handle(500, "Issues", err) + return + } + + // Get issue-user relations. + pairs, err := models.GetIssueUsers(repo.ID, posterID, isShowClosed) + if err != nil { + c.Handle(500, "GetIssueUsers", err) + return + } + + // Get posters. + for i := range issues { + if !c.IsLogged { + issues[i].IsRead = true + continue + } + + // Check read status. + idx := models.PairsContains(pairs, issues[i].ID, c.User.ID) + if idx > -1 { + issues[i].IsRead = pairs[idx].IsRead + } else { + issues[i].IsRead = true + } + } + c.Data["Issues"] = issues + + // Get milestones. + c.Data["Milestones"], err = models.GetMilestonesByRepoID(repo.ID) + if err != nil { + c.Handle(500, "GetAllRepoMilestones", err) + return + } + + // Get assignees. + c.Data["Assignees"], err = repo.GetAssignees() + if err != nil { + c.Handle(500, "GetAssignees", err) + return + } + + if viewType == "assigned" { + assigneeID = 0 // Reset ID to prevent unexpected selection of assignee. + } + + c.Data["IssueStats"] = issueStats + c.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64() + c.Data["ViewType"] = viewType + c.Data["SortType"] = sortType + c.Data["MilestoneID"] = milestoneID + c.Data["AssigneeID"] = assigneeID + c.Data["IsShowClosed"] = isShowClosed + if isShowClosed { + c.Data["State"] = "closed" + } else { + c.Data["State"] = "open" + } + + c.HTML(200, ISSUES) +} + +func Issues(c *context.Context) { + issues(c, false) +} + +func Pulls(c *context.Context) { + issues(c, true) +} + +func renderAttachmentSettings(c *context.Context) { + c.Data["RequireDropzone"] = true + c.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled + c.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes + c.Data["AttachmentMaxSize"] = setting.AttachmentMaxSize + c.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles +} + +func RetrieveRepoMilestonesAndAssignees(c *context.Context, repo *models.Repository) { + var err error + c.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false) + if err != nil { + c.Handle(500, "GetMilestones", err) + return + } + c.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true) + if err != nil { + c.Handle(500, "GetMilestones", err) + return + } + + c.Data["Assignees"], err = repo.GetAssignees() + if err != nil { + c.Handle(500, "GetAssignees", err) + return + } +} + +func RetrieveRepoMetas(c *context.Context, repo *models.Repository) []*models.Label { + if !c.Repo.IsWriter() { + return nil + } + + labels, err := models.GetLabelsByRepoID(repo.ID) + if err != nil { + c.Handle(500, "GetLabelsByRepoID", err) + return nil + } + c.Data["Labels"] = labels + + RetrieveRepoMilestonesAndAssignees(c, repo) + if c.Written() { + return nil + } + + return labels +} + +func getFileContentFromDefaultBranch(c *context.Context, filename string) (string, bool) { + var r io.Reader + var bytes []byte + + if c.Repo.Commit == nil { + var err error + c.Repo.Commit, err = c.Repo.GitRepo.GetBranchCommit(c.Repo.Repository.DefaultBranch) + if err != nil { + return "", false + } + } + + entry, err := c.Repo.Commit.GetTreeEntryByPath(filename) + if err != nil { + return "", false + } + r, err = entry.Blob().Data() + if err != nil { + return "", false + } + bytes, err = ioutil.ReadAll(r) + if err != nil { + return "", false + } + return string(bytes), true +} + +func setTemplateIfExists(c *context.Context, ctxDataKey string, possibleFiles []string) { + for _, filename := range possibleFiles { + content, found := getFileContentFromDefaultBranch(c, filename) + if found { + c.Data[ctxDataKey] = content + return + } + } +} + +func NewIssue(c *context.Context) { + c.Data["Title"] = c.Tr("repo.issues.new") + c.Data["PageIsIssueList"] = true + c.Data["RequireHighlightJS"] = true + c.Data["RequireSimpleMDE"] = true + setTemplateIfExists(c, ISSUE_TEMPLATE_KEY, IssueTemplateCandidates) + renderAttachmentSettings(c) + + RetrieveRepoMetas(c, c.Repo.Repository) + if c.Written() { + return + } + + c.HTML(200, ISSUE_NEW) +} + +func ValidateRepoMetas(c *context.Context, f form.NewIssue) ([]int64, int64, int64) { + var ( + repo = c.Repo.Repository + err error + ) + + labels := RetrieveRepoMetas(c, c.Repo.Repository) + if c.Written() { + return nil, 0, 0 + } + + if !c.Repo.IsWriter() { + return nil, 0, 0 + } + + // Check labels. + labelIDs := tool.StringsToInt64s(strings.Split(f.LabelIDs, ",")) + labelIDMark := tool.Int64sToMap(labelIDs) + hasSelected := false + for i := range labels { + if labelIDMark[labels[i].ID] { + labels[i].IsChecked = true + hasSelected = true + } + } + c.Data["HasSelectedLabel"] = hasSelected + c.Data["label_ids"] = f.LabelIDs + c.Data["Labels"] = labels + + // Check milestone. + milestoneID := f.MilestoneID + if milestoneID > 0 { + c.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID) + if err != nil { + c.Handle(500, "GetMilestoneByID", err) + return nil, 0, 0 + } + c.Data["milestone_id"] = milestoneID + } + + // Check assignee. + assigneeID := f.AssigneeID + if assigneeID > 0 { + c.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID) + if err != nil { + c.Handle(500, "GetAssigneeByID", err) + return nil, 0, 0 + } + c.Data["assignee_id"] = assigneeID + } + + return labelIDs, milestoneID, assigneeID +} + +func NewIssuePost(c *context.Context, f form.NewIssue) { + c.Data["Title"] = c.Tr("repo.issues.new") + c.Data["PageIsIssueList"] = true + c.Data["RequireHighlightJS"] = true + c.Data["RequireSimpleMDE"] = true + renderAttachmentSettings(c) + + labelIDs, milestoneID, assigneeID := ValidateRepoMetas(c, f) + if c.Written() { + return + } + + if c.HasError() { + c.HTML(200, ISSUE_NEW) + return + } + + var attachments []string + if setting.AttachmentEnabled { + attachments = f.Files + } + + issue := &models.Issue{ + RepoID: c.Repo.Repository.ID, + Title: f.Title, + PosterID: c.User.ID, + Poster: c.User, + MilestoneID: milestoneID, + AssigneeID: assigneeID, + Content: f.Content, + } + if err := models.NewIssue(c.Repo.Repository, issue, labelIDs, attachments); err != nil { + c.Handle(500, "NewIssue", err) + return + } + + log.Trace("Issue created: %d/%d", c.Repo.Repository.ID, issue.ID) + c.Redirect(c.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) +} + +func uploadAttachment(c *context.Context, allowedTypes []string) { + file, header, err := c.Req.FormFile("file") + if err != nil { + c.Error(500, fmt.Sprintf("FormFile: %v", err)) + return + } + defer file.Close() + + buf := make([]byte, 1024) + n, _ := file.Read(buf) + if n > 0 { + buf = buf[:n] + } + fileType := http.DetectContentType(buf) + + allowed := false + for _, t := range allowedTypes { + t := strings.Trim(t, " ") + if t == "*/*" || t == fileType { + allowed = true + break + } + } + + if !allowed { + c.Error(400, ErrFileTypeForbidden.Error()) + return + } + + attach, err := models.NewAttachment(header.Filename, buf, file) + if err != nil { + c.Error(500, fmt.Sprintf("NewAttachment: %v", err)) + return + } + + log.Trace("New attachment uploaded: %s", attach.UUID) + c.JSON(200, map[string]string{ + "uuid": attach.UUID, + }) +} + +func UploadIssueAttachment(c *context.Context) { + if !setting.AttachmentEnabled { + c.NotFound() + return + } + + uploadAttachment(c, strings.Split(setting.AttachmentAllowedTypes, ",")) +} + +func viewIssue(c *context.Context, isPullList bool) { + c.Data["RequireHighlightJS"] = true + c.Data["RequireDropzone"] = true + renderAttachmentSettings(c) + + index := c.ParamsInt64(":index") + if index <= 0 { + c.NotFound() + return + } + + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, index) + if err != nil { + c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err) + return + } + c.Data["Title"] = issue.Title + + // Make sure type and URL matches. + if !isPullList && issue.IsPull { + c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + return + } else if isPullList && !issue.IsPull { + c.Redirect(c.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) + return + } + + if issue.IsPull { + MustAllowPulls(c) + if c.Written() { + return + } + c.Data["PageIsPullList"] = true + c.Data["PageIsPullConversation"] = true + } else { + MustEnableIssues(c) + if c.Written() { + return + } + c.Data["PageIsIssueList"] = true + } + + issue.RenderedContent = string(markup.Markdown(issue.Content, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) + + repo := c.Repo.Repository + + // Get more information if it's a pull request. + if issue.IsPull { + if issue.PullRequest.HasMerged { + c.Data["DisableStatusChange"] = issue.PullRequest.HasMerged + PrepareMergedViewPullInfo(c, issue) + } else { + PrepareViewPullInfo(c, issue) + } + if c.Written() { + return + } + } + + // Metas. + // Check labels. + labelIDMark := make(map[int64]bool) + for i := range issue.Labels { + labelIDMark[issue.Labels[i].ID] = true + } + labels, err := models.GetLabelsByRepoID(repo.ID) + if err != nil { + c.Handle(500, "GetLabelsByRepoID", err) + return + } + hasSelected := false + for i := range labels { + if labelIDMark[labels[i].ID] { + labels[i].IsChecked = true + hasSelected = true + } + } + c.Data["HasSelectedLabel"] = hasSelected + c.Data["Labels"] = labels + + // Check milestone and assignee. + if c.Repo.IsWriter() { + RetrieveRepoMilestonesAndAssignees(c, repo) + if c.Written() { + return + } + } + + if c.IsLogged { + // Update issue-user. + if err = issue.ReadBy(c.User.ID); err != nil { + c.Handle(500, "ReadBy", err) + return + } + } + + var ( + tag models.CommentTag + ok bool + marked = make(map[int64]models.CommentTag) + comment *models.Comment + participants = make([]*models.User, 1, 10) + ) + + // Render comments and and fetch participants. + participants[0] = issue.Poster + for _, comment = range issue.Comments { + if comment.Type == models.COMMENT_TYPE_COMMENT { + comment.RenderedContent = string(markup.Markdown(comment.Content, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) + + // Check tag. + tag, ok = marked[comment.PosterID] + if ok { + comment.ShowTag = tag + continue + } + + if repo.IsOwnedBy(comment.PosterID) || + (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) { + comment.ShowTag = models.COMMENT_TAG_OWNER + } else if comment.Poster.IsWriterOfRepo(repo) { + comment.ShowTag = models.COMMENT_TAG_WRITER + } else if comment.PosterID == issue.PosterID { + comment.ShowTag = models.COMMENT_TAG_POSTER + } + + marked[comment.PosterID] = comment.ShowTag + + isAdded := false + for j := range participants { + if comment.Poster == participants[j] { + isAdded = true + break + } + } + if !isAdded && !issue.IsPoster(comment.Poster.ID) { + participants = append(participants, comment.Poster) + } + } + } + + if issue.IsPull && issue.PullRequest.HasMerged { + pull := issue.PullRequest + c.Data["IsPullBranchDeletable"] = pull.BaseRepoID == pull.HeadRepoID && + c.Repo.IsWriter() && c.Repo.GitRepo.IsBranchExist(pull.HeadBranch) + + deleteBranchUrl := c.Repo.RepoLink + "/branches/delete/" + pull.HeadBranch + c.Data["DeleteBranchLink"] = fmt.Sprintf("%s?commit=%s&redirect_to=%s", deleteBranchUrl, pull.MergedCommitID, c.Data["Link"]) + } + + c.Data["Participants"] = participants + c.Data["NumParticipants"] = len(participants) + c.Data["Issue"] = issue + c.Data["IsIssueOwner"] = c.Repo.IsWriter() || (c.IsLogged && issue.IsPoster(c.User.ID)) + c.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + c.Data["Link"].(string) + c.HTML(200, ISSUE_VIEW) +} + +func ViewIssue(c *context.Context) { + viewIssue(c, false) +} + +func ViewPull(c *context.Context) { + viewIssue(c, true) +} + +func getActionIssue(c *context.Context) *models.Issue { + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err) + return nil + } + + // Prevent guests accessing pull requests + if !c.Repo.HasAccess() && issue.IsPull { + c.NotFound() + return nil + } + + return issue +} + +func UpdateIssueTitle(c *context.Context) { + issue := getActionIssue(c) + if c.Written() { + return + } + + if !c.IsLogged || (!issue.IsPoster(c.User.ID) && !c.Repo.IsWriter()) { + c.Error(403) + return + } + + title := c.QueryTrim("title") + if len(title) == 0 { + c.Error(204) + return + } + + if err := issue.ChangeTitle(c.User, title); err != nil { + c.Handle(500, "ChangeTitle", err) + return + } + + c.JSON(200, map[string]interface{}{ + "title": issue.Title, + }) +} + +func UpdateIssueContent(c *context.Context) { + issue := getActionIssue(c) + if c.Written() { + return + } + + if !c.IsLogged || (c.User.ID != issue.PosterID && !c.Repo.IsWriter()) { + c.Error(403) + return + } + + content := c.Query("content") + if err := issue.ChangeContent(c.User, content); err != nil { + c.Handle(500, "ChangeContent", err) + return + } + + c.JSON(200, map[string]string{ + "content": string(markup.Markdown(issue.Content, c.Query("context"), c.Repo.Repository.ComposeMetas())), + }) +} + +func UpdateIssueLabel(c *context.Context) { + issue := getActionIssue(c) + if c.Written() { + return + } + + if c.Query("action") == "clear" { + if err := issue.ClearLabels(c.User); err != nil { + c.Handle(500, "ClearLabels", err) + return + } + } else { + isAttach := c.Query("action") == "attach" + label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")) + if err != nil { + if models.IsErrLabelNotExist(err) { + c.Error(404, "GetLabelByID") + } else { + c.Handle(500, "GetLabelByID", err) + } + return + } + + if isAttach && !issue.HasLabel(label.ID) { + if err = issue.AddLabel(c.User, label); err != nil { + c.Handle(500, "AddLabel", err) + return + } + } else if !isAttach && issue.HasLabel(label.ID) { + if err = issue.RemoveLabel(c.User, label); err != nil { + c.Handle(500, "RemoveLabel", err) + return + } + } + } + + c.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + +func UpdateIssueMilestone(c *context.Context) { + issue := getActionIssue(c) + if c.Written() { + return + } + + oldMilestoneID := issue.MilestoneID + milestoneID := c.QueryInt64("id") + if oldMilestoneID == milestoneID { + c.JSON(200, map[string]interface{}{ + "ok": true, + }) + return + } + + // Not check for invalid milestone id and give responsibility to owners. + issue.MilestoneID = milestoneID + if err := models.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil { + c.Handle(500, "ChangeMilestoneAssign", err) + return + } + + c.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + +func UpdateIssueAssignee(c *context.Context) { + issue := getActionIssue(c) + if c.Written() { + return + } + + assigneeID := c.QueryInt64("id") + if issue.AssigneeID == assigneeID { + c.JSON(200, map[string]interface{}{ + "ok": true, + }) + return + } + + if err := issue.ChangeAssignee(c.User, assigneeID); err != nil { + c.Handle(500, "ChangeAssignee", err) + return + } + + c.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + +func NewComment(c *context.Context, f form.CreateComment) { + issue := getActionIssue(c) + if c.Written() { + return + } + + var attachments []string + if setting.AttachmentEnabled { + attachments = f.Files + } + + if c.HasError() { + c.Flash.Error(c.Data["ErrorMsg"].(string)) + c.Redirect(fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issue.Index)) + return + } + + var err error + var comment *models.Comment + defer func() { + // Check if issue admin/poster changes the status of issue. + if (c.Repo.IsWriter() || (c.IsLogged && issue.IsPoster(c.User.ID))) && + (f.Status == "reopen" || f.Status == "close") && + !(issue.IsPull && issue.PullRequest.HasMerged) { + + // Duplication and conflict check should apply to reopen pull request. + var pr *models.PullRequest + + if f.Status == "reopen" && issue.IsPull { + pull := issue.PullRequest + pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch) + if err != nil { + if !models.IsErrPullRequestNotExist(err) { + c.ServerError("GetUnmergedPullRequest", err) + return + } + } + + // Regenerate patch and test conflict. + if pr == nil { + if err = issue.PullRequest.UpdatePatch(); err != nil { + c.ServerError("UpdatePatch", err) + return + } + + issue.PullRequest.AddToTaskQueue() + } + } + + if pr != nil { + c.Flash.Info(c.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index)) + } else { + if err = issue.ChangeStatus(c.User, c.Repo.Repository, f.Status == "close"); err != nil { + log.Error(2, "ChangeStatus: %v", err) + } else { + log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed) + } + } + } + + // Redirect to comment hashtag if there is any actual content. + typeName := "issues" + if issue.IsPull { + typeName = "pulls" + } + if comment != nil { + c.Redirect(fmt.Sprintf("%s/%s/%d#%s", c.Repo.RepoLink, typeName, issue.Index, comment.HashTag())) + } else { + c.Redirect(fmt.Sprintf("%s/%s/%d", c.Repo.RepoLink, typeName, issue.Index)) + } + }() + + // Fix #321: Allow empty comments, as long as we have attachments. + if len(f.Content) == 0 && len(attachments) == 0 { + return + } + + comment, err = models.CreateIssueComment(c.User, c.Repo.Repository, issue, f.Content, attachments) + if err != nil { + c.ServerError("CreateIssueComment", err) + return + } + + log.Trace("Comment created: %d/%d/%d", c.Repo.Repository.ID, issue.ID, comment.ID) +} + +func UpdateCommentContent(c *context.Context) { + comment, err := models.GetCommentByID(c.ParamsInt64(":id")) + if err != nil { + c.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err) + return + } + + if c.UserID() != comment.PosterID && !c.Repo.IsAdmin() { + c.Error(404) + return + } else if comment.Type != models.COMMENT_TYPE_COMMENT { + c.Error(204) + return + } + + oldContent := comment.Content + comment.Content = c.Query("content") + if len(comment.Content) == 0 { + c.JSON(200, map[string]interface{}{ + "content": "", + }) + return + } + if err = models.UpdateComment(c.User, comment, oldContent); err != nil { + c.Handle(500, "UpdateComment", err) + return + } + + c.JSON(200, map[string]string{ + "content": string(markup.Markdown(comment.Content, c.Query("context"), c.Repo.Repository.ComposeMetas())), + }) +} + +func DeleteComment(c *context.Context) { + comment, err := models.GetCommentByID(c.ParamsInt64(":id")) + if err != nil { + c.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err) + return + } + + if c.UserID() != comment.PosterID && !c.Repo.IsAdmin() { + c.Error(404) + return + } else if comment.Type != models.COMMENT_TYPE_COMMENT { + c.Error(204) + return + } + + if err = models.DeleteCommentByID(c.User, comment.ID); err != nil { + c.Handle(500, "DeleteCommentByID", err) + return + } + + c.Status(200) +} + +func Labels(c *context.Context) { + c.Data["Title"] = c.Tr("repo.labels") + c.Data["PageIsIssueList"] = true + c.Data["PageIsLabels"] = true + c.Data["RequireMinicolors"] = true + c.Data["LabelTemplates"] = models.LabelTemplates + c.HTML(200, LABELS) +} + +func InitializeLabels(c *context.Context, f form.InitializeLabels) { + if c.HasError() { + c.Redirect(c.Repo.RepoLink + "/labels") + return + } + list, err := models.GetLabelTemplateFile(f.TemplateName) + if err != nil { + c.Flash.Error(c.Tr("repo.issues.label_templates.fail_to_load_file", f.TemplateName, err)) + c.Redirect(c.Repo.RepoLink + "/labels") + return + } + + labels := make([]*models.Label, len(list)) + for i := 0; i < len(list); i++ { + labels[i] = &models.Label{ + RepoID: c.Repo.Repository.ID, + Name: list[i][0], + Color: list[i][1], + } + } + if err := models.NewLabels(labels...); err != nil { + c.Handle(500, "NewLabels", err) + return + } + c.Redirect(c.Repo.RepoLink + "/labels") +} + +func NewLabel(c *context.Context, f form.CreateLabel) { + c.Data["Title"] = c.Tr("repo.labels") + c.Data["PageIsLabels"] = true + + if c.HasError() { + c.Flash.Error(c.Data["ErrorMsg"].(string)) + c.Redirect(c.Repo.RepoLink + "/labels") + return + } + + l := &models.Label{ + RepoID: c.Repo.Repository.ID, + Name: f.Title, + Color: f.Color, + } + if err := models.NewLabels(l); err != nil { + c.Handle(500, "NewLabel", err) + return + } + c.Redirect(c.Repo.RepoLink + "/labels") +} + +func UpdateLabel(c *context.Context, f form.CreateLabel) { + l, err := models.GetLabelByID(f.ID) + if err != nil { + switch { + case models.IsErrLabelNotExist(err): + c.Error(404) + default: + c.Handle(500, "UpdateLabel", err) + } + return + } + + l.Name = f.Title + l.Color = f.Color + if err := models.UpdateLabel(l); err != nil { + c.Handle(500, "UpdateLabel", err) + return + } + c.Redirect(c.Repo.RepoLink + "/labels") +} + +func DeleteLabel(c *context.Context) { + if err := models.DeleteLabel(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteLabel: " + err.Error()) + } else { + c.Flash.Success(c.Tr("repo.issues.label_deletion_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Repo.RepoLink + "/labels", + }) + return +} + +func Milestones(c *context.Context) { + c.Data["Title"] = c.Tr("repo.milestones") + c.Data["PageIsIssueList"] = true + c.Data["PageIsMilestones"] = true + + isShowClosed := c.Query("state") == "closed" + openCount, closedCount := models.MilestoneStats(c.Repo.Repository.ID) + c.Data["OpenCount"] = openCount + c.Data["ClosedCount"] = closedCount + + page := c.QueryInt("page") + if page <= 1 { + page = 1 + } + + var total int + if !isShowClosed { + total = int(openCount) + } else { + total = int(closedCount) + } + c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5) + + miles, err := models.GetMilestones(c.Repo.Repository.ID, page, isShowClosed) + if err != nil { + c.Handle(500, "GetMilestones", err) + return + } + for _, m := range miles { + m.NumOpenIssues = int(m.CountIssues(false, false)) + m.NumClosedIssues = int(m.CountIssues(true, false)) + if m.NumOpenIssues+m.NumClosedIssues > 0 { + m.Completeness = m.NumClosedIssues * 100 / (m.NumOpenIssues + m.NumClosedIssues) + } + m.RenderedContent = string(markup.Markdown(m.Content, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) + } + c.Data["Milestones"] = miles + + if isShowClosed { + c.Data["State"] = "closed" + } else { + c.Data["State"] = "open" + } + + c.Data["IsShowClosed"] = isShowClosed + c.HTML(200, MILESTONE) +} + +func NewMilestone(c *context.Context) { + c.Data["Title"] = c.Tr("repo.milestones.new") + c.Data["PageIsIssueList"] = true + c.Data["PageIsMilestones"] = true + c.Data["RequireDatetimepicker"] = true + c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) + c.HTML(200, MILESTONE_NEW) +} + +func NewMilestonePost(c *context.Context, f form.CreateMilestone) { + c.Data["Title"] = c.Tr("repo.milestones.new") + c.Data["PageIsIssueList"] = true + c.Data["PageIsMilestones"] = true + c.Data["RequireDatetimepicker"] = true + c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) + + if c.HasError() { + c.HTML(200, MILESTONE_NEW) + return + } + + if len(f.Deadline) == 0 { + f.Deadline = "9999-12-31" + } + deadline, err := time.ParseInLocation("2006-01-02", f.Deadline, time.Local) + if err != nil { + c.Data["Err_Deadline"] = true + c.RenderWithErr(c.Tr("repo.milestones.invalid_due_date_format"), MILESTONE_NEW, &f) + return + } + + if err = models.NewMilestone(&models.Milestone{ + RepoID: c.Repo.Repository.ID, + Name: f.Title, + Content: f.Content, + Deadline: deadline, + }); err != nil { + c.Handle(500, "NewMilestone", err) + return + } + + c.Flash.Success(c.Tr("repo.milestones.create_success", f.Title)) + c.Redirect(c.Repo.RepoLink + "/milestones") +} + +func EditMilestone(c *context.Context) { + c.Data["Title"] = c.Tr("repo.milestones.edit") + c.Data["PageIsMilestones"] = true + c.Data["PageIsEditMilestone"] = true + c.Data["RequireDatetimepicker"] = true + c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) + + m, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if models.IsErrMilestoneNotExist(err) { + c.Handle(404, "", nil) + } else { + c.Handle(500, "GetMilestoneByRepoID", err) + } + return + } + c.Data["title"] = m.Name + c.Data["content"] = m.Content + if len(m.DeadlineString) > 0 { + c.Data["deadline"] = m.DeadlineString + } + c.HTML(200, MILESTONE_NEW) +} + +func EditMilestonePost(c *context.Context, f form.CreateMilestone) { + c.Data["Title"] = c.Tr("repo.milestones.edit") + c.Data["PageIsMilestones"] = true + c.Data["PageIsEditMilestone"] = true + c.Data["RequireDatetimepicker"] = true + c.Data["DateLang"] = setting.DateLang(c.Locale.Language()) + + if c.HasError() { + c.HTML(200, MILESTONE_NEW) + return + } + + if len(f.Deadline) == 0 { + f.Deadline = "9999-12-31" + } + deadline, err := time.ParseInLocation("2006-01-02", f.Deadline, time.Local) + if err != nil { + c.Data["Err_Deadline"] = true + c.RenderWithErr(c.Tr("repo.milestones.invalid_due_date_format"), MILESTONE_NEW, &f) + return + } + + m, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if models.IsErrMilestoneNotExist(err) { + c.Handle(404, "", nil) + } else { + c.Handle(500, "GetMilestoneByRepoID", err) + } + return + } + m.Name = f.Title + m.Content = f.Content + m.Deadline = deadline + if err = models.UpdateMilestone(m); err != nil { + c.Handle(500, "UpdateMilestone", err) + return + } + + c.Flash.Success(c.Tr("repo.milestones.edit_success", m.Name)) + c.Redirect(c.Repo.RepoLink + "/milestones") +} + +func ChangeMilestonStatus(c *context.Context) { + m, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + if models.IsErrMilestoneNotExist(err) { + c.Handle(404, "", err) + } else { + c.Handle(500, "GetMilestoneByRepoID", err) + } + return + } + + switch c.Params(":action") { + case "open": + if m.IsClosed { + if err = models.ChangeMilestoneStatus(m, false); err != nil { + c.Handle(500, "ChangeMilestoneStatus", err) + return + } + } + c.Redirect(c.Repo.RepoLink + "/milestones?state=open") + case "close": + if !m.IsClosed { + m.ClosedDate = time.Now() + if err = models.ChangeMilestoneStatus(m, true); err != nil { + c.Handle(500, "ChangeMilestoneStatus", err) + return + } + } + c.Redirect(c.Repo.RepoLink + "/milestones?state=closed") + default: + c.Redirect(c.Repo.RepoLink + "/milestones") + } +} + +func DeleteMilestone(c *context.Context) { + if err := models.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteMilestoneByRepoID: " + err.Error()) + } else { + c.Flash.Success(c.Tr("repo.milestones.deletion_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Repo.RepoLink + "/milestones", + }) +} diff --git a/routes/repo/pull.go b/routes/repo/pull.go new file mode 100644 index 00000000..73757280 --- /dev/null +++ b/routes/repo/pull.go @@ -0,0 +1,763 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "container/list" + "path" + "strings" + + "github.com/Unknwon/com" + log "gopkg.in/clog.v1" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + FORK = "repo/pulls/fork" + COMPARE_PULL = "repo/pulls/compare" + PULL_COMMITS = "repo/pulls/commits" + PULL_FILES = "repo/pulls/files" + + PULL_REQUEST_TEMPLATE_KEY = "PullRequestTemplate" +) + +var ( + PullRequestTemplateCandidates = []string{ + "PULL_REQUEST.md", + ".gogs/PULL_REQUEST.md", + ".github/PULL_REQUEST.md", + } +) + +func parseBaseRepository(c *context.Context) *models.Repository { + baseRepo, err := models.GetRepositoryByID(c.ParamsInt64(":repoid")) + if err != nil { + c.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err) + return nil + } + + if !baseRepo.CanBeForked() || !baseRepo.HasAccess(c.User.ID) { + c.NotFound() + return nil + } + + c.Data["repo_name"] = baseRepo.Name + c.Data["description"] = baseRepo.Description + c.Data["IsPrivate"] = baseRepo.IsPrivate + + if err = baseRepo.GetOwner(); err != nil { + c.ServerError("GetOwner", err) + return nil + } + c.Data["ForkFrom"] = baseRepo.Owner.Name + "/" + baseRepo.Name + + if err := c.User.GetOrganizations(true); err != nil { + c.ServerError("GetOrganizations", err) + return nil + } + c.Data["Orgs"] = c.User.Orgs + + return baseRepo +} + +func Fork(c *context.Context) { + c.Data["Title"] = c.Tr("new_fork") + + parseBaseRepository(c) + if c.Written() { + return + } + + c.Data["ContextUser"] = c.User + c.Success(FORK) +} + +func ForkPost(c *context.Context, f form.CreateRepo) { + c.Data["Title"] = c.Tr("new_fork") + + baseRepo := parseBaseRepository(c) + if c.Written() { + return + } + + ctxUser := checkContextUser(c, f.UserID) + if c.Written() { + return + } + c.Data["ContextUser"] = ctxUser + + if c.HasError() { + c.Success(FORK) + return + } + + repo, has := models.HasForkedRepo(ctxUser.ID, baseRepo.ID) + if has { + c.Redirect(repo.Link()) + return + } + + // Check ownership of organization. + if ctxUser.IsOrganization() && !ctxUser.IsOwnedBy(c.User.ID) { + c.Error(403) + return + } + + // Cannot fork to same owner + if ctxUser.ID == baseRepo.OwnerID { + c.RenderWithErr(c.Tr("repo.settings.cannot_fork_to_same_owner"), FORK, &f) + return + } + + repo, err := models.ForkRepository(c.User, ctxUser, baseRepo, f.RepoName, f.Description) + if err != nil { + c.Data["Err_RepoName"] = true + switch { + case models.IsErrRepoAlreadyExist(err): + c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), FORK, &f) + case models.IsErrNameReserved(err): + c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), FORK, &f) + case models.IsErrNamePatternNotAllowed(err): + c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), FORK, &f) + default: + c.ServerError("ForkPost", err) + } + return + } + + log.Trace("Repository forked from '%s' -> '%s'", baseRepo.FullName(), repo.FullName()) + c.Redirect(repo.Link()) +} + +func checkPullInfo(c *context.Context) *models.Issue { + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index")) + if err != nil { + c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err) + return nil + } + c.Data["Title"] = issue.Title + c.Data["Issue"] = issue + + if !issue.IsPull { + c.Handle(404, "ViewPullCommits", nil) + return nil + } + + if c.IsLogged { + // Update issue-user. + if err = issue.ReadBy(c.User.ID); err != nil { + c.ServerError("ReadBy", err) + return nil + } + } + + return issue +} + +func PrepareMergedViewPullInfo(c *context.Context, issue *models.Issue) { + pull := issue.PullRequest + c.Data["HasMerged"] = true + c.Data["HeadTarget"] = issue.PullRequest.HeadUserName + "/" + pull.HeadBranch + c.Data["BaseTarget"] = c.Repo.Owner.Name + "/" + pull.BaseBranch + + var err error + c.Data["NumCommits"], err = c.Repo.GitRepo.CommitsCountBetween(pull.MergeBase, pull.MergedCommitID) + if err != nil { + c.ServerError("Repo.GitRepo.CommitsCountBetween", err) + return + } + c.Data["NumFiles"], err = c.Repo.GitRepo.FilesCountBetween(pull.MergeBase, pull.MergedCommitID) + if err != nil { + c.ServerError("Repo.GitRepo.FilesCountBetween", err) + return + } +} + +func PrepareViewPullInfo(c *context.Context, issue *models.Issue) *git.PullRequestInfo { + repo := c.Repo.Repository + pull := issue.PullRequest + + c.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBranch + c.Data["BaseTarget"] = c.Repo.Owner.Name + "/" + pull.BaseBranch + + var ( + headGitRepo *git.Repository + err error + ) + + if pull.HeadRepo != nil { + headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath()) + if err != nil { + c.ServerError("OpenRepository", err) + return nil + } + } + + if pull.HeadRepo == nil || !headGitRepo.IsBranchExist(pull.HeadBranch) { + c.Data["IsPullReuqestBroken"] = true + c.Data["HeadTarget"] = "deleted" + c.Data["NumCommits"] = 0 + c.Data["NumFiles"] = 0 + return nil + } + + prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), + pull.BaseBranch, pull.HeadBranch) + if err != nil { + if strings.Contains(err.Error(), "fatal: Not a valid object name") { + c.Data["IsPullReuqestBroken"] = true + c.Data["BaseTarget"] = "deleted" + c.Data["NumCommits"] = 0 + c.Data["NumFiles"] = 0 + return nil + } + + c.ServerError("GetPullRequestInfo", err) + return nil + } + c.Data["NumCommits"] = prInfo.Commits.Len() + c.Data["NumFiles"] = prInfo.NumFiles + return prInfo +} + +func ViewPullCommits(c *context.Context) { + c.Data["PageIsPullList"] = true + c.Data["PageIsPullCommits"] = true + + issue := checkPullInfo(c) + if c.Written() { + return + } + pull := issue.PullRequest + + if pull.HeadRepo != nil { + c.Data["Username"] = pull.HeadUserName + c.Data["Reponame"] = pull.HeadRepo.Name + } + + var commits *list.List + if pull.HasMerged { + PrepareMergedViewPullInfo(c, issue) + if c.Written() { + return + } + startCommit, err := c.Repo.GitRepo.GetCommit(pull.MergeBase) + if err != nil { + c.ServerError("Repo.GitRepo.GetCommit", err) + return + } + endCommit, err := c.Repo.GitRepo.GetCommit(pull.MergedCommitID) + if err != nil { + c.ServerError("Repo.GitRepo.GetCommit", err) + return + } + commits, err = c.Repo.GitRepo.CommitsBetween(endCommit, startCommit) + if err != nil { + c.ServerError("Repo.GitRepo.CommitsBetween", err) + return + } + + } else { + prInfo := PrepareViewPullInfo(c, issue) + if c.Written() { + return + } else if prInfo == nil { + c.Handle(404, "ViewPullCommits", nil) + return + } + commits = prInfo.Commits + } + + commits = models.ValidateCommitsWithEmails(commits) + c.Data["Commits"] = commits + c.Data["CommitsCount"] = commits.Len() + + c.Success(PULL_COMMITS) +} + +func ViewPullFiles(c *context.Context) { + c.Data["PageIsPullList"] = true + c.Data["PageIsPullFiles"] = true + + issue := checkPullInfo(c) + if c.Written() { + return + } + pull := issue.PullRequest + + var ( + diffRepoPath string + startCommitID string + endCommitID string + gitRepo *git.Repository + ) + + if pull.HasMerged { + PrepareMergedViewPullInfo(c, issue) + if c.Written() { + return + } + + diffRepoPath = c.Repo.GitRepo.Path + startCommitID = pull.MergeBase + endCommitID = pull.MergedCommitID + gitRepo = c.Repo.GitRepo + } else { + prInfo := PrepareViewPullInfo(c, issue) + if c.Written() { + return + } else if prInfo == nil { + c.Handle(404, "ViewPullFiles", nil) + return + } + + headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name) + + headGitRepo, err := git.OpenRepository(headRepoPath) + if err != nil { + c.ServerError("OpenRepository", err) + return + } + + headCommitID, err := headGitRepo.GetBranchCommitID(pull.HeadBranch) + if err != nil { + c.ServerError("GetBranchCommitID", err) + return + } + + diffRepoPath = headRepoPath + startCommitID = prInfo.MergeBase + endCommitID = headCommitID + gitRepo = headGitRepo + } + + diff, err := models.GetDiffRange(diffRepoPath, + startCommitID, endCommitID, setting.Git.MaxGitDiffLines, + setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) + if err != nil { + c.ServerError("GetDiffRange", err) + return + } + c.Data["Diff"] = diff + c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 + + commit, err := gitRepo.GetCommit(endCommitID) + if err != nil { + c.ServerError("GetCommit", err) + return + } + + setEditorconfigIfExists(c) + if c.Written() { + return + } + + c.Data["IsSplitStyle"] = c.Query("style") == "split" + c.Data["IsImageFile"] = commit.IsImageFile + + // It is possible head repo has been deleted for merged pull requests + if pull.HeadRepo != nil { + c.Data["Username"] = pull.HeadUserName + c.Data["Reponame"] = pull.HeadRepo.Name + + headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name) + c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", endCommitID) + c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", startCommitID) + c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", endCommitID) + } + + c.Data["RequireHighlightJS"] = true + c.Success(PULL_FILES) +} + +func MergePullRequest(c *context.Context) { + issue := checkPullInfo(c) + if c.Written() { + return + } + if issue.IsClosed { + c.Handle(404, "MergePullRequest", nil) + return + } + + pr, err := models.GetPullRequestByIssueID(issue.ID) + if err != nil { + c.NotFoundOrServerError("GetPullRequestByIssueID", models.IsErrPullRequestNotExist, err) + return + } + + if !pr.CanAutoMerge() || pr.HasMerged { + c.Handle(404, "MergePullRequest", nil) + return + } + + pr.Issue = issue + pr.Issue.Repo = c.Repo.Repository + if err = pr.Merge(c.User, c.Repo.GitRepo); err != nil { + c.ServerError("Merge", err) + return + } + + log.Trace("Pull request merged: %d", pr.ID) + c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) +} + +func ParseCompareInfo(c *context.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { + baseRepo := c.Repo.Repository + + // Get compared branches information + // format: ...[:] + // base<-head: master...head:feature + // same repo: master...feature + infos := strings.Split(c.Params("*"), "...") + if len(infos) != 2 { + log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos) + c.NotFound() + return nil, nil, nil, nil, "", "" + } + + baseBranch := infos[0] + c.Data["BaseBranch"] = baseBranch + + var ( + headUser *models.User + headBranch string + isSameRepo bool + err error + ) + + // If there is no head repository, it means pull request between same repository. + headInfos := strings.Split(infos[1], ":") + if len(headInfos) == 1 { + isSameRepo = true + headUser = c.Repo.Owner + headBranch = headInfos[0] + + } else if len(headInfos) == 2 { + headUser, err = models.GetUserByName(headInfos[0]) + if err != nil { + c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) + return nil, nil, nil, nil, "", "" + } + headBranch = headInfos[1] + isSameRepo = headUser.ID == baseRepo.OwnerID + + } else { + c.NotFound() + return nil, nil, nil, nil, "", "" + } + c.Data["HeadUser"] = headUser + c.Data["HeadBranch"] = headBranch + c.Repo.PullRequest.SameRepo = isSameRepo + + // Check if base branch is valid. + if !c.Repo.GitRepo.IsBranchExist(baseBranch) { + c.NotFound() + return nil, nil, nil, nil, "", "" + } + + var ( + headRepo *models.Repository + headGitRepo *git.Repository + ) + + // In case user included redundant head user name for comparison in same repository, + // no need to check the fork relation. + if !isSameRepo { + var has bool + headRepo, has = models.HasForkedRepo(headUser.ID, baseRepo.ID) + if !has { + log.Trace("ParseCompareInfo [base_repo_id: %d]: does not have fork or in same repository", baseRepo.ID) + c.NotFound() + return nil, nil, nil, nil, "", "" + } + + headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name)) + if err != nil { + c.ServerError("OpenRepository", err) + return nil, nil, nil, nil, "", "" + } + } else { + headRepo = c.Repo.Repository + headGitRepo = c.Repo.GitRepo + } + + if !c.User.IsWriterOfRepo(headRepo) && !c.User.IsAdmin { + log.Trace("ParseCompareInfo [base_repo_id: %d]: does not have write access or site admin", baseRepo.ID) + c.NotFound() + return nil, nil, nil, nil, "", "" + } + + // Check if head branch is valid. + if !headGitRepo.IsBranchExist(headBranch) { + c.NotFound() + return nil, nil, nil, nil, "", "" + } + + headBranches, err := headGitRepo.GetBranches() + if err != nil { + c.ServerError("GetBranches", err) + return nil, nil, nil, nil, "", "" + } + c.Data["HeadBranches"] = headBranches + + prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) + if err != nil { + if git.IsErrNoMergeBase(err) { + c.Data["IsNoMergeBase"] = true + c.Success(COMPARE_PULL) + } else { + c.ServerError("GetPullRequestInfo", err) + } + return nil, nil, nil, nil, "", "" + } + c.Data["BeforeCommitID"] = prInfo.MergeBase + + return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch +} + +func PrepareCompareDiff( + c *context.Context, + headUser *models.User, + headRepo *models.Repository, + headGitRepo *git.Repository, + prInfo *git.PullRequestInfo, + baseBranch, headBranch string) bool { + + var ( + repo = c.Repo.Repository + err error + ) + + // Get diff information. + c.Data["CommitRepoLink"] = headRepo.Link() + + headCommitID, err := headGitRepo.GetBranchCommitID(headBranch) + if err != nil { + c.ServerError("GetBranchCommitID", err) + return false + } + c.Data["AfterCommitID"] = headCommitID + + if headCommitID == prInfo.MergeBase { + c.Data["IsNothingToCompare"] = true + return true + } + + diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name), + prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines, + setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) + if err != nil { + c.ServerError("GetDiffRange", err) + return false + } + c.Data["Diff"] = diff + c.Data["DiffNotAvailable"] = diff.NumFiles() == 0 + + headCommit, err := headGitRepo.GetCommit(headCommitID) + if err != nil { + c.ServerError("GetCommit", err) + return false + } + + prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits) + c.Data["Commits"] = prInfo.Commits + c.Data["CommitCount"] = prInfo.Commits.Len() + c.Data["Username"] = headUser.Name + c.Data["Reponame"] = headRepo.Name + c.Data["IsImageFile"] = headCommit.IsImageFile + + headTarget := path.Join(headUser.Name, repo.Name) + c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", headCommitID) + c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", prInfo.MergeBase) + c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", headCommitID) + return false +} + +func CompareAndPullRequest(c *context.Context) { + c.Data["Title"] = c.Tr("repo.pulls.compare_changes") + c.Data["PageIsComparePull"] = true + c.Data["IsDiffCompare"] = true + c.Data["RequireHighlightJS"] = true + setTemplateIfExists(c, PULL_REQUEST_TEMPLATE_KEY, PullRequestTemplateCandidates) + renderAttachmentSettings(c) + + headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(c) + if c.Written() { + return + } + + pr, err := models.GetUnmergedPullRequest(headRepo.ID, c.Repo.Repository.ID, headBranch, baseBranch) + if err != nil { + if !models.IsErrPullRequestNotExist(err) { + c.ServerError("GetUnmergedPullRequest", err) + return + } + } else { + c.Data["HasPullRequest"] = true + c.Data["PullRequest"] = pr + c.Success(COMPARE_PULL) + return + } + + nothingToCompare := PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) + if c.Written() { + return + } + + if !nothingToCompare { + // Setup information for new form. + RetrieveRepoMetas(c, c.Repo.Repository) + if c.Written() { + return + } + } + + setEditorconfigIfExists(c) + if c.Written() { + return + } + + c.Data["IsSplitStyle"] = c.Query("style") == "split" + c.Success(COMPARE_PULL) +} + +func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) { + c.Data["Title"] = c.Tr("repo.pulls.compare_changes") + c.Data["PageIsComparePull"] = true + c.Data["IsDiffCompare"] = true + c.Data["RequireHighlightJS"] = true + renderAttachmentSettings(c) + + var ( + repo = c.Repo.Repository + attachments []string + ) + + headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(c) + if c.Written() { + return + } + + labelIDs, milestoneID, assigneeID := ValidateRepoMetas(c, f) + if c.Written() { + return + } + + if setting.AttachmentEnabled { + attachments = f.Files + } + + if c.HasError() { + form.Assign(f, c.Data) + + // This stage is already stop creating new pull request, so it does not matter if it has + // something to compare or not. + PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) + if c.Written() { + return + } + + c.Success(COMPARE_PULL) + return + } + + patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch) + if err != nil { + c.ServerError("GetPatch", err) + return + } + + pullIssue := &models.Issue{ + RepoID: repo.ID, + Index: repo.NextIssueIndex(), + Title: f.Title, + PosterID: c.User.ID, + Poster: c.User, + MilestoneID: milestoneID, + AssigneeID: assigneeID, + IsPull: true, + Content: f.Content, + } + pullRequest := &models.PullRequest{ + HeadRepoID: headRepo.ID, + BaseRepoID: repo.ID, + HeadUserName: headUser.Name, + HeadBranch: headBranch, + BaseBranch: baseBranch, + HeadRepo: headRepo, + BaseRepo: repo, + MergeBase: prInfo.MergeBase, + Type: models.PULL_REQUEST_GOGS, + } + // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt + // instead of 500. + if err := models.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch); err != nil { + c.ServerError("NewPullRequest", err) + return + } else if err := pullRequest.PushToBaseRepo(); err != nil { + c.ServerError("PushToBaseRepo", err) + return + } + + log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID) + c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index)) +} + +func parseOwnerAndRepo(c *context.Context) (*models.User, *models.Repository) { + owner, err := models.GetUserByName(c.Params(":username")) + if err != nil { + c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) + return nil, nil + } + + repo, err := models.GetRepositoryByName(owner.ID, c.Params(":reponame")) + if err != nil { + c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err) + return nil, nil + } + + return owner, repo +} + +func TriggerTask(c *context.Context) { + pusherID := c.QueryInt64("pusher") + branch := c.Query("branch") + secret := c.Query("secret") + if len(branch) == 0 || len(secret) == 0 || pusherID <= 0 { + c.Error(404) + log.Trace("TriggerTask: branch or secret is empty, or pusher ID is not valid") + return + } + owner, repo := parseOwnerAndRepo(c) + if c.Written() { + return + } + if secret != tool.MD5(owner.Salt) { + c.Error(404) + log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name) + return + } + + pusher, err := models.GetUserByID(pusherID) + if err != nil { + c.NotFoundOrServerError("GetUserByID", errors.IsUserNotExist, err) + return + } + + log.Trace("TriggerTask '%s/%s' by '%s'", repo.Name, branch, pusher.Name) + + go models.HookQueue.Add(repo.ID) + go models.AddTestPullRequestTask(pusher, repo.ID, branch, true) + c.Status(202) +} diff --git a/routes/repo/release.go b/routes/repo/release.go new file mode 100644 index 00000000..86dfe6f7 --- /dev/null +++ b/routes/repo/release.go @@ -0,0 +1,332 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "fmt" + "strings" + + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/markup" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + RELEASES = "repo/release/list" + RELEASE_NEW = "repo/release/new" +) + +// calReleaseNumCommitsBehind calculates given release has how many commits behind release target. +func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *models.Release, countCache map[string]int64) error { + // Get count if not exists + if _, ok := countCache[release.Target]; !ok { + if repoCtx.GitRepo.IsBranchExist(release.Target) { + commit, err := repoCtx.GitRepo.GetBranchCommit(release.Target) + if err != nil { + return fmt.Errorf("GetBranchCommit: %v", err) + } + countCache[release.Target], err = commit.CommitsCount() + if err != nil { + return fmt.Errorf("CommitsCount: %v", err) + } + } else { + // Use NumCommits of the newest release on that target + countCache[release.Target] = release.NumCommits + } + } + release.NumCommitsBehind = countCache[release.Target] - release.NumCommits + return nil +} + +func Releases(c *context.Context) { + c.Data["Title"] = c.Tr("repo.release.releases") + c.Data["PageIsViewFiles"] = true + c.Data["PageIsReleaseList"] = true + + tagsResult, err := c.Repo.GitRepo.GetTagsAfter(c.Query("after"), 10) + if err != nil { + c.Handle(500, fmt.Sprintf("GetTags '%s'", c.Repo.Repository.RepoPath()), err) + return + } + + releases, err := models.GetPublishedReleasesByRepoID(c.Repo.Repository.ID, tagsResult.Tags...) + if err != nil { + c.Handle(500, "GetPublishedReleasesByRepoID", err) + return + } + + // Temproray cache commits count of used branches to speed up. + countCache := make(map[string]int64) + + results := make([]*models.Release, len(tagsResult.Tags)) + for i, rawTag := range tagsResult.Tags { + for j, r := range releases { + if r == nil || r.TagName != rawTag { + continue + } + releases[j] = nil // Mark as used. + + if err = r.LoadAttributes(); err != nil { + c.Handle(500, "LoadAttributes", err) + return + } + + if err := calReleaseNumCommitsBehind(c.Repo, r, countCache); err != nil { + c.Handle(500, "calReleaseNumCommitsBehind", err) + return + } + + r.Note = string(markup.Markdown(r.Note, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) + results[i] = r + break + } + + // No published release matches this tag + if results[i] == nil { + commit, err := c.Repo.GitRepo.GetTagCommit(rawTag) + if err != nil { + c.Handle(500, "GetTagCommit", err) + return + } + + results[i] = &models.Release{ + Title: rawTag, + TagName: rawTag, + Sha1: commit.ID.String(), + } + + results[i].NumCommits, err = commit.CommitsCount() + if err != nil { + c.Handle(500, "CommitsCount", err) + return + } + results[i].NumCommitsBehind = c.Repo.CommitsCount - results[i].NumCommits + } + } + models.SortReleases(results) + + // Only show drafts if user is viewing the latest page + var drafts []*models.Release + if tagsResult.HasLatest { + drafts, err = models.GetDraftReleasesByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Handle(500, "GetDraftReleasesByRepoID", err) + return + } + + for _, r := range drafts { + if err = r.LoadAttributes(); err != nil { + c.Handle(500, "LoadAttributes", err) + return + } + + if err := calReleaseNumCommitsBehind(c.Repo, r, countCache); err != nil { + c.Handle(500, "calReleaseNumCommitsBehind", err) + return + } + + r.Note = string(markup.Markdown(r.Note, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) + } + + if len(drafts) > 0 { + results = append(drafts, results...) + } + } + + c.Data["Releases"] = results + c.Data["HasPrevious"] = !tagsResult.HasLatest + c.Data["ReachEnd"] = tagsResult.ReachEnd + c.Data["PreviousAfter"] = tagsResult.PreviousAfter + if len(results) > 0 { + c.Data["NextAfter"] = results[len(results)-1].TagName + } + c.HTML(200, RELEASES) +} + +func renderReleaseAttachmentSettings(c *context.Context) { + c.Data["RequireDropzone"] = true + c.Data["IsAttachmentEnabled"] = setting.Release.Attachment.Enabled + c.Data["AttachmentAllowedTypes"] = strings.Join(setting.Release.Attachment.AllowedTypes, ",") + c.Data["AttachmentMaxSize"] = setting.Release.Attachment.MaxSize + c.Data["AttachmentMaxFiles"] = setting.Release.Attachment.MaxFiles +} + +func NewRelease(c *context.Context) { + c.Data["Title"] = c.Tr("repo.release.new_release") + c.Data["PageIsReleaseList"] = true + c.Data["tag_target"] = c.Repo.Repository.DefaultBranch + renderReleaseAttachmentSettings(c) + c.HTML(200, RELEASE_NEW) +} + +func NewReleasePost(c *context.Context, f form.NewRelease) { + c.Data["Title"] = c.Tr("repo.release.new_release") + c.Data["PageIsReleaseList"] = true + renderReleaseAttachmentSettings(c) + + if c.HasError() { + c.HTML(200, RELEASE_NEW) + return + } + + if !c.Repo.GitRepo.IsBranchExist(f.Target) { + c.RenderWithErr(c.Tr("form.target_branch_not_exist"), RELEASE_NEW, &f) + return + } + + // Use current time if tag not yet exist, otherwise get time from Git + var tagCreatedUnix int64 + tag, err := c.Repo.GitRepo.GetTag(f.TagName) + if err == nil { + commit, err := tag.Commit() + if err == nil { + tagCreatedUnix = commit.Author.When.Unix() + } + } + + commit, err := c.Repo.GitRepo.GetBranchCommit(f.Target) + if err != nil { + c.Handle(500, "GetBranchCommit", err) + return + } + + commitsCount, err := commit.CommitsCount() + if err != nil { + c.Handle(500, "CommitsCount", err) + return + } + + var attachments []string + if setting.Release.Attachment.Enabled { + attachments = f.Files + } + + rel := &models.Release{ + RepoID: c.Repo.Repository.ID, + PublisherID: c.User.ID, + Title: f.Title, + TagName: f.TagName, + Target: f.Target, + Sha1: commit.ID.String(), + NumCommits: commitsCount, + Note: f.Content, + IsDraft: len(f.Draft) > 0, + IsPrerelease: f.Prerelease, + CreatedUnix: tagCreatedUnix, + } + if err = models.NewRelease(c.Repo.GitRepo, rel, attachments); err != nil { + c.Data["Err_TagName"] = true + switch { + case models.IsErrReleaseAlreadyExist(err): + c.RenderWithErr(c.Tr("repo.release.tag_name_already_exist"), RELEASE_NEW, &f) + case models.IsErrInvalidTagName(err): + c.RenderWithErr(c.Tr("repo.release.tag_name_invalid"), RELEASE_NEW, &f) + default: + c.Handle(500, "NewRelease", err) + } + return + } + log.Trace("Release created: %s/%s:%s", c.User.LowerName, c.Repo.Repository.Name, f.TagName) + + c.Redirect(c.Repo.RepoLink + "/releases") +} + +func EditRelease(c *context.Context) { + c.Data["Title"] = c.Tr("repo.release.edit_release") + c.Data["PageIsReleaseList"] = true + c.Data["PageIsEditRelease"] = true + renderReleaseAttachmentSettings(c) + + tagName := c.Params("*") + rel, err := models.GetRelease(c.Repo.Repository.ID, tagName) + if err != nil { + if models.IsErrReleaseNotExist(err) { + c.Handle(404, "GetRelease", err) + } else { + c.Handle(500, "GetRelease", err) + } + return + } + c.Data["ID"] = rel.ID + c.Data["tag_name"] = rel.TagName + c.Data["tag_target"] = rel.Target + c.Data["title"] = rel.Title + c.Data["content"] = rel.Note + c.Data["attachments"] = rel.Attachments + c.Data["prerelease"] = rel.IsPrerelease + c.Data["IsDraft"] = rel.IsDraft + + c.HTML(200, RELEASE_NEW) +} + +func EditReleasePost(c *context.Context, f form.EditRelease) { + c.Data["Title"] = c.Tr("repo.release.edit_release") + c.Data["PageIsReleaseList"] = true + c.Data["PageIsEditRelease"] = true + renderReleaseAttachmentSettings(c) + + tagName := c.Params("*") + rel, err := models.GetRelease(c.Repo.Repository.ID, tagName) + if err != nil { + if models.IsErrReleaseNotExist(err) { + c.Handle(404, "GetRelease", err) + } else { + c.Handle(500, "GetRelease", err) + } + return + } + c.Data["tag_name"] = rel.TagName + c.Data["tag_target"] = rel.Target + c.Data["title"] = rel.Title + c.Data["content"] = rel.Note + c.Data["attachments"] = rel.Attachments + c.Data["prerelease"] = rel.IsPrerelease + c.Data["IsDraft"] = rel.IsDraft + + if c.HasError() { + c.HTML(200, RELEASE_NEW) + return + } + + var attachments []string + if setting.Release.Attachment.Enabled { + attachments = f.Files + } + + isPublish := rel.IsDraft && len(f.Draft) == 0 + rel.Title = f.Title + rel.Note = f.Content + rel.IsDraft = len(f.Draft) > 0 + rel.IsPrerelease = f.Prerelease + if err = models.UpdateRelease(c.User, c.Repo.GitRepo, rel, isPublish, attachments); err != nil { + c.Handle(500, "UpdateRelease", err) + return + } + c.Redirect(c.Repo.RepoLink + "/releases") +} + +func UploadReleaseAttachment(c *context.Context) { + if !setting.Release.Attachment.Enabled { + c.NotFound() + return + } + uploadAttachment(c, setting.Release.Attachment.AllowedTypes) +} + +func DeleteRelease(c *context.Context) { + if err := models.DeleteReleaseOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteReleaseByID: " + err.Error()) + } else { + c.Flash.Success(c.Tr("repo.release.deletion_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Repo.RepoLink + "/releases", + }) +} diff --git a/routes/repo/repo.go b/routes/repo/repo.go new file mode 100644 index 00000000..ea3c1a60 --- /dev/null +++ b/routes/repo/repo.go @@ -0,0 +1,335 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "fmt" + "os" + "path" + "strings" + + "github.com/Unknwon/com" + log "gopkg.in/clog.v1" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + CREATE = "repo/create" + MIGRATE = "repo/migrate" +) + +func MustBeNotBare(c *context.Context) { + if c.Repo.Repository.IsBare { + c.Handle(404, "MustBeNotBare", nil) + } +} + +func checkContextUser(c *context.Context, uid int64) *models.User { + orgs, err := models.GetOwnedOrgsByUserIDDesc(c.User.ID, "updated_unix") + if err != nil { + c.Handle(500, "GetOwnedOrgsByUserIDDesc", err) + return nil + } + c.Data["Orgs"] = orgs + + // Not equal means current user is an organization. + if uid == c.User.ID || uid == 0 { + return c.User + } + + org, err := models.GetUserByID(uid) + if errors.IsUserNotExist(err) { + return c.User + } + + if err != nil { + c.Handle(500, "GetUserByID", fmt.Errorf("[%d]: %v", uid, err)) + return nil + } + + // Check ownership of organization. + if !org.IsOrganization() || !(c.User.IsAdmin || org.IsOwnedBy(c.User.ID)) { + c.Error(403) + return nil + } + return org +} + +func Create(c *context.Context) { + c.Data["Title"] = c.Tr("new_repo") + + // Give default value for template to render. + c.Data["Gitignores"] = models.Gitignores + c.Data["Licenses"] = models.Licenses + c.Data["Readmes"] = models.Readmes + c.Data["readme"] = "Default" + c.Data["private"] = c.User.LastRepoVisibility + c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + + ctxUser := checkContextUser(c, c.QueryInt64("org")) + if c.Written() { + return + } + c.Data["ContextUser"] = ctxUser + + c.HTML(200, CREATE) +} + +func handleCreateError(c *context.Context, owner *models.User, err error, name, tpl string, form interface{}) { + switch { + case errors.IsReachLimitOfRepo(err): + c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", owner.RepoCreationNum()), tpl, form) + case models.IsErrRepoAlreadyExist(err): + c.Data["Err_RepoName"] = true + c.RenderWithErr(c.Tr("form.repo_name_been_taken"), tpl, form) + case models.IsErrNameReserved(err): + c.Data["Err_RepoName"] = true + c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) + case models.IsErrNamePatternNotAllowed(err): + c.Data["Err_RepoName"] = true + c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) + default: + c.Handle(500, name, err) + } +} + +func CreatePost(c *context.Context, f form.CreateRepo) { + c.Data["Title"] = c.Tr("new_repo") + + c.Data["Gitignores"] = models.Gitignores + c.Data["Licenses"] = models.Licenses + c.Data["Readmes"] = models.Readmes + + ctxUser := checkContextUser(c, f.UserID) + if c.Written() { + return + } + c.Data["ContextUser"] = ctxUser + + if c.HasError() { + c.HTML(200, CREATE) + return + } + + repo, err := models.CreateRepository(c.User, ctxUser, models.CreateRepoOptions{ + Name: f.RepoName, + Description: f.Description, + Gitignores: f.Gitignores, + License: f.License, + Readme: f.Readme, + IsPrivate: f.Private || setting.Repository.ForcePrivate, + AutoInit: f.AutoInit, + }) + if err == nil { + log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) + c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name) + return + } + + if repo != nil { + if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { + log.Error(4, "DeleteRepository: %v", errDelete) + } + } + + handleCreateError(c, ctxUser, err, "CreatePost", CREATE, &f) +} + +func Migrate(c *context.Context) { + c.Data["Title"] = c.Tr("new_migrate") + c.Data["private"] = c.User.LastRepoVisibility + c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + c.Data["mirror"] = c.Query("mirror") == "1" + + ctxUser := checkContextUser(c, c.QueryInt64("org")) + if c.Written() { + return + } + c.Data["ContextUser"] = ctxUser + + c.HTML(200, MIGRATE) +} + +func MigratePost(c *context.Context, f form.MigrateRepo) { + c.Data["Title"] = c.Tr("new_migrate") + + ctxUser := checkContextUser(c, f.Uid) + if c.Written() { + return + } + c.Data["ContextUser"] = ctxUser + + if c.HasError() { + c.HTML(200, MIGRATE) + return + } + + remoteAddr, err := f.ParseRemoteAddr(c.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + c.Data["Err_CloneAddr"] = true + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + c.RenderWithErr(c.Tr("form.url_error"), MIGRATE, &f) + case addrErr.IsPermissionDenied: + c.RenderWithErr(c.Tr("repo.migrate.permission_denied"), MIGRATE, &f) + case addrErr.IsInvalidPath: + c.RenderWithErr(c.Tr("repo.migrate.invalid_local_path"), MIGRATE, &f) + default: + c.Handle(500, "Unknown error", err) + } + } else { + c.Handle(500, "ParseRemoteAddr", err) + } + return + } + + repo, err := models.MigrateRepository(c.User, ctxUser, models.MigrateRepoOptions{ + Name: f.RepoName, + Description: f.Description, + IsPrivate: f.Private || setting.Repository.ForcePrivate, + IsMirror: f.Mirror, + RemoteAddr: remoteAddr, + }) + if err == nil { + log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, f.RepoName) + c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + f.RepoName) + return + } + + if repo != nil { + if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { + log.Error(4, "DeleteRepository: %v", errDelete) + } + } + + if strings.Contains(err.Error(), "Authentication failed") || + strings.Contains(err.Error(), "could not read Username") { + c.Data["Err_Auth"] = true + c.RenderWithErr(c.Tr("form.auth_failed", models.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f) + return + } else if strings.Contains(err.Error(), "fatal:") { + c.Data["Err_CloneAddr"] = true + c.RenderWithErr(c.Tr("repo.migrate.failed", models.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f) + return + } + + handleCreateError(c, ctxUser, err, "MigratePost", MIGRATE, &f) +} + +func Action(c *context.Context) { + var err error + switch c.Params(":action") { + case "watch": + err = models.WatchRepo(c.User.ID, c.Repo.Repository.ID, true) + case "unwatch": + err = models.WatchRepo(c.User.ID, c.Repo.Repository.ID, false) + case "star": + err = models.StarRepo(c.User.ID, c.Repo.Repository.ID, true) + case "unstar": + err = models.StarRepo(c.User.ID, c.Repo.Repository.ID, false) + case "desc": // FIXME: this is not used + if !c.Repo.IsOwner() { + c.Error(404) + return + } + + c.Repo.Repository.Description = c.Query("desc") + c.Repo.Repository.Website = c.Query("site") + err = models.UpdateRepository(c.Repo.Repository, false) + } + + if err != nil { + c.Handle(500, fmt.Sprintf("Action (%s)", c.Params(":action")), err) + return + } + + redirectTo := c.Query("redirect_to") + if len(redirectTo) == 0 { + redirectTo = c.Repo.RepoLink + } + c.Redirect(redirectTo) +} + +func Download(c *context.Context) { + var ( + uri = c.Params("*") + refName string + ext string + archivePath string + archiveType git.ArchiveType + ) + + switch { + case strings.HasSuffix(uri, ".zip"): + ext = ".zip" + archivePath = path.Join(c.Repo.GitRepo.Path, "archives/zip") + archiveType = git.ZIP + case strings.HasSuffix(uri, ".tar.gz"): + ext = ".tar.gz" + archivePath = path.Join(c.Repo.GitRepo.Path, "archives/targz") + archiveType = git.TARGZ + default: + log.Trace("Unknown format: %s", uri) + c.Error(404) + return + } + refName = strings.TrimSuffix(uri, ext) + + if !com.IsDir(archivePath) { + if err := os.MkdirAll(archivePath, os.ModePerm); err != nil { + c.Handle(500, "Download -> os.MkdirAll(archivePath)", err) + return + } + } + + // Get corresponding commit. + var ( + commit *git.Commit + err error + ) + gitRepo := c.Repo.GitRepo + if gitRepo.IsBranchExist(refName) { + commit, err = gitRepo.GetBranchCommit(refName) + if err != nil { + c.Handle(500, "GetBranchCommit", err) + return + } + } else if gitRepo.IsTagExist(refName) { + commit, err = gitRepo.GetTagCommit(refName) + if err != nil { + c.Handle(500, "GetTagCommit", err) + return + } + } else if len(refName) >= 7 && len(refName) <= 40 { + commit, err = gitRepo.GetCommit(refName) + if err != nil { + c.NotFound() + return + } + } else { + c.NotFound() + return + } + + archivePath = path.Join(archivePath, tool.ShortSHA1(commit.ID.String())+ext) + if !com.IsFile(archivePath) { + if err := commit.CreateArchive(archivePath, archiveType); err != nil { + c.Handle(500, "Download -> CreateArchive "+archivePath, err) + return + } + } + + c.ServeFile(archivePath, c.Repo.Repository.Name+"-"+refName+ext) +} diff --git a/routes/repo/setting.go b/routes/repo/setting.go new file mode 100644 index 00000000..9168b04a --- /dev/null +++ b/routes/repo/setting.go @@ -0,0 +1,631 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "fmt" + "strings" + "time" + + log "gopkg.in/clog.v1" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/mailer" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + SETTINGS_OPTIONS = "repo/settings/options" + SETTINGS_COLLABORATION = "repo/settings/collaboration" + SETTINGS_BRANCHES = "repo/settings/branches" + SETTINGS_PROTECTED_BRANCH = "repo/settings/protected_branch" + SETTINGS_GITHOOKS = "repo/settings/githooks" + SETTINGS_GITHOOK_EDIT = "repo/settings/githook_edit" + SETTINGS_DEPLOY_KEYS = "repo/settings/deploy_keys" +) + +func Settings(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings") + c.Data["PageIsSettingsOptions"] = true + c.HTML(200, SETTINGS_OPTIONS) +} + +func SettingsPost(c *context.Context, f form.RepoSetting) { + c.Data["Title"] = c.Tr("repo.settings") + c.Data["PageIsSettingsOptions"] = true + + repo := c.Repo.Repository + + switch c.Query("action") { + case "update": + if c.HasError() { + c.HTML(200, SETTINGS_OPTIONS) + return + } + + isNameChanged := false + oldRepoName := repo.Name + newRepoName := f.RepoName + // Check if repository name has been changed. + if repo.LowerName != strings.ToLower(newRepoName) { + isNameChanged = true + if err := models.ChangeRepositoryName(c.Repo.Owner, repo.Name, newRepoName); err != nil { + c.Data["Err_RepoName"] = true + switch { + case models.IsErrRepoAlreadyExist(err): + c.RenderWithErr(c.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, &f) + case models.IsErrNameReserved(err): + c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), SETTINGS_OPTIONS, &f) + case models.IsErrNamePatternNotAllowed(err): + c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), SETTINGS_OPTIONS, &f) + default: + c.Handle(500, "ChangeRepositoryName", err) + } + return + } + + log.Trace("Repository name changed: %s/%s -> %s", c.Repo.Owner.Name, repo.Name, newRepoName) + } + // In case it's just a case change. + repo.Name = newRepoName + repo.LowerName = strings.ToLower(newRepoName) + + repo.Description = f.Description + repo.Website = f.Website + + // Visibility of forked repository is forced sync with base repository. + if repo.IsFork { + f.Private = repo.BaseRepo.IsPrivate + } + + visibilityChanged := repo.IsPrivate != f.Private + repo.IsPrivate = f.Private + if err := models.UpdateRepository(repo, visibilityChanged); err != nil { + c.Handle(500, "UpdateRepository", err) + return + } + log.Trace("Repository basic settings updated: %s/%s", c.Repo.Owner.Name, repo.Name) + + if isNameChanged { + if err := models.RenameRepoAction(c.User, oldRepoName, repo); err != nil { + log.Error(4, "RenameRepoAction: %v", err) + } + } + + c.Flash.Success(c.Tr("repo.settings.update_settings_success")) + c.Redirect(repo.Link() + "/settings") + + case "mirror": + if !repo.IsMirror { + c.Handle(404, "", nil) + return + } + + if f.Interval > 0 { + c.Repo.Mirror.EnablePrune = f.EnablePrune + c.Repo.Mirror.Interval = f.Interval + c.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(f.Interval) * time.Hour) + if err := models.UpdateMirror(c.Repo.Mirror); err != nil { + c.Handle(500, "UpdateMirror", err) + return + } + } + if err := c.Repo.Mirror.SaveAddress(f.MirrorAddress); err != nil { + c.Handle(500, "SaveAddress", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.update_settings_success")) + c.Redirect(repo.Link() + "/settings") + + case "mirror-sync": + if !repo.IsMirror { + c.Handle(404, "", nil) + return + } + + go models.MirrorQueue.Add(repo.ID) + c.Flash.Info(c.Tr("repo.settings.mirror_sync_in_progress")) + c.Redirect(repo.Link() + "/settings") + + case "advanced": + repo.EnableWiki = f.EnableWiki + repo.AllowPublicWiki = f.AllowPublicWiki + repo.EnableExternalWiki = f.EnableExternalWiki + repo.ExternalWikiURL = f.ExternalWikiURL + repo.EnableIssues = f.EnableIssues + repo.AllowPublicIssues = f.AllowPublicIssues + repo.EnableExternalTracker = f.EnableExternalTracker + repo.ExternalTrackerURL = f.ExternalTrackerURL + repo.ExternalTrackerFormat = f.TrackerURLFormat + repo.ExternalTrackerStyle = f.TrackerIssueStyle + repo.EnablePulls = f.EnablePulls + + if err := models.UpdateRepository(repo, false); err != nil { + c.Handle(500, "UpdateRepository", err) + return + } + log.Trace("Repository advanced settings updated: %s/%s", c.Repo.Owner.Name, repo.Name) + + c.Flash.Success(c.Tr("repo.settings.update_settings_success")) + c.Redirect(c.Repo.RepoLink + "/settings") + + case "convert": + if !c.Repo.IsOwner() { + c.Error(404) + return + } + if repo.Name != f.RepoName { + c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) + return + } + + if c.Repo.Owner.IsOrganization() { + if !c.Repo.Owner.IsOwnedBy(c.User.ID) { + c.Error(404) + return + } + } + + if !repo.IsMirror { + c.Error(404) + return + } + repo.IsMirror = false + + if _, err := models.CleanUpMigrateInfo(repo); err != nil { + c.Handle(500, "CleanUpMigrateInfo", err) + return + } else if err = models.DeleteMirrorByRepoID(c.Repo.Repository.ID); err != nil { + c.Handle(500, "DeleteMirrorByRepoID", err) + return + } + log.Trace("Repository converted from mirror to regular: %s/%s", c.Repo.Owner.Name, repo.Name) + c.Flash.Success(c.Tr("repo.settings.convert_succeed")) + c.Redirect(setting.AppSubURL + "/" + c.Repo.Owner.Name + "/" + repo.Name) + + case "transfer": + if !c.Repo.IsOwner() { + c.Error(404) + return + } + if repo.Name != f.RepoName { + c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) + return + } + + if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin { + if !c.Repo.Owner.IsOwnedBy(c.User.ID) { + c.Error(404) + return + } + } + + newOwner := c.Query("new_owner_name") + isExist, err := models.IsUserExist(0, newOwner) + if err != nil { + c.Handle(500, "IsUserExist", err) + return + } else if !isExist { + c.RenderWithErr(c.Tr("form.enterred_invalid_owner_name"), SETTINGS_OPTIONS, nil) + return + } + + if err = models.TransferOwnership(c.User, newOwner, repo); err != nil { + if models.IsErrRepoAlreadyExist(err) { + c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), SETTINGS_OPTIONS, nil) + } else { + c.Handle(500, "TransferOwnership", err) + } + return + } + log.Trace("Repository transfered: %s/%s -> %s", c.Repo.Owner.Name, repo.Name, newOwner) + c.Flash.Success(c.Tr("repo.settings.transfer_succeed")) + c.Redirect(setting.AppSubURL + "/" + newOwner + "/" + repo.Name) + + case "delete": + if !c.Repo.IsOwner() { + c.Error(404) + return + } + if repo.Name != f.RepoName { + c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) + return + } + + if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin { + if !c.Repo.Owner.IsOwnedBy(c.User.ID) { + c.Error(404) + return + } + } + + if err := models.DeleteRepository(c.Repo.Owner.ID, repo.ID); err != nil { + c.Handle(500, "DeleteRepository", err) + return + } + log.Trace("Repository deleted: %s/%s", c.Repo.Owner.Name, repo.Name) + + c.Flash.Success(c.Tr("repo.settings.deletion_success")) + c.Redirect(c.Repo.Owner.DashboardLink()) + + case "delete-wiki": + if !c.Repo.IsOwner() { + c.Error(404) + return + } + if repo.Name != f.RepoName { + c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil) + return + } + + if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin { + if !c.Repo.Owner.IsOwnedBy(c.User.ID) { + c.Error(404) + return + } + } + + repo.DeleteWiki() + log.Trace("Repository wiki deleted: %s/%s", c.Repo.Owner.Name, repo.Name) + + repo.EnableWiki = false + if err := models.UpdateRepository(repo, false); err != nil { + c.Handle(500, "UpdateRepository", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.wiki_deletion_success")) + c.Redirect(c.Repo.RepoLink + "/settings") + + default: + c.Handle(404, "", nil) + } +} + +func SettingsCollaboration(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings") + c.Data["PageIsSettingsCollaboration"] = true + + users, err := c.Repo.Repository.GetCollaborators() + if err != nil { + c.Handle(500, "GetCollaborators", err) + return + } + c.Data["Collaborators"] = users + + c.HTML(200, SETTINGS_COLLABORATION) +} + +func SettingsCollaborationPost(c *context.Context) { + name := strings.ToLower(c.Query("collaborator")) + if len(name) == 0 || c.Repo.Owner.LowerName == name { + c.Redirect(setting.AppSubURL + c.Req.URL.Path) + return + } + + u, err := models.GetUserByName(name) + if err != nil { + if errors.IsUserNotExist(err) { + c.Flash.Error(c.Tr("form.user_not_exist")) + c.Redirect(setting.AppSubURL + c.Req.URL.Path) + } else { + c.Handle(500, "GetUserByName", err) + } + return + } + + // Organization is not allowed to be added as a collaborator + if u.IsOrganization() { + c.Flash.Error(c.Tr("repo.settings.org_not_allowed_to_be_collaborator")) + c.Redirect(setting.AppSubURL + c.Req.URL.Path) + return + } + + if err = c.Repo.Repository.AddCollaborator(u); err != nil { + c.Handle(500, "AddCollaborator", err) + return + } + + if setting.Service.EnableNotifyMail { + mailer.SendCollaboratorMail(models.NewMailerUser(u), models.NewMailerUser(c.User), models.NewMailerRepo(c.Repo.Repository)) + } + + c.Flash.Success(c.Tr("repo.settings.add_collaborator_success")) + c.Redirect(setting.AppSubURL + c.Req.URL.Path) +} + +func ChangeCollaborationAccessMode(c *context.Context) { + if err := c.Repo.Repository.ChangeCollaborationAccessMode( + c.QueryInt64("uid"), + models.AccessMode(c.QueryInt("mode"))); err != nil { + log.Error(2, "ChangeCollaborationAccessMode: %v", err) + return + } + + c.Status(204) +} + +func DeleteCollaboration(c *context.Context) { + if err := c.Repo.Repository.DeleteCollaboration(c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteCollaboration: " + err.Error()) + } else { + c.Flash.Success(c.Tr("repo.settings.remove_collaborator_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Repo.RepoLink + "/settings/collaboration", + }) +} + +func SettingsBranches(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings.branches") + c.Data["PageIsSettingsBranches"] = true + + if c.Repo.Repository.IsBare { + c.Flash.Info(c.Tr("repo.settings.branches_bare"), true) + c.HTML(200, SETTINGS_BRANCHES) + return + } + + protectBranches, err := models.GetProtectBranchesByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Handle(500, "GetProtectBranchesByRepoID", err) + return + } + + // Filter out deleted branches + branches := make([]string, 0, len(protectBranches)) + for i := range protectBranches { + if c.Repo.GitRepo.IsBranchExist(protectBranches[i].Name) { + branches = append(branches, protectBranches[i].Name) + } + } + c.Data["ProtectBranches"] = branches + + c.HTML(200, SETTINGS_BRANCHES) +} + +func UpdateDefaultBranch(c *context.Context) { + branch := c.Query("branch") + if c.Repo.GitRepo.IsBranchExist(branch) && + c.Repo.Repository.DefaultBranch != branch { + c.Repo.Repository.DefaultBranch = branch + if err := c.Repo.GitRepo.SetDefaultBranch(branch); err != nil { + if !git.IsErrUnsupportedVersion(err) { + c.Handle(500, "SetDefaultBranch", err) + return + } + + c.Flash.Warning(c.Tr("repo.settings.update_default_branch_unsupported")) + c.Redirect(c.Repo.RepoLink + "/settings/branches") + return + } + } + + if err := models.UpdateRepository(c.Repo.Repository, false); err != nil { + c.Handle(500, "UpdateRepository", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.update_default_branch_success")) + c.Redirect(c.Repo.RepoLink + "/settings/branches") +} + +func SettingsProtectedBranch(c *context.Context) { + branch := c.Params("*") + if !c.Repo.GitRepo.IsBranchExist(branch) { + c.NotFound() + return + } + + c.Data["Title"] = c.Tr("repo.settings.protected_branches") + " - " + branch + c.Data["PageIsSettingsBranches"] = true + + protectBranch, err := models.GetProtectBranchOfRepoByName(c.Repo.Repository.ID, branch) + if err != nil { + if !models.IsErrBranchNotExist(err) { + c.Handle(500, "GetProtectBranchOfRepoByName", err) + return + } + + // No options found, create defaults. + protectBranch = &models.ProtectBranch{ + Name: branch, + } + } + + if c.Repo.Owner.IsOrganization() { + users, err := c.Repo.Repository.GetWriters() + if err != nil { + c.Handle(500, "Repo.Repository.GetPushers", err) + return + } + c.Data["Users"] = users + c.Data["whitelist_users"] = protectBranch.WhitelistUserIDs + + teams, err := c.Repo.Owner.TeamsHaveAccessToRepo(c.Repo.Repository.ID, models.ACCESS_MODE_WRITE) + if err != nil { + c.Handle(500, "Repo.Owner.TeamsHaveAccessToRepo", err) + return + } + c.Data["Teams"] = teams + c.Data["whitelist_teams"] = protectBranch.WhitelistTeamIDs + } + + c.Data["Branch"] = protectBranch + c.HTML(200, SETTINGS_PROTECTED_BRANCH) +} + +func SettingsProtectedBranchPost(c *context.Context, f form.ProtectBranch) { + branch := c.Params("*") + if !c.Repo.GitRepo.IsBranchExist(branch) { + c.NotFound() + return + } + + protectBranch, err := models.GetProtectBranchOfRepoByName(c.Repo.Repository.ID, branch) + if err != nil { + if !models.IsErrBranchNotExist(err) { + c.Handle(500, "GetProtectBranchOfRepoByName", err) + return + } + + // No options found, create defaults. + protectBranch = &models.ProtectBranch{ + RepoID: c.Repo.Repository.ID, + Name: branch, + } + } + + protectBranch.Protected = f.Protected + protectBranch.RequirePullRequest = f.RequirePullRequest + protectBranch.EnableWhitelist = f.EnableWhitelist + if c.Repo.Owner.IsOrganization() { + err = models.UpdateOrgProtectBranch(c.Repo.Repository, protectBranch, f.WhitelistUsers, f.WhitelistTeams) + } else { + err = models.UpdateProtectBranch(protectBranch) + } + if err != nil { + c.Handle(500, "UpdateOrgProtectBranch/UpdateProtectBranch", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.update_protect_branch_success")) + c.Redirect(fmt.Sprintf("%s/settings/branches/%s", c.Repo.RepoLink, branch)) +} + +func SettingsGitHooks(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings.githooks") + c.Data["PageIsSettingsGitHooks"] = true + + hooks, err := c.Repo.GitRepo.Hooks() + if err != nil { + c.Handle(500, "Hooks", err) + return + } + c.Data["Hooks"] = hooks + + c.HTML(200, SETTINGS_GITHOOKS) +} + +func SettingsGitHooksEdit(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings.githooks") + c.Data["PageIsSettingsGitHooks"] = true + c.Data["RequireSimpleMDE"] = true + + name := c.Params(":name") + hook, err := c.Repo.GitRepo.GetHook(name) + if err != nil { + if err == git.ErrNotValidHook { + c.Handle(404, "GetHook", err) + } else { + c.Handle(500, "GetHook", err) + } + return + } + c.Data["Hook"] = hook + c.HTML(200, SETTINGS_GITHOOK_EDIT) +} + +func SettingsGitHooksEditPost(c *context.Context) { + name := c.Params(":name") + hook, err := c.Repo.GitRepo.GetHook(name) + if err != nil { + if err == git.ErrNotValidHook { + c.Handle(404, "GetHook", err) + } else { + c.Handle(500, "GetHook", err) + } + return + } + hook.Content = c.Query("content") + if err = hook.Update(); err != nil { + c.Handle(500, "hook.Update", err) + return + } + c.Redirect(c.Data["Link"].(string)) +} + +func SettingsDeployKeys(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings.deploy_keys") + c.Data["PageIsSettingsKeys"] = true + + keys, err := models.ListDeployKeys(c.Repo.Repository.ID) + if err != nil { + c.Handle(500, "ListDeployKeys", err) + return + } + c.Data["Deploykeys"] = keys + + c.HTML(200, SETTINGS_DEPLOY_KEYS) +} + +func SettingsDeployKeysPost(c *context.Context, f form.AddSSHKey) { + c.Data["Title"] = c.Tr("repo.settings.deploy_keys") + c.Data["PageIsSettingsKeys"] = true + + keys, err := models.ListDeployKeys(c.Repo.Repository.ID) + if err != nil { + c.Handle(500, "ListDeployKeys", err) + return + } + c.Data["Deploykeys"] = keys + + if c.HasError() { + c.HTML(200, SETTINGS_DEPLOY_KEYS) + return + } + + content, err := models.CheckPublicKeyString(f.Content) + if err != nil { + if models.IsErrKeyUnableVerify(err) { + c.Flash.Info(c.Tr("form.unable_verify_ssh_key")) + } else { + c.Data["HasError"] = true + c.Data["Err_Content"] = true + c.Flash.Error(c.Tr("form.invalid_ssh_key", err.Error())) + c.Redirect(c.Repo.RepoLink + "/settings/keys") + return + } + } + + key, err := models.AddDeployKey(c.Repo.Repository.ID, f.Title, content) + if err != nil { + c.Data["HasError"] = true + switch { + case models.IsErrKeyAlreadyExist(err): + c.Data["Err_Content"] = true + c.RenderWithErr(c.Tr("repo.settings.key_been_used"), SETTINGS_DEPLOY_KEYS, &f) + case models.IsErrKeyNameAlreadyUsed(err): + c.Data["Err_Title"] = true + c.RenderWithErr(c.Tr("repo.settings.key_name_used"), SETTINGS_DEPLOY_KEYS, &f) + default: + c.Handle(500, "AddDeployKey", err) + } + return + } + + log.Trace("Deploy key added: %d", c.Repo.Repository.ID) + c.Flash.Success(c.Tr("repo.settings.add_key_success", key.Name)) + c.Redirect(c.Repo.RepoLink + "/settings/keys") +} + +func DeleteDeployKey(c *context.Context) { + if err := models.DeleteDeployKey(c.User, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteDeployKey: " + err.Error()) + } else { + c.Flash.Success(c.Tr("repo.settings.deploy_key_deletion_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Repo.RepoLink + "/settings/keys", + }) +} diff --git a/routes/repo/view.go b/routes/repo/view.go new file mode 100644 index 00000000..1ea25d51 --- /dev/null +++ b/routes/repo/view.go @@ -0,0 +1,367 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "bytes" + "fmt" + gotemplate "html/template" + "io/ioutil" + "path" + "strings" + + "github.com/Unknwon/paginater" + log "gopkg.in/clog.v1" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/markup" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/template" + "github.com/gogits/gogs/pkg/template/highlight" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + BARE = "repo/bare" + HOME = "repo/home" + WATCHERS = "repo/watchers" + FORKS = "repo/forks" +) + +func renderDirectory(c *context.Context, treeLink string) { + tree, err := c.Repo.Commit.SubTree(c.Repo.TreePath) + if err != nil { + c.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err) + return + } + + entries, err := tree.ListEntries() + if err != nil { + c.ServerError("ListEntries", err) + return + } + entries.Sort() + + c.Data["Files"], err = entries.GetCommitsInfoWithCustomConcurrency(c.Repo.Commit, c.Repo.TreePath, setting.Repository.CommitsFetchConcurrency) + if err != nil { + c.ServerError("GetCommitsInfoWithCustomConcurrency", err) + return + } + + var readmeFile *git.Blob + for _, entry := range entries { + if entry.IsDir() || !markup.IsReadmeFile(entry.Name()) { + continue + } + + // TODO: collect all possible README files and show with priority. + readmeFile = entry.Blob() + break + } + + if readmeFile != nil { + c.Data["RawFileLink"] = "" + c.Data["ReadmeInList"] = true + c.Data["ReadmeExist"] = true + + dataRc, err := readmeFile.Data() + if err != nil { + c.ServerError("readmeFile.Data", err) + return + } + + buf := make([]byte, 1024) + n, _ := dataRc.Read(buf) + buf = buf[:n] + + isTextFile := tool.IsTextFile(buf) + c.Data["IsTextFile"] = isTextFile + c.Data["FileName"] = readmeFile.Name() + if isTextFile { + d, _ := ioutil.ReadAll(dataRc) + buf = append(buf, d...) + + switch markup.Detect(readmeFile.Name()) { + case markup.MARKDOWN: + c.Data["IsMarkdown"] = true + buf = markup.Markdown(buf, treeLink, c.Repo.Repository.ComposeMetas()) + case markup.ORG_MODE: + c.Data["IsMarkdown"] = true + buf = markup.OrgMode(buf, treeLink, c.Repo.Repository.ComposeMetas()) + case markup.IPYTHON_NOTEBOOK: + c.Data["IsIPythonNotebook"] = true + c.Data["RawFileLink"] = c.Repo.RepoLink + "/raw/" + path.Join(c.Repo.BranchName, c.Repo.TreePath, readmeFile.Name()) + default: + buf = bytes.Replace(buf, []byte("\n"), []byte(`
    `), -1) + } + c.Data["FileContent"] = string(buf) + } + } + + // Show latest commit info of repository in table header, + // or of directory if not in root directory. + latestCommit := c.Repo.Commit + if len(c.Repo.TreePath) > 0 { + latestCommit, err = c.Repo.Commit.GetCommitByPath(c.Repo.TreePath) + if err != nil { + c.ServerError("GetCommitByPath", err) + return + } + } + c.Data["LatestCommit"] = latestCommit + c.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) + + if c.Repo.CanEnableEditor() { + c.Data["CanAddFile"] = true + c.Data["CanUploadFile"] = setting.Repository.Upload.Enabled + } +} + +func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink string) { + c.Data["IsViewFile"] = true + + blob := entry.Blob() + dataRc, err := blob.Data() + if err != nil { + c.Handle(500, "Data", err) + return + } + + c.Data["FileSize"] = blob.Size() + c.Data["FileName"] = blob.Name() + c.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name()) + c.Data["RawFileLink"] = rawLink + "/" + c.Repo.TreePath + + buf := make([]byte, 1024) + n, _ := dataRc.Read(buf) + buf = buf[:n] + + isTextFile := tool.IsTextFile(buf) + c.Data["IsTextFile"] = isTextFile + + // Assume file is not editable first. + if !isTextFile { + c.Data["EditFileTooltip"] = c.Tr("repo.editor.cannot_edit_non_text_files") + } + + canEnableEditor := c.Repo.CanEnableEditor() + switch { + case isTextFile: + if blob.Size() >= setting.UI.MaxDisplayFileSize { + c.Data["IsFileTooLarge"] = true + break + } + + c.Data["ReadmeExist"] = markup.IsReadmeFile(blob.Name()) + + d, _ := ioutil.ReadAll(dataRc) + buf = append(buf, d...) + + switch markup.Detect(blob.Name()) { + case markup.MARKDOWN: + c.Data["IsMarkdown"] = true + c.Data["FileContent"] = string(markup.Markdown(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas())) + case markup.ORG_MODE: + c.Data["IsMarkdown"] = true + c.Data["FileContent"] = string(markup.OrgMode(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas())) + case markup.IPYTHON_NOTEBOOK: + c.Data["IsIPythonNotebook"] = true + default: + // Building code view blocks with line number on server side. + var fileContent string + if err, content := template.ToUTF8WithErr(buf); err != nil { + if err != nil { + log.Error(4, "ToUTF8WithErr: %s", err) + } + fileContent = string(buf) + } else { + fileContent = content + } + + var output bytes.Buffer + lines := strings.Split(fileContent, "\n") + for index, line := range lines { + output.WriteString(fmt.Sprintf(`
  • %s
  • `, index+1, index+1, gotemplate.HTMLEscapeString(strings.TrimRight(line, "\r"))) + "\n") + } + c.Data["FileContent"] = gotemplate.HTML(output.String()) + + output.Reset() + for i := 0; i < len(lines); i++ { + output.WriteString(fmt.Sprintf(`%d`, i+1, i+1)) + } + c.Data["LineNums"] = gotemplate.HTML(output.String()) + } + + if canEnableEditor { + c.Data["CanEditFile"] = true + c.Data["EditFileTooltip"] = c.Tr("repo.editor.edit_this_file") + } else if !c.Repo.IsViewBranch { + c.Data["EditFileTooltip"] = c.Tr("repo.editor.must_be_on_a_branch") + } else if !c.Repo.IsWriter() { + c.Data["EditFileTooltip"] = c.Tr("repo.editor.fork_before_edit") + } + + case tool.IsPDFFile(buf): + c.Data["IsPDFFile"] = true + case tool.IsVideoFile(buf): + c.Data["IsVideoFile"] = true + case tool.IsImageFile(buf): + c.Data["IsImageFile"] = true + } + + if canEnableEditor { + c.Data["CanDeleteFile"] = true + c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.delete_this_file") + } else if !c.Repo.IsViewBranch { + c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.must_be_on_a_branch") + } else if !c.Repo.IsWriter() { + c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.must_have_write_access") + } +} + +func setEditorconfigIfExists(c *context.Context) { + ec, err := c.Repo.GetEditorconfig() + if err != nil && !git.IsErrNotExist(err) { + log.Trace("setEditorconfigIfExists.GetEditorconfig [%d]: %v", c.Repo.Repository.ID, err) + return + } + c.Data["Editorconfig"] = ec +} + +func Home(c *context.Context) { + c.Data["PageIsViewFiles"] = true + + if c.Repo.Repository.IsBare { + c.HTML(200, BARE) + return + } + + title := c.Repo.Repository.Owner.Name + "/" + c.Repo.Repository.Name + if len(c.Repo.Repository.Description) > 0 { + title += ": " + c.Repo.Repository.Description + } + c.Data["Title"] = title + if c.Repo.BranchName != c.Repo.Repository.DefaultBranch { + c.Data["Title"] = title + " @ " + c.Repo.BranchName + } + c.Data["RequireHighlightJS"] = true + + branchLink := c.Repo.RepoLink + "/src/" + c.Repo.BranchName + treeLink := branchLink + rawLink := c.Repo.RepoLink + "/raw/" + c.Repo.BranchName + + isRootDir := false + if len(c.Repo.TreePath) > 0 { + treeLink += "/" + c.Repo.TreePath + } else { + isRootDir = true + + // Only show Git stats panel when view root directory + var err error + c.Repo.CommitsCount, err = c.Repo.Commit.CommitsCount() + if err != nil { + c.Handle(500, "CommitsCount", err) + return + } + c.Data["CommitsCount"] = c.Repo.CommitsCount + } + c.Data["PageIsRepoHome"] = isRootDir + + // Get current entry user currently looking at. + entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath) + if err != nil { + c.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err) + return + } + + if entry.IsDir() { + renderDirectory(c, treeLink) + } else { + renderFile(c, entry, treeLink, rawLink) + } + if c.Written() { + return + } + + setEditorconfigIfExists(c) + if c.Written() { + return + } + + var treeNames []string + paths := make([]string, 0, 5) + if len(c.Repo.TreePath) > 0 { + treeNames = strings.Split(c.Repo.TreePath, "/") + for i := range treeNames { + paths = append(paths, strings.Join(treeNames[:i+1], "/")) + } + + c.Data["HasParentPath"] = true + if len(paths)-2 >= 0 { + c.Data["ParentPath"] = "/" + paths[len(paths)-2] + } + } + + c.Data["Paths"] = paths + c.Data["TreeLink"] = treeLink + c.Data["TreeNames"] = treeNames + c.Data["BranchLink"] = branchLink + c.HTML(200, HOME) +} + +func RenderUserCards(c *context.Context, total int, getter func(page int) ([]*models.User, error), tpl string) { + page := c.QueryInt("page") + if page <= 0 { + page = 1 + } + pager := paginater.New(total, models.ItemsPerPage, page, 5) + c.Data["Page"] = pager + + items, err := getter(pager.Current()) + if err != nil { + c.Handle(500, "getter", err) + return + } + c.Data["Cards"] = items + + c.HTML(200, tpl) +} + +func Watchers(c *context.Context) { + c.Data["Title"] = c.Tr("repo.watchers") + c.Data["CardsTitle"] = c.Tr("repo.watchers") + c.Data["PageIsWatchers"] = true + RenderUserCards(c, c.Repo.Repository.NumWatches, c.Repo.Repository.GetWatchers, WATCHERS) +} + +func Stars(c *context.Context) { + c.Data["Title"] = c.Tr("repo.stargazers") + c.Data["CardsTitle"] = c.Tr("repo.stargazers") + c.Data["PageIsStargazers"] = true + RenderUserCards(c, c.Repo.Repository.NumStars, c.Repo.Repository.GetStargazers, WATCHERS) +} + +func Forks(c *context.Context) { + c.Data["Title"] = c.Tr("repos.forks") + + forks, err := c.Repo.Repository.GetForks() + if err != nil { + c.Handle(500, "GetForks", err) + return + } + + for _, fork := range forks { + if err = fork.GetOwner(); err != nil { + c.Handle(500, "GetOwner", err) + return + } + } + c.Data["Forks"] = forks + + c.HTML(200, FORKS) +} diff --git a/routes/repo/webhook.go b/routes/repo/webhook.go new file mode 100644 index 00000000..c572d446 --- /dev/null +++ b/routes/repo/webhook.go @@ -0,0 +1,558 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/Unknwon/com" + + git "github.com/gogits/git-module" + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + WEBHOOKS = "repo/settings/webhook/base" + WEBHOOK_NEW = "repo/settings/webhook/new" + ORG_WEBHOOK_NEW = "org/settings/webhook_new" +) + +func Webhooks(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings.hooks") + c.Data["PageIsSettingsHooks"] = true + c.Data["BaseLink"] = c.Repo.RepoLink + c.Data["Description"] = c.Tr("repo.settings.hooks_desc", "https://github.com/gogits/go-gogs-client/wiki/Repositories-Webhooks") + c.Data["Types"] = setting.Webhook.Types + + ws, err := models.GetWebhooksByRepoID(c.Repo.Repository.ID) + if err != nil { + c.Handle(500, "GetWebhooksByRepoID", err) + return + } + c.Data["Webhooks"] = ws + + c.HTML(200, WEBHOOKS) +} + +type OrgRepoCtx struct { + OrgID int64 + RepoID int64 + Link string + NewTemplate string +} + +// getOrgRepoCtx determines whether this is a repo context or organization context. +func getOrgRepoCtx(c *context.Context) (*OrgRepoCtx, error) { + if len(c.Repo.RepoLink) > 0 { + c.Data["PageIsRepositoryContext"] = true + return &OrgRepoCtx{ + RepoID: c.Repo.Repository.ID, + Link: c.Repo.RepoLink, + NewTemplate: WEBHOOK_NEW, + }, nil + } + + if len(c.Org.OrgLink) > 0 { + c.Data["PageIsOrganizationContext"] = true + return &OrgRepoCtx{ + OrgID: c.Org.Organization.ID, + Link: c.Org.OrgLink, + NewTemplate: ORG_WEBHOOK_NEW, + }, nil + } + + return nil, errors.New("Unable to set OrgRepo context") +} + +func checkHookType(c *context.Context) string { + hookType := strings.ToLower(c.Params(":type")) + if !com.IsSliceContainsStr(setting.Webhook.Types, hookType) { + c.Handle(404, "checkHookType", nil) + return "" + } + return hookType +} + +func WebhooksNew(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings.add_webhook") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksNew"] = true + c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} + + orCtx, err := getOrgRepoCtx(c) + if err != nil { + c.Handle(500, "getOrgRepoCtx", err) + return + } + + c.Data["HookType"] = checkHookType(c) + if c.Written() { + return + } + c.Data["BaseLink"] = orCtx.Link + + c.HTML(200, orCtx.NewTemplate) +} + +func ParseHookEvent(f form.Webhook) *models.HookEvent { + return &models.HookEvent{ + PushOnly: f.PushOnly(), + SendEverything: f.SendEverything(), + ChooseEvents: f.ChooseEvents(), + HookEvents: models.HookEvents{ + Create: f.Create, + Delete: f.Delete, + Fork: f.Fork, + Push: f.Push, + Issues: f.Issues, + IssueComment: f.IssueComment, + PullRequest: f.PullRequest, + Release: f.Release, + }, + } +} + +func WebHooksNewPost(c *context.Context, f form.NewWebhook) { + c.Data["Title"] = c.Tr("repo.settings.add_webhook") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksNew"] = true + c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} + c.Data["HookType"] = "gogs" + + orCtx, err := getOrgRepoCtx(c) + if err != nil { + c.Handle(500, "getOrgRepoCtx", err) + return + } + c.Data["BaseLink"] = orCtx.Link + + if c.HasError() { + c.HTML(200, orCtx.NewTemplate) + return + } + + contentType := models.JSON + if models.HookContentType(f.ContentType) == models.FORM { + contentType = models.FORM + } + + w := &models.Webhook{ + RepoID: orCtx.RepoID, + URL: f.PayloadURL, + ContentType: contentType, + Secret: f.Secret, + HookEvent: ParseHookEvent(f.Webhook), + IsActive: f.Active, + HookTaskType: models.GOGS, + OrgID: orCtx.OrgID, + } + if err := w.UpdateEvent(); err != nil { + c.Handle(500, "UpdateEvent", err) + return + } else if err := models.CreateWebhook(w); err != nil { + c.Handle(500, "CreateWebhook", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.add_hook_success")) + c.Redirect(orCtx.Link + "/settings/hooks") +} + +func SlackHooksNewPost(c *context.Context, f form.NewSlackHook) { + c.Data["Title"] = c.Tr("repo.settings") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksNew"] = true + c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} + + orCtx, err := getOrgRepoCtx(c) + if err != nil { + c.Handle(500, "getOrgRepoCtx", err) + return + } + + if c.HasError() { + c.HTML(200, orCtx.NewTemplate) + return + } + + meta, err := json.Marshal(&models.SlackMeta{ + Channel: f.Channel, + Username: f.Username, + IconURL: f.IconURL, + Color: f.Color, + }) + if err != nil { + c.Handle(500, "Marshal", err) + return + } + + w := &models.Webhook{ + RepoID: orCtx.RepoID, + URL: f.PayloadURL, + ContentType: models.JSON, + HookEvent: ParseHookEvent(f.Webhook), + IsActive: f.Active, + HookTaskType: models.SLACK, + Meta: string(meta), + OrgID: orCtx.OrgID, + } + if err := w.UpdateEvent(); err != nil { + c.Handle(500, "UpdateEvent", err) + return + } else if err := models.CreateWebhook(w); err != nil { + c.Handle(500, "CreateWebhook", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.add_hook_success")) + c.Redirect(orCtx.Link + "/settings/hooks") +} + +// FIXME: merge logic to Slack +func DiscordHooksNewPost(c *context.Context, f form.NewDiscordHook) { + c.Data["Title"] = c.Tr("repo.settings") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksNew"] = true + c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} + + orCtx, err := getOrgRepoCtx(c) + if err != nil { + c.Handle(500, "getOrgRepoCtx", err) + return + } + + if c.HasError() { + c.HTML(200, orCtx.NewTemplate) + return + } + + meta, err := json.Marshal(&models.SlackMeta{ + Username: f.Username, + IconURL: f.IconURL, + Color: f.Color, + }) + if err != nil { + c.Handle(500, "Marshal", err) + return + } + + w := &models.Webhook{ + RepoID: orCtx.RepoID, + URL: f.PayloadURL, + ContentType: models.JSON, + HookEvent: ParseHookEvent(f.Webhook), + IsActive: f.Active, + HookTaskType: models.DISCORD, + Meta: string(meta), + OrgID: orCtx.OrgID, + } + if err := w.UpdateEvent(); err != nil { + c.Handle(500, "UpdateEvent", err) + return + } else if err := models.CreateWebhook(w); err != nil { + c.Handle(500, "CreateWebhook", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.add_hook_success")) + c.Redirect(orCtx.Link + "/settings/hooks") +} + +func checkWebhook(c *context.Context) (*OrgRepoCtx, *models.Webhook) { + c.Data["RequireHighlightJS"] = true + + orCtx, err := getOrgRepoCtx(c) + if err != nil { + c.Handle(500, "getOrgRepoCtx", err) + return nil, nil + } + c.Data["BaseLink"] = orCtx.Link + + var w *models.Webhook + if orCtx.RepoID > 0 { + w, err = models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + } else { + w, err = models.GetWebhookByOrgID(c.Org.Organization.ID, c.ParamsInt64(":id")) + } + if err != nil { + c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err) + return nil, nil + } + + switch w.HookTaskType { + case models.SLACK: + c.Data["SlackHook"] = w.GetSlackHook() + c.Data["HookType"] = "slack" + case models.DISCORD: + c.Data["SlackHook"] = w.GetSlackHook() + c.Data["HookType"] = "discord" + default: + c.Data["HookType"] = "gogs" + } + + c.Data["History"], err = w.History(1) + if err != nil { + c.Handle(500, "History", err) + } + return orCtx, w +} + +func WebHooksEdit(c *context.Context) { + c.Data["Title"] = c.Tr("repo.settings.update_webhook") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksEdit"] = true + + orCtx, w := checkWebhook(c) + if c.Written() { + return + } + c.Data["Webhook"] = w + + c.HTML(200, orCtx.NewTemplate) +} + +func WebHooksEditPost(c *context.Context, f form.NewWebhook) { + c.Data["Title"] = c.Tr("repo.settings.update_webhook") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksEdit"] = true + + orCtx, w := checkWebhook(c) + if c.Written() { + return + } + c.Data["Webhook"] = w + + if c.HasError() { + c.HTML(200, orCtx.NewTemplate) + return + } + + contentType := models.JSON + if models.HookContentType(f.ContentType) == models.FORM { + contentType = models.FORM + } + + w.URL = f.PayloadURL + w.ContentType = contentType + w.Secret = f.Secret + w.HookEvent = ParseHookEvent(f.Webhook) + w.IsActive = f.Active + if err := w.UpdateEvent(); err != nil { + c.Handle(500, "UpdateEvent", err) + return + } else if err := models.UpdateWebhook(w); err != nil { + c.Handle(500, "WebHooksEditPost", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.update_hook_success")) + c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) +} + +func SlackHooksEditPost(c *context.Context, f form.NewSlackHook) { + c.Data["Title"] = c.Tr("repo.settings") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksEdit"] = true + + orCtx, w := checkWebhook(c) + if c.Written() { + return + } + c.Data["Webhook"] = w + + if c.HasError() { + c.HTML(200, orCtx.NewTemplate) + return + } + + meta, err := json.Marshal(&models.SlackMeta{ + Channel: f.Channel, + Username: f.Username, + IconURL: f.IconURL, + Color: f.Color, + }) + if err != nil { + c.Handle(500, "Marshal", err) + return + } + + w.URL = f.PayloadURL + w.Meta = string(meta) + w.HookEvent = ParseHookEvent(f.Webhook) + w.IsActive = f.Active + if err := w.UpdateEvent(); err != nil { + c.Handle(500, "UpdateEvent", err) + return + } else if err := models.UpdateWebhook(w); err != nil { + c.Handle(500, "UpdateWebhook", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.update_hook_success")) + c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) +} + +// FIXME: merge logic to Slack +func DiscordHooksEditPost(c *context.Context, f form.NewDiscordHook) { + c.Data["Title"] = c.Tr("repo.settings") + c.Data["PageIsSettingsHooks"] = true + c.Data["PageIsSettingsHooksEdit"] = true + + orCtx, w := checkWebhook(c) + if c.Written() { + return + } + c.Data["Webhook"] = w + + if c.HasError() { + c.HTML(200, orCtx.NewTemplate) + return + } + + meta, err := json.Marshal(&models.SlackMeta{ + Username: f.Username, + IconURL: f.IconURL, + Color: f.Color, + }) + if err != nil { + c.Handle(500, "Marshal", err) + return + } + + w.URL = f.PayloadURL + w.Meta = string(meta) + w.HookEvent = ParseHookEvent(f.Webhook) + w.IsActive = f.Active + if err := w.UpdateEvent(); err != nil { + c.Handle(500, "UpdateEvent", err) + return + } else if err := models.UpdateWebhook(w); err != nil { + c.Handle(500, "UpdateWebhook", err) + return + } + + c.Flash.Success(c.Tr("repo.settings.update_hook_success")) + c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) +} + +func TestWebhook(c *context.Context) { + var authorUsername, committerUsername string + + // Grab latest commit or fake one if it's empty repository. + commit := c.Repo.Commit + if commit == nil { + ghost := models.NewGhostUser() + commit = &git.Commit{ + ID: git.MustIDFromString(git.EMPTY_SHA), + Author: ghost.NewGitSig(), + Committer: ghost.NewGitSig(), + CommitMessage: "This is a fake commit", + } + authorUsername = ghost.Name + committerUsername = ghost.Name + } else { + // Try to match email with a real user. + author, err := models.GetUserByEmail(commit.Author.Email) + if err == nil { + authorUsername = author.Name + } else if !errors.IsUserNotExist(err) { + c.Handle(500, "GetUserByEmail.(author)", err) + return + } + + committer, err := models.GetUserByEmail(commit.Committer.Email) + if err == nil { + committerUsername = committer.Name + } else if !errors.IsUserNotExist(err) { + c.Handle(500, "GetUserByEmail.(committer)", err) + return + } + } + + fileStatus, err := commit.FileStatus() + if err != nil { + c.Handle(500, "FileStatus", err) + return + } + + apiUser := c.User.APIFormat() + p := &api.PushPayload{ + Ref: git.BRANCH_PREFIX + c.Repo.Repository.DefaultBranch, + Before: commit.ID.String(), + After: commit.ID.String(), + Commits: []*api.PayloadCommit{ + { + ID: commit.ID.String(), + Message: commit.Message(), + URL: c.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(), + Author: &api.PayloadUser{ + Name: commit.Author.Name, + Email: commit.Author.Email, + UserName: authorUsername, + }, + Committer: &api.PayloadUser{ + Name: commit.Committer.Name, + Email: commit.Committer.Email, + UserName: committerUsername, + }, + Added: fileStatus.Added, + Removed: fileStatus.Removed, + Modified: fileStatus.Modified, + }, + }, + Repo: c.Repo.Repository.APIFormat(nil), + Pusher: apiUser, + Sender: apiUser, + } + if err := models.TestWebhook(c.Repo.Repository, models.HOOK_EVENT_PUSH, p, c.ParamsInt64("id")); err != nil { + c.Handle(500, "TestWebhook", err) + } else { + c.Flash.Info(c.Tr("repo.settings.webhook.test_delivery_success")) + c.Status(200) + } +} + +func RedeliveryWebhook(c *context.Context) { + webhook, err := models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) + if err != nil { + c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err) + return + } + + hookTask, err := models.GetHookTaskOfWebhookByUUID(webhook.ID, c.Query("uuid")) + if err != nil { + c.NotFoundOrServerError("GetHookTaskOfWebhookByUUID/GetWebhookByOrgID", errors.IsHookTaskNotExist, err) + return + } + + hookTask.IsDelivered = false + if err = models.UpdateHookTask(hookTask); err != nil { + c.Handle(500, "UpdateHookTask", err) + } else { + go models.HookQueue.Add(c.Repo.Repository.ID) + c.Flash.Info(c.Tr("repo.settings.webhook.redelivery_success", hookTask.UUID)) + c.Status(200) + } +} + +func DeleteWebhook(c *context.Context) { + if err := models.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) + } else { + c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success")) + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Repo.RepoLink + "/settings/hooks", + }) +} diff --git a/routes/repo/wiki.go b/routes/repo/wiki.go new file mode 100644 index 00000000..ad2cfbae --- /dev/null +++ b/routes/repo/wiki.go @@ -0,0 +1,274 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "io/ioutil" + "strings" + "time" + + "github.com/gogits/git-module" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/markup" +) + +const ( + WIKI_START = "repo/wiki/start" + WIKI_VIEW = "repo/wiki/view" + WIKI_NEW = "repo/wiki/new" + WIKI_PAGES = "repo/wiki/pages" +) + +func MustEnableWiki(c *context.Context) { + if !c.Repo.Repository.EnableWiki { + c.Handle(404, "MustEnableWiki", nil) + return + } + + if c.Repo.Repository.EnableExternalWiki { + c.Redirect(c.Repo.Repository.ExternalWikiURL) + return + } +} + +type PageMeta struct { + Name string + URL string + Updated time.Time +} + +func renderWikiPage(c *context.Context, isViewPage bool) (*git.Repository, string) { + wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath()) + if err != nil { + c.Handle(500, "OpenRepository", err) + return nil, "" + } + commit, err := wikiRepo.GetBranchCommit("master") + if err != nil { + c.Handle(500, "GetBranchCommit", err) + return nil, "" + } + + // Get page list. + if isViewPage { + entries, err := commit.ListEntries() + if err != nil { + c.Handle(500, "ListEntries", err) + return nil, "" + } + pages := make([]PageMeta, 0, len(entries)) + for i := range entries { + if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") { + name := strings.TrimSuffix(entries[i].Name(), ".md") + pages = append(pages, PageMeta{ + Name: name, + URL: models.ToWikiPageURL(name), + }) + } + } + c.Data["Pages"] = pages + } + + pageURL := c.Params(":page") + if len(pageURL) == 0 { + pageURL = "Home" + } + c.Data["PageURL"] = pageURL + + pageName := models.ToWikiPageName(pageURL) + c.Data["old_title"] = pageName + c.Data["Title"] = pageName + c.Data["title"] = pageName + c.Data["RequireHighlightJS"] = true + + blob, err := commit.GetBlobByPath(pageName + ".md") + if err != nil { + if git.IsErrNotExist(err) { + c.Redirect(c.Repo.RepoLink + "/wiki/_pages") + } else { + c.Handle(500, "GetBlobByPath", err) + } + return nil, "" + } + r, err := blob.Data() + if err != nil { + c.Handle(500, "Data", err) + return nil, "" + } + data, err := ioutil.ReadAll(r) + if err != nil { + c.Handle(500, "ReadAll", err) + return nil, "" + } + if isViewPage { + c.Data["content"] = string(markup.Markdown(data, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas())) + } else { + c.Data["content"] = string(data) + } + + return wikiRepo, pageName +} + +func Wiki(c *context.Context) { + c.Data["PageIsWiki"] = true + + if !c.Repo.Repository.HasWiki() { + c.Data["Title"] = c.Tr("repo.wiki") + c.HTML(200, WIKI_START) + return + } + + wikiRepo, pageName := renderWikiPage(c, true) + if c.Written() { + return + } + + // Get last change information. + lastCommit, err := wikiRepo.GetCommitByPath(pageName + ".md") + if err != nil { + c.Handle(500, "GetCommitByPath", err) + return + } + c.Data["Author"] = lastCommit.Author + + c.HTML(200, WIKI_VIEW) +} + +func WikiPages(c *context.Context) { + c.Data["Title"] = c.Tr("repo.wiki.pages") + c.Data["PageIsWiki"] = true + + if !c.Repo.Repository.HasWiki() { + c.Redirect(c.Repo.RepoLink + "/wiki") + return + } + + wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath()) + if err != nil { + c.Handle(500, "OpenRepository", err) + return + } + commit, err := wikiRepo.GetBranchCommit("master") + if err != nil { + c.Handle(500, "GetBranchCommit", err) + return + } + + entries, err := commit.ListEntries() + if err != nil { + c.Handle(500, "ListEntries", err) + return + } + pages := make([]PageMeta, 0, len(entries)) + for i := range entries { + if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") { + commit, err := wikiRepo.GetCommitByPath(entries[i].Name()) + if err != nil { + c.ServerError("GetCommitByPath", err) + return + } + name := strings.TrimSuffix(entries[i].Name(), ".md") + pages = append(pages, PageMeta{ + Name: name, + URL: models.ToWikiPageURL(name), + Updated: commit.Author.When, + }) + } + } + c.Data["Pages"] = pages + + c.HTML(200, WIKI_PAGES) +} + +func NewWiki(c *context.Context) { + c.Data["Title"] = c.Tr("repo.wiki.new_page") + c.Data["PageIsWiki"] = true + c.Data["RequireSimpleMDE"] = true + + if !c.Repo.Repository.HasWiki() { + c.Data["title"] = "Home" + } + + c.HTML(200, WIKI_NEW) +} + +func NewWikiPost(c *context.Context, f form.NewWiki) { + c.Data["Title"] = c.Tr("repo.wiki.new_page") + c.Data["PageIsWiki"] = true + c.Data["RequireSimpleMDE"] = true + + if c.HasError() { + c.HTML(200, WIKI_NEW) + return + } + + if err := c.Repo.Repository.AddWikiPage(c.User, f.Title, f.Content, f.Message); err != nil { + if models.IsErrWikiAlreadyExist(err) { + c.Data["Err_Title"] = true + c.RenderWithErr(c.Tr("repo.wiki.page_already_exists"), WIKI_NEW, &f) + } else { + c.Handle(500, "AddWikiPage", err) + } + return + } + + c.Redirect(c.Repo.RepoLink + "/wiki/" + models.ToWikiPageURL(models.ToWikiPageName(f.Title))) +} + +func EditWiki(c *context.Context) { + c.Data["PageIsWiki"] = true + c.Data["PageIsWikiEdit"] = true + c.Data["RequireSimpleMDE"] = true + + if !c.Repo.Repository.HasWiki() { + c.Redirect(c.Repo.RepoLink + "/wiki") + return + } + + renderWikiPage(c, false) + if c.Written() { + return + } + + c.HTML(200, WIKI_NEW) +} + +func EditWikiPost(c *context.Context, f form.NewWiki) { + c.Data["Title"] = c.Tr("repo.wiki.new_page") + c.Data["PageIsWiki"] = true + c.Data["RequireSimpleMDE"] = true + + if c.HasError() { + c.HTML(200, WIKI_NEW) + return + } + + if err := c.Repo.Repository.EditWikiPage(c.User, f.OldTitle, f.Title, f.Content, f.Message); err != nil { + c.Handle(500, "EditWikiPage", err) + return + } + + c.Redirect(c.Repo.RepoLink + "/wiki/" + models.ToWikiPageURL(models.ToWikiPageName(f.Title))) +} + +func DeleteWikiPagePost(c *context.Context) { + pageURL := c.Params(":page") + if len(pageURL) == 0 { + pageURL = "Home" + } + + pageName := models.ToWikiPageName(pageURL) + if err := c.Repo.Repository.DeleteWikiPage(c.User, pageName); err != nil { + c.Handle(500, "DeleteWikiPage", err) + return + } + + c.JSON(200, map[string]interface{}{ + "redirect": c.Repo.RepoLink + "/wiki/", + }) +} diff --git a/routes/user/auth.go b/routes/user/auth.go new file mode 100644 index 00000000..34fdbd85 --- /dev/null +++ b/routes/user/auth.go @@ -0,0 +1,534 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "fmt" + "net/url" + + "github.com/go-macaron/captcha" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/mailer" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + LOGIN = "user/auth/login" + TWO_FACTOR = "user/auth/two_factor" + TWO_FACTOR_RECOVERY_CODE = "user/auth/two_factor_recovery_code" + SIGNUP = "user/auth/signup" + ACTIVATE = "user/auth/activate" + FORGOT_PASSWORD = "user/auth/forgot_passwd" + RESET_PASSWORD = "user/auth/reset_passwd" +) + +// AutoLogin reads cookie and try to auto-login. +func AutoLogin(c *context.Context) (bool, error) { + if !models.HasEngine { + return false, nil + } + + uname := c.GetCookie(setting.CookieUserName) + if len(uname) == 0 { + return false, nil + } + + isSucceed := false + defer func() { + if !isSucceed { + log.Trace("auto-login cookie cleared: %s", uname) + c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) + c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) + c.SetCookie(setting.LoginStatusCookieName, "", -1, setting.AppSubURL) + } + }() + + u, err := models.GetUserByName(uname) + if err != nil { + if !errors.IsUserNotExist(err) { + return false, fmt.Errorf("GetUserByName: %v", err) + } + return false, nil + } + + if val, ok := c.GetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName); !ok || val != u.Name { + return false, nil + } + + isSucceed = true + c.Session.Set("uid", u.ID) + c.Session.Set("uname", u.Name) + c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) + if setting.EnableLoginStatusCookie { + c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL) + } + return true, nil +} + +// isValidRedirect returns false if the URL does not redirect to same site. +// False: //url, http://url +// True: /url +func isValidRedirect(url string) bool { + return len(url) >= 2 && url[0] == '/' && url[1] != '/' +} + +func Login(c *context.Context) { + c.Data["Title"] = c.Tr("sign_in") + + // Check auto-login. + isSucceed, err := AutoLogin(c) + if err != nil { + c.Handle(500, "AutoLogin", err) + return + } + + redirectTo := c.Query("redirect_to") + if len(redirectTo) > 0 { + c.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL) + } else { + redirectTo, _ = url.QueryUnescape(c.GetCookie("redirect_to")) + } + c.SetCookie("redirect_to", "", -1, setting.AppSubURL) + + if isSucceed { + if isValidRedirect(redirectTo) { + c.Redirect(redirectTo) + } else { + c.Redirect(setting.AppSubURL + "/") + } + return + } + + c.HTML(200, LOGIN) +} + +func afterLogin(c *context.Context, u *models.User, remember bool) { + if remember { + days := 86400 * setting.LoginRememberDays + c.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true) + c.SetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true) + } + + c.Session.Set("uid", u.ID) + c.Session.Set("uname", u.Name) + c.Session.Delete("twoFactorRemember") + c.Session.Delete("twoFactorUserID") + + // Clear whatever CSRF has right now, force to generate a new one + c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) + if setting.EnableLoginStatusCookie { + c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL) + } + + redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")) + c.SetCookie("redirect_to", "", -1, setting.AppSubURL) + if isValidRedirect(redirectTo) { + c.Redirect(redirectTo) + return + } + + c.Redirect(setting.AppSubURL + "/") +} + +func LoginPost(c *context.Context, f form.SignIn) { + c.Data["Title"] = c.Tr("sign_in") + + if c.HasError() { + c.Success(LOGIN) + return + } + + u, err := models.UserSignIn(f.UserName, f.Password) + if err != nil { + if errors.IsUserNotExist(err) { + c.RenderWithErr(c.Tr("form.username_password_incorrect"), LOGIN, &f) + } else { + c.ServerError("UserSignIn", err) + } + return + } + + if !u.IsEnabledTwoFactor() { + afterLogin(c, u, f.Remember) + return + } + + c.Session.Set("twoFactorRemember", f.Remember) + c.Session.Set("twoFactorUserID", u.ID) + c.Redirect(setting.AppSubURL + "/user/login/two_factor") +} + +func LoginTwoFactor(c *context.Context) { + _, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + c.Success(TWO_FACTOR) +} + +func LoginTwoFactorPost(c *context.Context) { + userID, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + t, err := models.GetTwoFactorByUserID(userID) + if err != nil { + c.ServerError("GetTwoFactorByUserID", err) + return + } + valid, err := t.ValidateTOTP(c.Query("passcode")) + if err != nil { + c.ServerError("ValidateTOTP", err) + return + } else if !valid { + c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode")) + c.Redirect(setting.AppSubURL + "/user/login/two_factor") + return + } + + u, err := models.GetUserByID(userID) + if err != nil { + c.ServerError("GetUserByID", err) + return + } + afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) +} + +func LoginTwoFactorRecoveryCode(c *context.Context) { + _, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + c.Success(TWO_FACTOR_RECOVERY_CODE) +} + +func LoginTwoFactorRecoveryCodePost(c *context.Context) { + userID, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + if err := models.UseRecoveryCode(userID, c.Query("recovery_code")); err != nil { + if errors.IsTwoFactorRecoveryCodeNotFound(err) { + c.Flash.Error(c.Tr("auth.login_two_factor_invalid_recovery_code")) + c.Redirect(setting.AppSubURL + "/user/login/two_factor_recovery_code") + } else { + c.ServerError("UseRecoveryCode", err) + } + return + } + + u, err := models.GetUserByID(userID) + if err != nil { + c.ServerError("GetUserByID", err) + return + } + afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) +} + +func SignOut(c *context.Context) { + c.Session.Delete("uid") + c.Session.Delete("uname") + c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) + c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) + c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) + c.Redirect(setting.AppSubURL + "/") +} + +func SignUp(c *context.Context) { + c.Data["Title"] = c.Tr("sign_up") + + c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + + if setting.Service.DisableRegistration { + c.Data["DisableRegistration"] = true + c.HTML(200, SIGNUP) + return + } + + c.HTML(200, SIGNUP) +} + +func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) { + c.Data["Title"] = c.Tr("sign_up") + + c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + + if setting.Service.DisableRegistration { + c.Error(403) + return + } + + if c.HasError() { + c.HTML(200, SIGNUP) + return + } + + if setting.Service.EnableCaptcha && !cpt.VerifyReq(c.Req) { + c.Data["Err_Captcha"] = true + c.RenderWithErr(c.Tr("form.captcha_incorrect"), SIGNUP, &f) + return + } + + if f.Password != f.Retype { + c.Data["Err_Password"] = true + c.RenderWithErr(c.Tr("form.password_not_match"), SIGNUP, &f) + return + } + + u := &models.User{ + Name: f.UserName, + Email: f.Email, + Passwd: f.Password, + IsActive: !setting.Service.RegisterEmailConfirm, + } + if err := models.CreateUser(u); err != nil { + switch { + case models.IsErrUserAlreadyExist(err): + c.Data["Err_UserName"] = true + c.RenderWithErr(c.Tr("form.username_been_taken"), SIGNUP, &f) + case models.IsErrEmailAlreadyUsed(err): + c.Data["Err_Email"] = true + c.RenderWithErr(c.Tr("form.email_been_used"), SIGNUP, &f) + case models.IsErrNameReserved(err): + c.Data["Err_UserName"] = true + c.RenderWithErr(c.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), SIGNUP, &f) + case models.IsErrNamePatternNotAllowed(err): + c.Data["Err_UserName"] = true + c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), SIGNUP, &f) + default: + c.Handle(500, "CreateUser", err) + } + return + } + log.Trace("Account created: %s", u.Name) + + // Auto-set admin for the only user. + if models.CountUsers() == 1 { + u.IsAdmin = true + u.IsActive = true + if err := models.UpdateUser(u); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + } + + // Send confirmation email, no need for social account. + if setting.Service.RegisterEmailConfirm && u.ID > 1 { + mailer.SendActivateAccountMail(c.Context, models.NewMailerUser(u)) + c.Data["IsSendRegisterMail"] = true + c.Data["Email"] = u.Email + c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 + c.HTML(200, ACTIVATE) + + if err := c.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { + log.Error(4, "Set cache(MailResendLimit) fail: %v", err) + } + return + } + + c.Redirect(setting.AppSubURL + "/user/login") +} + +func Activate(c *context.Context) { + code := c.Query("code") + if len(code) == 0 { + c.Data["IsActivatePage"] = true + if c.User.IsActive { + c.Error(404) + return + } + // Resend confirmation email. + if setting.Service.RegisterEmailConfirm { + if c.Cache.IsExist("MailResendLimit_" + c.User.LowerName) { + c.Data["ResendLimited"] = true + } else { + c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 + mailer.SendActivateAccountMail(c.Context, models.NewMailerUser(c.User)) + + keyName := "MailResendLimit_" + c.User.LowerName + if err := c.Cache.Put(keyName, c.User.LowerName, 180); err != nil { + log.Error(2, "Set cache '%s' fail: %v", keyName, err) + } + } + } else { + c.Data["ServiceNotEnabled"] = true + } + c.HTML(200, ACTIVATE) + return + } + + // Verify code. + if user := models.VerifyUserActiveCode(code); user != nil { + user.IsActive = true + var err error + if user.Rands, err = models.GetUserSalt(); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + if err := models.UpdateUser(user); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + + log.Trace("User activated: %s", user.Name) + + c.Session.Set("uid", user.ID) + c.Session.Set("uname", user.Name) + c.Redirect(setting.AppSubURL + "/") + return + } + + c.Data["IsActivateFailed"] = true + c.HTML(200, ACTIVATE) +} + +func ActivateEmail(c *context.Context) { + code := c.Query("code") + email_string := c.Query("email") + + // Verify code. + if email := models.VerifyActiveEmailCode(code, email_string); email != nil { + if err := email.Activate(); err != nil { + c.Handle(500, "ActivateEmail", err) + } + + log.Trace("Email activated: %s", email.Email) + c.Flash.Success(c.Tr("settings.add_email_success")) + } + + c.Redirect(setting.AppSubURL + "/user/settings/email") + return +} + +func ForgotPasswd(c *context.Context) { + c.Data["Title"] = c.Tr("auth.forgot_password") + + if setting.MailService == nil { + c.Data["IsResetDisable"] = true + c.HTML(200, FORGOT_PASSWORD) + return + } + + c.Data["IsResetRequest"] = true + c.HTML(200, FORGOT_PASSWORD) +} + +func ForgotPasswdPost(c *context.Context) { + c.Data["Title"] = c.Tr("auth.forgot_password") + + if setting.MailService == nil { + c.Handle(403, "ForgotPasswdPost", nil) + return + } + c.Data["IsResetRequest"] = true + + email := c.Query("email") + c.Data["Email"] = email + + u, err := models.GetUserByEmail(email) + if err != nil { + if errors.IsUserNotExist(err) { + c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 + c.Data["IsResetSent"] = true + c.HTML(200, FORGOT_PASSWORD) + return + } else { + c.Handle(500, "user.ResetPasswd(check existence)", err) + } + return + } + + if !u.IsLocal() { + c.Data["Err_Email"] = true + c.RenderWithErr(c.Tr("auth.non_local_account"), FORGOT_PASSWORD, nil) + return + } + + if c.Cache.IsExist("MailResendLimit_" + u.LowerName) { + c.Data["ResendLimited"] = true + c.HTML(200, FORGOT_PASSWORD) + return + } + + mailer.SendResetPasswordMail(c.Context, models.NewMailerUser(u)) + if err = c.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { + log.Error(4, "Set cache(MailResendLimit) fail: %v", err) + } + + c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 + c.Data["IsResetSent"] = true + c.HTML(200, FORGOT_PASSWORD) +} + +func ResetPasswd(c *context.Context) { + c.Data["Title"] = c.Tr("auth.reset_password") + + code := c.Query("code") + if len(code) == 0 { + c.Error(404) + return + } + c.Data["Code"] = code + c.Data["IsResetForm"] = true + c.HTML(200, RESET_PASSWORD) +} + +func ResetPasswdPost(c *context.Context) { + c.Data["Title"] = c.Tr("auth.reset_password") + + code := c.Query("code") + if len(code) == 0 { + c.Error(404) + return + } + c.Data["Code"] = code + + if u := models.VerifyUserActiveCode(code); u != nil { + // Validate password length. + passwd := c.Query("password") + if len(passwd) < 6 { + c.Data["IsResetForm"] = true + c.Data["Err_Password"] = true + c.RenderWithErr(c.Tr("auth.password_too_short"), RESET_PASSWORD, nil) + return + } + + u.Passwd = passwd + var err error + if u.Rands, err = models.GetUserSalt(); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + if u.Salt, err = models.GetUserSalt(); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + u.EncodePasswd() + if err := models.UpdateUser(u); err != nil { + c.Handle(500, "UpdateUser", err) + return + } + + log.Trace("User password reset: %s", u.Name) + c.Redirect(setting.AppSubURL + "/user/login") + return + } + + c.Data["IsResetFailed"] = true + c.HTML(200, RESET_PASSWORD) +} diff --git a/routes/user/home.go b/routes/user/home.go new file mode 100644 index 00000000..c3b9b182 --- /dev/null +++ b/routes/user/home.go @@ -0,0 +1,424 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "bytes" + "fmt" + + "github.com/Unknwon/com" + "github.com/Unknwon/paginater" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" +) + +const ( + DASHBOARD = "user/dashboard/dashboard" + NEWS_FEED = "user/dashboard/feeds" + ISSUES = "user/dashboard/issues" + PROFILE = "user/profile" + ORG_HOME = "org/home" +) + +// getDashboardContextUser finds out dashboard is viewing as which context user. +func getDashboardContextUser(c *context.Context) *models.User { + ctxUser := c.User + orgName := c.Params(":org") + if len(orgName) > 0 { + // Organization. + org, err := models.GetUserByName(orgName) + if err != nil { + c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) + return nil + } + ctxUser = org + } + c.Data["ContextUser"] = ctxUser + + if err := c.User.GetOrganizations(true); err != nil { + c.Handle(500, "GetOrganizations", err) + return nil + } + c.Data["Orgs"] = c.User.Orgs + + return ctxUser +} + +// retrieveFeeds loads feeds from database by given context user. +// The user could be organization so it is not always the logged in user, +// which is why we have to explicitly pass the context user ID. +func retrieveFeeds(c *context.Context, ctxUser *models.User, userID int64, isProfile bool) { + actions, err := models.GetFeeds(ctxUser, userID, c.QueryInt64("after_id"), isProfile) + if err != nil { + c.Handle(500, "GetFeeds", err) + return + } + + // Check access of private repositories. + feeds := make([]*models.Action, 0, len(actions)) + unameAvatars := make(map[string]string) + for _, act := range actions { + // Cache results to reduce queries. + _, ok := unameAvatars[act.ActUserName] + if !ok { + u, err := models.GetUserByName(act.ActUserName) + if err != nil { + if errors.IsUserNotExist(err) { + continue + } + c.Handle(500, "GetUserByName", err) + return + } + unameAvatars[act.ActUserName] = u.RelAvatarLink() + } + + act.ActAvatar = unameAvatars[act.ActUserName] + feeds = append(feeds, act) + } + c.Data["Feeds"] = feeds + if len(feeds) > 0 { + afterID := feeds[len(feeds)-1].ID + c.Data["AfterID"] = afterID + c.Header().Set("X-AJAX-URL", fmt.Sprintf("%s?after_id=%d", c.Data["Link"], afterID)) + } +} + +func Dashboard(c *context.Context) { + ctxUser := getDashboardContextUser(c) + if c.Written() { + return + } + + retrieveFeeds(c, ctxUser, c.User.ID, false) + if c.Written() { + return + } + + if c.Req.Header.Get("X-AJAX") == "true" { + c.HTML(200, NEWS_FEED) + return + } + + c.Data["Title"] = ctxUser.DisplayName() + " - " + c.Tr("dashboard") + c.Data["PageIsDashboard"] = true + c.Data["PageIsNews"] = true + + // Only user can have collaborative repositories. + if !ctxUser.IsOrganization() { + collaborateRepos, err := c.User.GetAccessibleRepositories(setting.UI.User.RepoPagingNum) + if err != nil { + c.Handle(500, "GetAccessibleRepositories", err) + return + } else if err = models.RepositoryList(collaborateRepos).LoadAttributes(); err != nil { + c.Handle(500, "RepositoryList.LoadAttributes", err) + return + } + c.Data["CollaborativeRepos"] = collaborateRepos + } + + var err error + var repos, mirrors []*models.Repository + var repoCount int64 + if ctxUser.IsOrganization() { + repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, setting.UI.User.RepoPagingNum) + if err != nil { + c.Handle(500, "GetUserRepositories", err) + return + } + + mirrors, err = ctxUser.GetUserMirrorRepositories(c.User.ID) + if err != nil { + c.Handle(500, "GetUserMirrorRepositories", err) + return + } + } else { + if err = ctxUser.GetRepositories(1, setting.UI.User.RepoPagingNum); err != nil { + c.Handle(500, "GetRepositories", err) + return + } + repos = ctxUser.Repos + repoCount = int64(ctxUser.NumRepos) + + mirrors, err = ctxUser.GetMirrorRepositories() + if err != nil { + c.Handle(500, "GetMirrorRepositories", err) + return + } + } + c.Data["Repos"] = repos + c.Data["RepoCount"] = repoCount + c.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum + + if err := models.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil { + c.Handle(500, "MirrorRepositoryList.LoadAttributes", err) + return + } + c.Data["MirrorCount"] = len(mirrors) + c.Data["Mirrors"] = mirrors + + c.HTML(200, DASHBOARD) +} + +func Issues(c *context.Context) { + isPullList := c.Params(":type") == "pulls" + if isPullList { + c.Data["Title"] = c.Tr("pull_requests") + c.Data["PageIsPulls"] = true + } else { + c.Data["Title"] = c.Tr("issues") + c.Data["PageIsIssues"] = true + } + + ctxUser := getDashboardContextUser(c) + if c.Written() { + return + } + + var ( + sortType = c.Query("sort") + filterMode = models.FILTER_MODE_YOUR_REPOS + ) + + // Note: Organization does not have view type and filter mode. + if !ctxUser.IsOrganization() { + viewType := c.Query("type") + types := []string{ + string(models.FILTER_MODE_YOUR_REPOS), + string(models.FILTER_MODE_ASSIGN), + string(models.FILTER_MODE_CREATE), + } + if !com.IsSliceContainsStr(types, viewType) { + viewType = string(models.FILTER_MODE_YOUR_REPOS) + } + filterMode = models.FilterMode(viewType) + } + + page := c.QueryInt("page") + if page <= 1 { + page = 1 + } + + repoID := c.QueryInt64("repo") + isShowClosed := c.Query("state") == "closed" + + // Get repositories. + var ( + err error + repos []*models.Repository + userRepoIDs []int64 + showRepos = make([]*models.Repository, 0, 10) + ) + if ctxUser.IsOrganization() { + repos, _, err = ctxUser.GetUserRepositories(c.User.ID, 1, ctxUser.NumRepos) + if err != nil { + c.Handle(500, "GetRepositories", err) + return + } + } else { + if err := ctxUser.GetRepositories(1, c.User.NumRepos); err != nil { + c.Handle(500, "GetRepositories", err) + return + } + repos = ctxUser.Repos + } + + userRepoIDs = make([]int64, 0, len(repos)) + for _, repo := range repos { + userRepoIDs = append(userRepoIDs, repo.ID) + + if filterMode != models.FILTER_MODE_YOUR_REPOS { + continue + } + + if isPullList { + if isShowClosed && repo.NumClosedPulls == 0 || + !isShowClosed && repo.NumOpenPulls == 0 { + continue + } + } else { + if !repo.EnableIssues || repo.EnableExternalTracker || + isShowClosed && repo.NumClosedIssues == 0 || + !isShowClosed && repo.NumOpenIssues == 0 { + continue + } + } + + showRepos = append(showRepos, repo) + } + + // Filter repositories if the page shows issues. + if !isPullList { + userRepoIDs, err = models.FilterRepositoryWithIssues(userRepoIDs) + if err != nil { + c.Handle(500, "FilterRepositoryWithIssues", err) + return + } + } + + issueOptions := &models.IssuesOptions{ + RepoID: repoID, + Page: page, + IsClosed: isShowClosed, + IsPull: isPullList, + SortType: sortType, + } + switch filterMode { + case models.FILTER_MODE_YOUR_REPOS: + // Get all issues from repositories from this user. + if userRepoIDs == nil { + issueOptions.RepoIDs = []int64{-1} + } else { + issueOptions.RepoIDs = userRepoIDs + } + + case models.FILTER_MODE_ASSIGN: + // Get all issues assigned to this user. + issueOptions.AssigneeID = ctxUser.ID + + case models.FILTER_MODE_CREATE: + // Get all issues created by this user. + issueOptions.PosterID = ctxUser.ID + } + + issues, err := models.Issues(issueOptions) + if err != nil { + c.Handle(500, "Issues", err) + return + } + + if repoID > 0 { + repo, err := models.GetRepositoryByID(repoID) + if err != nil { + c.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d] %v", repoID, err)) + return + } + + if err = repo.GetOwner(); err != nil { + c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", repoID, err)) + return + } + + // Check if user has access to given repository. + if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser.ID) { + c.Handle(404, "Issues", fmt.Errorf("#%d", repoID)) + return + } + } + + for _, issue := range issues { + if err = issue.Repo.GetOwner(); err != nil { + c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", issue.RepoID, err)) + return + } + } + + issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList) + + var total int + if !isShowClosed { + total = int(issueStats.OpenCount) + } else { + total = int(issueStats.ClosedCount) + } + + c.Data["Issues"] = issues + c.Data["Repos"] = showRepos + c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5) + c.Data["IssueStats"] = issueStats + c.Data["ViewType"] = string(filterMode) + c.Data["SortType"] = sortType + c.Data["RepoID"] = repoID + c.Data["IsShowClosed"] = isShowClosed + + if isShowClosed { + c.Data["State"] = "closed" + } else { + c.Data["State"] = "open" + } + + c.HTML(200, ISSUES) +} + +func ShowSSHKeys(c *context.Context, uid int64) { + keys, err := models.ListPublicKeys(uid) + if err != nil { + c.Handle(500, "ListPublicKeys", err) + return + } + + var buf bytes.Buffer + for i := range keys { + buf.WriteString(keys[i].OmitEmail()) + buf.WriteString("\n") + } + c.PlainText(200, buf.Bytes()) +} + +func showOrgProfile(c *context.Context) { + c.SetParams(":org", c.Params(":username")) + context.HandleOrgAssignment(c) + if c.Written() { + return + } + + org := c.Org.Organization + c.Data["Title"] = org.FullName + + page := c.QueryInt("page") + if page <= 0 { + page = 1 + } + + var ( + repos []*models.Repository + count int64 + err error + ) + if c.IsLogged && !c.User.IsAdmin { + repos, count, err = org.GetUserRepositories(c.User.ID, page, setting.UI.User.RepoPagingNum) + if err != nil { + c.Handle(500, "GetUserRepositories", err) + return + } + c.Data["Repos"] = repos + } else { + showPrivate := c.IsLogged && c.User.IsAdmin + repos, err = models.GetUserRepositories(&models.UserRepoOptions{ + UserID: org.ID, + Private: showPrivate, + Page: page, + PageSize: setting.UI.User.RepoPagingNum, + }) + if err != nil { + c.Handle(500, "GetRepositories", err) + return + } + c.Data["Repos"] = repos + count = models.CountUserRepositories(org.ID, showPrivate) + } + c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5) + + if err := org.GetMembers(); err != nil { + c.Handle(500, "GetMembers", err) + return + } + c.Data["Members"] = org.Members + + c.Data["Teams"] = org.Teams + + c.HTML(200, ORG_HOME) +} + +func Email2User(c *context.Context) { + u, err := models.GetUserByEmail(c.Query("email")) + if err != nil { + c.NotFoundOrServerError("GetUserByEmail", errors.IsUserNotExist, err) + return + } + c.Redirect(setting.AppSubURL + "/user/" + u.Name) +} diff --git a/routes/user/profile.go b/routes/user/profile.go new file mode 100644 index 00000000..a6eba351 --- /dev/null +++ b/routes/user/profile.go @@ -0,0 +1,169 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "fmt" + "path" + "strings" + + "github.com/Unknwon/paginater" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/routes/repo" +) + +const ( + FOLLOWERS = "user/meta/followers" + STARS = "user/meta/stars" +) + +func GetUserByName(c *context.Context, name string) *models.User { + user, err := models.GetUserByName(name) + if err != nil { + c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) + return nil + } + return user +} + +// GetUserByParams returns user whose name is presented in URL paramenter. +func GetUserByParams(c *context.Context) *models.User { + return GetUserByName(c, c.Params(":username")) +} + +func Profile(c *context.Context) { + uname := c.Params(":username") + // Special handle for FireFox requests favicon.ico. + if uname == "favicon.ico" { + c.ServeFile(path.Join(setting.StaticRootPath, "public/img/favicon.png")) + return + } else if strings.HasSuffix(uname, ".png") { + c.Error(404) + return + } + + isShowKeys := false + if strings.HasSuffix(uname, ".keys") { + isShowKeys = true + } + + ctxUser := GetUserByName(c, strings.TrimSuffix(uname, ".keys")) + if c.Written() { + return + } + + // Show SSH keys. + if isShowKeys { + ShowSSHKeys(c, ctxUser.ID) + return + } + + if ctxUser.IsOrganization() { + showOrgProfile(c) + return + } + + c.Data["Title"] = ctxUser.DisplayName() + c.Data["PageIsUserProfile"] = true + c.Data["Owner"] = ctxUser + + orgs, err := models.GetOrgsByUserID(ctxUser.ID, c.IsLogged && (c.User.IsAdmin || c.User.ID == ctxUser.ID)) + if err != nil { + c.Handle(500, "GetOrgsByUserIDDesc", err) + return + } + + c.Data["Orgs"] = orgs + + tab := c.Query("tab") + c.Data["TabName"] = tab + switch tab { + case "activity": + retrieveFeeds(c, ctxUser, -1, true) + if c.Written() { + return + } + default: + page := c.QueryInt("page") + if page <= 0 { + page = 1 + } + + showPrivate := c.IsLogged && (ctxUser.ID == c.User.ID || c.User.IsAdmin) + c.Data["Repos"], err = models.GetUserRepositories(&models.UserRepoOptions{ + UserID: ctxUser.ID, + Private: showPrivate, + Page: page, + PageSize: setting.UI.User.RepoPagingNum, + }) + if err != nil { + c.Handle(500, "GetRepositories", err) + return + } + + count := models.CountUserRepositories(ctxUser.ID, showPrivate) + c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5) + } + + c.HTML(200, PROFILE) +} + +func Followers(c *context.Context) { + u := GetUserByParams(c) + if c.Written() { + return + } + c.Data["Title"] = u.DisplayName() + c.Data["CardsTitle"] = c.Tr("user.followers") + c.Data["PageIsFollowers"] = true + c.Data["Owner"] = u + repo.RenderUserCards(c, u.NumFollowers, u.GetFollowers, FOLLOWERS) +} + +func Following(c *context.Context) { + u := GetUserByParams(c) + if c.Written() { + return + } + c.Data["Title"] = u.DisplayName() + c.Data["CardsTitle"] = c.Tr("user.following") + c.Data["PageIsFollowing"] = true + c.Data["Owner"] = u + repo.RenderUserCards(c, u.NumFollowing, u.GetFollowing, FOLLOWERS) +} + +func Stars(c *context.Context) { + +} + +func Action(c *context.Context) { + u := GetUserByParams(c) + if c.Written() { + return + } + + var err error + switch c.Params(":action") { + case "follow": + err = models.FollowUser(c.User.ID, u.ID) + case "unfollow": + err = models.UnfollowUser(c.User.ID, u.ID) + } + + if err != nil { + c.Handle(500, fmt.Sprintf("Action (%s)", c.Params(":action")), err) + return + } + + redirectTo := c.Query("redirect_to") + if len(redirectTo) == 0 { + redirectTo = u.HomeLink() + } + c.Redirect(redirectTo) +} diff --git a/routes/user/setting.go b/routes/user/setting.go new file mode 100644 index 00000000..723b3da2 --- /dev/null +++ b/routes/user/setting.go @@ -0,0 +1,664 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "bytes" + "encoding/base64" + "fmt" + "html/template" + "image/png" + "io/ioutil" + "strings" + + "github.com/Unknwon/com" + "github.com/pquerna/otp" + "github.com/pquerna/otp/totp" + log "gopkg.in/clog.v1" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/models/errors" + "github.com/gogits/gogs/pkg/context" + "github.com/gogits/gogs/pkg/form" + "github.com/gogits/gogs/pkg/mailer" + "github.com/gogits/gogs/pkg/setting" + "github.com/gogits/gogs/pkg/tool" +) + +const ( + SETTINGS_PROFILE = "user/settings/profile" + SETTINGS_AVATAR = "user/settings/avatar" + SETTINGS_PASSWORD = "user/settings/password" + SETTINGS_EMAILS = "user/settings/email" + SETTINGS_SSH_KEYS = "user/settings/sshkeys" + SETTINGS_SECURITY = "user/settings/security" + SETTINGS_TWO_FACTOR_ENABLE = "user/settings/two_factor_enable" + SETTINGS_TWO_FACTOR_RECOVERY_CODES = "user/settings/two_factor_recovery_codes" + SETTINGS_REPOSITORIES = "user/settings/repositories" + SETTINGS_ORGANIZATIONS = "user/settings/organizations" + SETTINGS_APPLICATIONS = "user/settings/applications" + SETTINGS_DELETE = "user/settings/delete" + NOTIFICATION = "user/notification" +) + +func Settings(c *context.Context) { + c.Title("settings.profile") + c.PageIs("SettingsProfile") + c.Data["origin_name"] = c.User.Name + c.Data["name"] = c.User.Name + c.Data["full_name"] = c.User.FullName + c.Data["email"] = c.User.Email + c.Data["website"] = c.User.Website + c.Data["location"] = c.User.Location + c.Success(SETTINGS_PROFILE) +} + +func SettingsPost(c *context.Context, f form.UpdateProfile) { + c.Title("settings.profile") + c.PageIs("SettingsProfile") + c.Data["origin_name"] = c.User.Name + + if c.HasError() { + c.Success(SETTINGS_PROFILE) + return + } + + // Non-local users are not allowed to change their username + if c.User.IsLocal() { + // Check if username characters have been changed + if c.User.LowerName != strings.ToLower(f.Name) { + if err := models.ChangeUserName(c.User, f.Name); err != nil { + c.FormErr("Name") + var msg string + switch { + case models.IsErrUserAlreadyExist(err): + msg = c.Tr("form.username_been_taken") + case models.IsErrEmailAlreadyUsed(err): + msg = c.Tr("form.email_been_used") + case models.IsErrNameReserved(err): + msg = c.Tr("form.name_reserved") + case models.IsErrNamePatternNotAllowed(err): + msg = c.Tr("form.name_pattern_not_allowed") + default: + c.ServerError("ChangeUserName", err) + return + } + + c.RenderWithErr(msg, SETTINGS_PROFILE, &f) + return + } + + log.Trace("Username changed: %s -> %s", c.User.Name, f.Name) + } + + // In case it's just a case change + c.User.Name = f.Name + c.User.LowerName = strings.ToLower(f.Name) + } + + c.User.FullName = f.FullName + c.User.Email = f.Email + c.User.Website = f.Website + c.User.Location = f.Location + if err := models.UpdateUser(c.User); err != nil { + c.ServerError("UpdateUser", err) + return + } + + c.Flash.Success(c.Tr("settings.update_profile_success")) + c.SubURLRedirect("/user/settings") +} + +// FIXME: limit size. +func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *models.User) error { + ctxUser.UseCustomAvatar = f.Source == form.AVATAR_LOCAL + if len(f.Gravatar) > 0 { + ctxUser.Avatar = tool.MD5(f.Gravatar) + ctxUser.AvatarEmail = f.Gravatar + } + + if f.Avatar != nil { + r, err := f.Avatar.Open() + if err != nil { + return fmt.Errorf("Avatar.Open: %v", err) + } + defer r.Close() + + data, err := ioutil.ReadAll(r) + if err != nil { + return fmt.Errorf("ioutil.ReadAll: %v", err) + } + if !tool.IsImageFile(data) { + return errors.New(c.Tr("settings.uploaded_avatar_not_a_image")) + } + if err = ctxUser.UploadAvatar(data); err != nil { + return fmt.Errorf("UploadAvatar: %v", err) + } + } else { + // No avatar is uploaded but setting has been changed to enable, + // generate a random one when needed. + if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) { + if err := ctxUser.GenerateRandomAvatar(); err != nil { + log.Error(4, "GenerateRandomAvatar[%d]: %v", ctxUser.ID, err) + } + } + } + + if err := models.UpdateUser(ctxUser); err != nil { + return fmt.Errorf("UpdateUser: %v", err) + } + + return nil +} + +func SettingsAvatar(c *context.Context) { + c.Title("settings.avatar") + c.PageIs("SettingsAvatar") + c.Success(SETTINGS_AVATAR) +} + +func SettingsAvatarPost(c *context.Context, f form.Avatar) { + if err := UpdateAvatarSetting(c, f, c.User); err != nil { + c.Flash.Error(err.Error()) + } else { + c.Flash.Success(c.Tr("settings.update_avatar_success")) + } + + c.SubURLRedirect("/user/settings/avatar") +} + +func SettingsDeleteAvatar(c *context.Context) { + if err := c.User.DeleteAvatar(); err != nil { + c.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err)) + } + + c.SubURLRedirect("/user/settings/avatar") +} + +func SettingsPassword(c *context.Context) { + c.Title("settings.password") + c.PageIs("SettingsPassword") + c.Success(SETTINGS_PASSWORD) +} + +func SettingsPasswordPost(c *context.Context, f form.ChangePassword) { + c.Title("settings.password") + c.PageIs("SettingsPassword") + + if c.HasError() { + c.Success(SETTINGS_PASSWORD) + return + } + + if !c.User.ValidatePassword(f.OldPassword) { + c.Flash.Error(c.Tr("settings.password_incorrect")) + } else if f.Password != f.Retype { + c.Flash.Error(c.Tr("form.password_not_match")) + } else { + c.User.Passwd = f.Password + var err error + if c.User.Salt, err = models.GetUserSalt(); err != nil { + c.ServerError("GetUserSalt", err) + return + } + c.User.EncodePasswd() + if err := models.UpdateUser(c.User); err != nil { + c.ServerError("UpdateUser", err) + return + } + c.Flash.Success(c.Tr("settings.change_password_success")) + } + + c.SubURLRedirect("/user/settings/password") +} + +func SettingsEmails(c *context.Context) { + c.Title("settings.emails") + c.PageIs("SettingsEmails") + + emails, err := models.GetEmailAddresses(c.User.ID) + if err != nil { + c.ServerError("GetEmailAddresses", err) + return + } + c.Data["Emails"] = emails + + c.Success(SETTINGS_EMAILS) +} + +func SettingsEmailPost(c *context.Context, f form.AddEmail) { + c.Title("settings.emails") + c.PageIs("SettingsEmails") + + // Make emailaddress primary. + if c.Query("_method") == "PRIMARY" { + if err := models.MakeEmailPrimary(&models.EmailAddress{ID: c.QueryInt64("id")}); err != nil { + c.ServerError("MakeEmailPrimary", err) + return + } + + c.SubURLRedirect("/user/settings/email") + return + } + + // Add Email address. + emails, err := models.GetEmailAddresses(c.User.ID) + if err != nil { + c.ServerError("GetEmailAddresses", err) + return + } + c.Data["Emails"] = emails + + if c.HasError() { + c.Success(SETTINGS_EMAILS) + return + } + + email := &models.EmailAddress{ + UID: c.User.ID, + Email: f.Email, + IsActivated: !setting.Service.RegisterEmailConfirm, + } + if err := models.AddEmailAddress(email); err != nil { + if models.IsErrEmailAlreadyUsed(err) { + c.RenderWithErr(c.Tr("form.email_been_used"), SETTINGS_EMAILS, &f) + } else { + c.ServerError("AddEmailAddress", err) + } + return + } + + // Send confirmation email + if setting.Service.RegisterEmailConfirm { + mailer.SendActivateEmailMail(c.Context, models.NewMailerUser(c.User), email.Email) + + if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil { + log.Error(2, "Set cache 'MailResendLimit' failed: %v", err) + } + c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, setting.Service.ActiveCodeLives/60)) + } else { + c.Flash.Success(c.Tr("settings.add_email_success")) + } + + c.SubURLRedirect("/user/settings/email") +} + +func DeleteEmail(c *context.Context) { + if err := models.DeleteEmailAddress(&models.EmailAddress{ + ID: c.QueryInt64("id"), + UID: c.User.ID, + }); err != nil { + c.ServerError("DeleteEmailAddress", err) + return + } + + c.Flash.Success(c.Tr("settings.email_deletion_success")) + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/email", + }) +} + +func SettingsSSHKeys(c *context.Context) { + c.Title("settings.ssh_keys") + c.PageIs("SettingsSSHKeys") + + keys, err := models.ListPublicKeys(c.User.ID) + if err != nil { + c.ServerError("ListPublicKeys", err) + return + } + c.Data["Keys"] = keys + + c.Success(SETTINGS_SSH_KEYS) +} + +func SettingsSSHKeysPost(c *context.Context, f form.AddSSHKey) { + c.Title("settings.ssh_keys") + c.PageIs("SettingsSSHKeys") + + keys, err := models.ListPublicKeys(c.User.ID) + if err != nil { + c.ServerError("ListPublicKeys", err) + return + } + c.Data["Keys"] = keys + + if c.HasError() { + c.Success(SETTINGS_SSH_KEYS) + return + } + + content, err := models.CheckPublicKeyString(f.Content) + if err != nil { + if models.IsErrKeyUnableVerify(err) { + c.Flash.Info(c.Tr("form.unable_verify_ssh_key")) + } else { + c.Flash.Error(c.Tr("form.invalid_ssh_key", err.Error())) + c.SubURLRedirect("/user/settings/ssh") + return + } + } + + if _, err = models.AddPublicKey(c.User.ID, f.Title, content); err != nil { + c.Data["HasError"] = true + switch { + case models.IsErrKeyAlreadyExist(err): + c.FormErr("Content") + c.RenderWithErr(c.Tr("settings.ssh_key_been_used"), SETTINGS_SSH_KEYS, &f) + case models.IsErrKeyNameAlreadyUsed(err): + c.FormErr("Title") + c.RenderWithErr(c.Tr("settings.ssh_key_name_used"), SETTINGS_SSH_KEYS, &f) + default: + c.ServerError("AddPublicKey", err) + } + return + } + + c.Flash.Success(c.Tr("settings.add_key_success", f.Title)) + c.SubURLRedirect("/user/settings/ssh") +} + +func DeleteSSHKey(c *context.Context) { + if err := models.DeletePublicKey(c.User, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeletePublicKey: " + err.Error()) + } else { + c.Flash.Success(c.Tr("settings.ssh_key_deletion_success")) + } + + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/ssh", + }) +} + +func SettingsSecurity(c *context.Context) { + c.Title("settings.security") + c.PageIs("SettingsSecurity") + + t, err := models.GetTwoFactorByUserID(c.UserID()) + if err != nil && !errors.IsTwoFactorNotFound(err) { + c.ServerError("GetTwoFactorByUserID", err) + return + } + c.Data["TwoFactor"] = t + + c.Success(SETTINGS_SECURITY) +} + +func SettingsTwoFactorEnable(c *context.Context) { + if c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + c.Title("settings.two_factor_enable_title") + c.PageIs("SettingsSecurity") + + var key *otp.Key + var err error + keyURL := c.Session.Get("twoFactorURL") + if keyURL != nil { + key, _ = otp.NewKeyFromURL(keyURL.(string)) + } + if key == nil { + key, err = totp.Generate(totp.GenerateOpts{ + Issuer: setting.AppName, + AccountName: c.User.Email, + }) + if err != nil { + c.ServerError("Generate", err) + return + } + } + c.Data["TwoFactorSecret"] = key.Secret() + + img, err := key.Image(240, 240) + if err != nil { + c.ServerError("Image", err) + return + } + + var buf bytes.Buffer + if err = png.Encode(&buf, img); err != nil { + c.ServerError("Encode", err) + return + } + c.Data["QRCode"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())) + + c.Session.Set("twoFactorSecret", c.Data["TwoFactorSecret"]) + c.Session.Set("twoFactorURL", key.String()) + c.Success(SETTINGS_TWO_FACTOR_ENABLE) +} + +func SettingsTwoFactorEnablePost(c *context.Context) { + secret, ok := c.Session.Get("twoFactorSecret").(string) + if !ok { + c.NotFound() + return + } + + if !totp.Validate(c.Query("passcode"), secret) { + c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode")) + c.SubURLRedirect("/user/settings/security/two_factor_enable") + return + } + + if err := models.NewTwoFactor(c.UserID(), secret); err != nil { + c.Flash.Error(c.Tr("settings.two_factor_enable_error", err)) + c.SubURLRedirect("/user/settings/security/two_factor_enable") + return + } + + c.Session.Delete("twoFactorSecret") + c.Session.Delete("twoFactorURL") + c.Flash.Success(c.Tr("settings.two_factor_enable_success")) + c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes") +} + +func SettingsTwoFactorRecoveryCodes(c *context.Context) { + if !c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + c.Title("settings.two_factor_recovery_codes_title") + c.PageIs("SettingsSecurity") + + recoveryCodes, err := models.GetRecoveryCodesByUserID(c.UserID()) + if err != nil { + c.ServerError("GetRecoveryCodesByUserID", err) + return + } + c.Data["RecoveryCodes"] = recoveryCodes + + c.Success(SETTINGS_TWO_FACTOR_RECOVERY_CODES) +} + +func SettingsTwoFactorRecoveryCodesPost(c *context.Context) { + if !c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + if err := models.RegenerateRecoveryCodes(c.UserID()); err != nil { + c.Flash.Error(c.Tr("settings.two_factor_regenerate_recovery_codes_error", err)) + } else { + c.Flash.Success(c.Tr("settings.two_factor_regenerate_recovery_codes_success")) + } + + c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes") +} + +func SettingsTwoFactorDisable(c *context.Context) { + if !c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + if err := models.DeleteTwoFactor(c.UserID()); err != nil { + c.ServerError("DeleteTwoFactor", err) + return + } + + c.Flash.Success(c.Tr("settings.two_factor_disable_success")) + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/security", + }) +} + +func SettingsRepos(c *context.Context) { + c.Title("settings.repos") + c.PageIs("SettingsRepositories") + + repos, err := models.GetUserAndCollaborativeRepositories(c.User.ID) + if err != nil { + c.ServerError("GetUserAndCollaborativeRepositories", err) + return + } + if err = models.RepositoryList(repos).LoadAttributes(); err != nil { + c.ServerError("LoadAttributes", err) + return + } + c.Data["Repos"] = repos + + c.Success(SETTINGS_REPOSITORIES) +} + +func SettingsLeaveRepo(c *context.Context) { + repo, err := models.GetRepositoryByID(c.QueryInt64("id")) + if err != nil { + c.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err) + return + } + + if err = repo.DeleteCollaboration(c.User.ID); err != nil { + c.ServerError("DeleteCollaboration", err) + return + } + + c.Flash.Success(c.Tr("settings.repos.leave_success", repo.FullName())) + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/repositories", + }) +} + +func SettingsOrganizations(c *context.Context) { + c.Title("settings.orgs") + c.PageIs("SettingsOrganizations") + + orgs, err := models.GetOrgsByUserID(c.User.ID, true) + if err != nil { + c.ServerError("GetOrgsByUserID", err) + return + } + c.Data["Orgs"] = orgs + + c.Success(SETTINGS_ORGANIZATIONS) +} + +func SettingsLeaveOrganization(c *context.Context) { + if err := models.RemoveOrgUser(c.QueryInt64("id"), c.User.ID); err != nil { + if models.IsErrLastOrgOwner(err) { + c.Flash.Error(c.Tr("form.last_org_owner")) + } else { + c.ServerError("RemoveOrgUser", err) + return + } + } + + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/organizations", + }) +} + +func SettingsApplications(c *context.Context) { + c.Title("settings.applications") + c.PageIs("SettingsApplications") + + tokens, err := models.ListAccessTokens(c.User.ID) + if err != nil { + c.ServerError("ListAccessTokens", err) + return + } + c.Data["Tokens"] = tokens + + c.Success(SETTINGS_APPLICATIONS) +} + +func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) { + c.Title("settings.applications") + c.PageIs("SettingsApplications") + + if c.HasError() { + tokens, err := models.ListAccessTokens(c.User.ID) + if err != nil { + c.ServerError("ListAccessTokens", err) + return + } + + c.Data["Tokens"] = tokens + c.Success(SETTINGS_APPLICATIONS) + return + } + + t := &models.AccessToken{ + UID: c.User.ID, + Name: f.Name, + } + if err := models.NewAccessToken(t); err != nil { + c.ServerError("NewAccessToken", err) + return + } + + c.Flash.Success(c.Tr("settings.generate_token_succees")) + c.Flash.Info(t.Sha1) + c.SubURLRedirect("/user/settings/applications") +} + +func SettingsDeleteApplication(c *context.Context) { + if err := models.DeleteAccessTokenOfUserByID(c.User.ID, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteAccessTokenByID: " + err.Error()) + } else { + c.Flash.Success(c.Tr("settings.delete_token_success")) + } + + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/applications", + }) +} + +func SettingsDelete(c *context.Context) { + c.Title("settings.delete") + c.PageIs("SettingsDelete") + + if c.Req.Method == "POST" { + if _, err := models.UserSignIn(c.User.Name, c.Query("password")); err != nil { + if errors.IsUserNotExist(err) { + c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil) + } else { + c.ServerError("UserSignIn", err) + } + return + } + + if err := models.DeleteUser(c.User); err != nil { + switch { + case models.IsErrUserOwnRepos(err): + c.Flash.Error(c.Tr("form.still_own_repo")) + c.Redirect(setting.AppSubURL + "/user/settings/delete") + case models.IsErrUserHasOrgs(err): + c.Flash.Error(c.Tr("form.still_has_org")) + c.Redirect(setting.AppSubURL + "/user/settings/delete") + default: + c.ServerError("DeleteUser", err) + } + } else { + log.Trace("Account deleted: %s", c.User.Name) + c.Redirect(setting.AppSubURL + "/") + } + return + } + + c.Success(SETTINGS_DELETE) +} diff --git a/templates/.VERSION b/templates/.VERSION index 95335b2f..82236ad3 100644 --- a/templates/.VERSION +++ b/templates/.VERSION @@ -1 +1 @@ -0.11.19.0609 \ No newline at end of file +0.11.19.0611 \ No newline at end of file -- cgit v1.2.3