From 64c68220d203cb07be001184cde4b35d4b503344 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Mon, 6 Oct 2014 17:50:00 -0400 Subject: [PATCH] Fix #264 --- README.md | 4 +- README_ZH.md | 4 +- cmd/web.go | 6 ++ conf/app.ini | 2 + conf/locale/locale_en-US.ini | 6 ++ conf/locale/locale_zh-CN.ini | 6 ++ gogs.go | 2 +- modules/git/hooks.go | 111 ++++++++++++++++++++++ modules/git/utils.go | 21 ++++ modules/middleware/repo.go | 10 ++ modules/setting/setting.go | 2 + public/ng/css/ui.css | 4 + public/ng/less/ui/form.less | 23 ++--- routers/repo/setting.go | 54 +++++++++++ templates/.VERSION | 2 +- templates/repo/settings/githook_edit.tmpl | 41 ++++++++ templates/repo/settings/githooks.tmpl | 37 ++++++++ templates/repo/settings/nav.tmpl | 1 + 18 files changed, 317 insertions(+), 19 deletions(-) create mode 100644 modules/git/hooks.go create mode 100644 templates/repo/settings/githook_edit.tmpl create mode 100644 templates/repo/settings/githooks.tmpl diff --git a/README.md b/README.md index b7ff264e..826d9b9e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) is a painless self-hosted Git Service written in Go. ![Demo](https://gowalker.org/public/gogs_demo.gif) -##### Current version: 0.5.4 Beta +##### Current version: 0.5.5 Beta ### NOTICES @@ -44,7 +44,7 @@ The goal of this project is to make the easiest, fastest and most painless way t - Slack webhook integration - Supports MySQL, PostgreSQL and SQLite3 - Social account login(GitHub, Google, QQ, Weibo) -- Multi-language support(English, Chinese, Germany, French etc.) +- Multi-language support(English, Chinese, Germany, French, Dutch etc.) ## System Requirements diff --git a/README_ZH.md b/README_ZH.md index d704053f..3da3b6be 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。 ![Demo](https://gowalker.org/public/gogs_demo.gif) -##### 当前版本:0.5.4 Beta +##### 当前版本:0.5.5 Beta ## 开发目的 @@ -35,7 +35,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自 - Slack Web 钩子集成 - 支持 MySQL、PostgreSQL 以及 SQLite3 数据库 - 社交帐号登录(GitHub、Google、QQ、微博) -- 多语言支持(英文、简体中文、德语、法语等等) +- 多语言支持(英文、简体中文、德语、法语、荷兰语等等) ## 系统要求 diff --git a/cmd/web.go b/cmd/web.go index 72a58bc9..810e36d3 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -313,6 +313,12 @@ func runWeb(*cli.Context) { r.Get("/hooks/:id", repo.WebHooksEdit) r.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) r.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) + + m.Group("/hooks/git", func(r *macaron.Router) { + r.Get("", repo.GitHooks) + r.Get("/:name", repo.GitHooksEdit) + r.Post("/:name", repo.GitHooksEditPost) + }, middleware.GitHookService()) }) }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) diff --git a/conf/app.ini b/conf/app.ini index 224f45dd..1ea92e9f 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -70,6 +70,8 @@ ENABLE_CACHE_AVATAR = false ENABLE_NOTIFY_MAIL = false ; More detail: https://github.com/gogits/gogs/issues/165 ENABLE_REVERSE_PROXY_AUTHENTICATION = false +; Repository Git hooks +ENABLE_GIT_HOOKS = false [webhook] ; Cron task interval in minutes diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 8e68fb98..4fc8c359 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -287,6 +287,7 @@ settings = Settings settings.options = Options settings.collaboration = Collaboration settings.hooks = Webhooks +settings.githooks = Git Hooks settings.deploy_keys = Deploy Keys settings.basic_settings = Basic Settings settings.danger_zone = Danger Zone @@ -310,6 +311,11 @@ settings.add_collaborator_success = New collaborator has been added. settings.remove_collaborator_success = Collaborator has been removed. settings.add_webhook = Add Webhook settings.hooks_desc = Webhooks allow external services to be notified when certain events happen on Gogs. When the specified events happen, we'll send a POST request to each of the URLs you provide. Learn more in our Webhooks Guide. +settings.githooks_desc = Git Hooks are powered by Git itself, you can edit files of supported hooks in the list below to apply custom operations. +settings.githook_edit_desc = If hook is not active, sample content will be presented. Leave content to be blank will disable this hook. +settings.githook_name = Hook Name +settings.githook_content = Hook Content +settings.update_githook = Update Hook settings.remove_hook_success = Webhook has been removed. settings.add_webhook_desc = We’ll send a POST request to the URL below with details of any subscribed events. You can also specify which data format you'd like to receive (JSON, x-www-form-urlencoded, etc). More information can be found in Webhooks Guide. settings.payload_url = Payload URL diff --git a/conf/locale/locale_zh-CN.ini b/conf/locale/locale_zh-CN.ini index 360bf4bc..dc454848 100644 --- a/conf/locale/locale_zh-CN.ini +++ b/conf/locale/locale_zh-CN.ini @@ -287,6 +287,7 @@ settings = 仓库设置 settings.options = 基本设置 settings.collaboration = 管理协作者 settings.hooks = 管理 Web 钩子 +settings.githooks = 管理 Git 钩子 settings.deploy_keys = 管理部署密钥 settings.basic_settings = 基本设置 settings.danger_zone = 危险操作区 @@ -312,6 +313,11 @@ settings.add_webhook = 添加 Web 钩子 settings.hooks_desc = Web 钩子允许您设定在 Gogs 上发生指定事件时对指定 URL 发送 POST 通知。查看 Webhooks 文档 获取更多信息。 settings.remove_hook_success = Web 钩子删除成功! settings.add_webhook_desc = 我们会通过 POST 请求将订阅事件信息发送至向指定 URL 地址。您可以设置不同的数据接收方式(JSON 或 x-www-form-urlencoded)。 请查阅 Webhooks 文档 获取更多信息。 +settings.githooks_desc = Git 钩子是由 Git 本身提供的功能,以下为 Gogs 所支持的钩子列表。 +settings.githook_edit_desc = 如果钩子未启动,则会显示样例文件中的内容。如果想要删除某个钩子,则提交空白文本即可。 +settings.githook_name = 钩子名称 +settings.githook_content = 钩子文本 +settings.update_githook = 更新钩子设置 settings.payload_url = 推送地址 settings.content_type = 数据格式 settings.secret = 密钥文本 diff --git a/gogs.go b/gogs.go index c0f1d1e2..43705061 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/setting" ) -const APP_VER = "0.5.4.1005 Beta" +const APP_VER = "0.5.5.1006 Beta" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/modules/git/hooks.go b/modules/git/hooks.go new file mode 100644 index 00000000..b8d15e5e --- /dev/null +++ b/modules/git/hooks.go @@ -0,0 +1,111 @@ +// 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 git + +import ( + "errors" + "io/ioutil" + "os" + "path" + "strings" +) + +// hookNames is a list of Git hooks' name that are supported. +var hookNames = []string{ + "pre-applypatch", + "applypatch-msg", + "prepare-commit-msg", + "commit-msg", + "pre-commit", + "pre-rebase", + "post-commit", + "post-receive", + "post-update", +} + +var ( + ErrNotValidHook = errors.New("not a valid Git hook") +) + +// IsValidHookName returns true if given name is a valid Git hook. +func IsValidHookName(name string) bool { + for _, hn := range hookNames { + if hn == name { + return true + } + } + return false +} + +// Hook represents a Git hook. +type Hook struct { + name string + IsActive bool // Indicates whether repository has this hook. + Content string // Content of hook if it's active. + Sample string // Sample content from Git. + path string // Hook file path. +} + +// GetHook returns a Git hook by given name and repository. +func GetHook(repoPath, name string) (*Hook, error) { + if !IsValidHookName(name) { + return nil, ErrNotValidHook + } + h := &Hook{ + name: name, + path: path.Join(repoPath, "hooks", name), + } + if isFile(h.path) { + data, err := ioutil.ReadFile(h.path) + if err != nil { + return nil, err + } + h.IsActive = true + h.Content = string(data) + } else if isFile(h.path + ".sample") { + data, err := ioutil.ReadFile(h.path + ".sample") + if err != nil { + return nil, err + } + h.Sample = string(data) + } + return h, nil +} + +func (h *Hook) Name() string { + return h.name +} + +// Update updates hook settings. +func (h *Hook) Update() error { + if len(strings.TrimSpace(h.Content)) == 0 { + return os.Remove(h.path) + } + return ioutil.WriteFile(h.path, []byte(h.Content), os.ModePerm) +} + +// ListHooks returns a list of Git hooks of given repository. +func ListHooks(repoPath string) (_ []*Hook, err error) { + if !isDir(path.Join(repoPath, "hooks")) { + return nil, errors.New("hooks path does not exist") + } + + hooks := make([]*Hook, len(hookNames)) + for i, name := range hookNames { + hooks[i], err = GetHook(repoPath, name) + if err != nil { + return nil, err + } + } + return hooks, nil +} + +func (repo *Repository) GetHook(name string) (*Hook, error) { + return GetHook(repo.Path, name) +} + +func (repo *Repository) Hooks() ([]*Hook, error) { + return ListHooks(repo.Path) +} diff --git a/modules/git/utils.go b/modules/git/utils.go index 26eef231..6abbca55 100644 --- a/modules/git/utils.go +++ b/modules/git/utils.go @@ -7,6 +7,7 @@ package git import ( "bytes" "container/list" + "os" "path/filepath" "strings" ) @@ -46,3 +47,23 @@ func RefEndName(refStr string) string { func filepathFromSHA1(rootdir, sha1 string) string { return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:]) } + +// isDir returns true if given path is a directory, +// or returns false when it's a file or does not exist. +func isDir(dir string) bool { + f, e := os.Stat(dir) + if e != nil { + return false + } + return f.IsDir() +} + +// isFile returns true if given path is a file, +// or returns false when it's a directory or does not exist. +func isFile(filePath string) bool { + f, e := os.Stat(filePath) + if e != nil { + return false + } + return !f.IsDir() +} diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index c6250f6d..78af58ea 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -308,3 +308,13 @@ func RequireTrueOwner() macaron.Handler { } } } + +// GitHookService checks if repsitory Git hooks service has been enabled. +func GitHookService() macaron.Handler { + return func(ctx *Context) { + if !setting.Service.EnableGitHooks { + ctx.Handle(404, "GitHookService", nil) + return + } + } +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 67e48108..b8fc4dec 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -275,6 +275,7 @@ var Service struct { LdapAuth bool ActiveCodeLives int ResetPwdCodeLives int + EnableGitHooks bool } func newService() { @@ -284,6 +285,7 @@ func newService() { Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW") Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR") Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION") + Service.EnableGitHooks = Cfg.MustBool("service", "ENABLE_GIT_HOOKS") } var logLevels = map[string]string{ diff --git a/public/ng/css/ui.css b/public/ng/css/ui.css index 9c3c8ded..3ea6fcd4 100644 --- a/public/ng/css/ui.css +++ b/public/ng/css/ui.css @@ -480,6 +480,10 @@ dt { .ipt-large { font-size: 14.4px; } +.ipt-textarea { + height: auto !important; + width: auto; +} .ipt-disabled, input[disabled] { background-color: #f2f2f2 !important; diff --git a/public/ng/less/ui/form.less b/public/ng/less/ui/form.less index 4a681994..b3de4273 100644 --- a/public/ng/less/ui/form.less +++ b/public/ng/less/ui/form.less @@ -116,25 +116,24 @@ } // input form elements - .ipt { - &:focus { - border-color: @iptFocusBorderColor; - } + &:focus { + border-color: @iptFocusBorderColor; + } } - .ipt-radius { - border-radius: .25em; + border-radius: .25em; } - .ipt-small { - font-size: .8*@baseFontSize; + font-size: .8*@baseFontSize; } - .ipt-large { - font-size: 1.2*@baseFontSize; + font-size: 1.2*@baseFontSize; +} +.ipt-textarea { + height: auto !important; + width: auto; } - .ipt-disabled, input[disabled] { background-color: @iptDisabledColor !important; @@ -144,14 +143,12 @@ input[disabled] { color: #888; cursor: not-allowed; } - .ipt-readonly, input[readonly] { &:focus { background-color: @iptDisabledColor !important; } } - .ipt-error { border-color: @iptErrorBorderColor !important; background-color: @iptErrorFocusColor !important; diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 48089787..0e58029e 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -16,6 +16,7 @@ import ( "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/git" "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/mailer" "github.com/gogits/gogs/modules/middleware" @@ -26,6 +27,8 @@ const ( SETTINGS_OPTIONS base.TplName = "repo/settings/options" COLLABORATION base.TplName = "repo/settings/collaboration" HOOKS base.TplName = "repo/settings/hooks" + GITHOOKS base.TplName = "repo/settings/githooks" + GITHOOK_EDIT base.TplName = "repo/settings/githook_edit" HOOK_NEW base.TplName = "repo/settings/hook_new" ORG_HOOK_NEW base.TplName = "org/settings/hook_new" ) @@ -591,3 +594,54 @@ func getOrgRepoCtx(ctx *middleware.Context) (*OrgRepoCtx, error) { return &OrgRepoCtx{}, errors.New("Unable to set OrgRepo context") } } + +func GitHooks(ctx *middleware.Context) { + ctx.Data["Title"] = ctx.Tr("repo.settings") + ctx.Data["PageIsSettingsGitHooks"] = true + + hooks, err := ctx.Repo.GitRepo.Hooks() + if err != nil { + ctx.Handle(500, "Hooks", err) + return + } + ctx.Data["Hooks"] = hooks + + ctx.HTML(200, GITHOOKS) +} + +func GitHooksEdit(ctx *middleware.Context) { + ctx.Data["Title"] = ctx.Tr("repo.settings") + ctx.Data["PageIsSettingsGitHooks"] = true + + name := ctx.Params(":name") + hook, err := ctx.Repo.GitRepo.GetHook(name) + if err != nil { + if err == git.ErrNotValidHook { + ctx.Handle(404, "GetHook", err) + } else { + ctx.Handle(500, "GetHook", err) + } + return + } + ctx.Data["Hook"] = hook + ctx.HTML(200, GITHOOK_EDIT) +} + +func GitHooksEditPost(ctx *middleware.Context) { + name := ctx.Params(":name") + hook, err := ctx.Repo.GitRepo.GetHook(name) + if err != nil { + if err == git.ErrNotValidHook { + ctx.Handle(404, "GetHook", err) + } else { + ctx.Handle(500, "GetHook", err) + } + return + } + hook.Content = ctx.Query("content") + if err = hook.Update(); err != nil { + ctx.Handle(500, "hook.Update", err) + return + } + ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git") +} diff --git a/templates/.VERSION b/templates/.VERSION index 7760f35b..e551dcfe 100644 --- a/templates/.VERSION +++ b/templates/.VERSION @@ -1 +1 @@ -0.5.4.1005 Beta \ No newline at end of file +0.5.5.1006 Beta \ No newline at end of file diff --git a/templates/repo/settings/githook_edit.tmpl b/templates/repo/settings/githook_edit.tmpl new file mode 100644 index 00000000..23fc26e3 --- /dev/null +++ b/templates/repo/settings/githook_edit.tmpl @@ -0,0 +1,41 @@ +{{template "ng/base/head" .}} +{{template "ng/base/header" .}} +
+ {{template "repo/header" .}} +
+
+ {{template "repo/settings/nav" .}} +
+
+ {{template "ng/base/alert" .}} +
+
+
+ {{.i18n.Tr "repo.settings.githooks"}} +
+
+ {{.CsrfTokenHtml}} +
{{.i18n.Tr "repo.settings.githook_edit_desc"}}
+ {{with .Hook}} +
+ + +
+
+ + +
+
+ + +
+ {{end}} +
+
+
+
+
+
+
+
+{{template "ng/base/footer" .}} \ No newline at end of file diff --git a/templates/repo/settings/githooks.tmpl b/templates/repo/settings/githooks.tmpl new file mode 100644 index 00000000..a059b0e7 --- /dev/null +++ b/templates/repo/settings/githooks.tmpl @@ -0,0 +1,37 @@ +{{template "ng/base/head" .}} +{{template "ng/base/header" .}} +
+ {{template "repo/header" .}} +
+
+ {{template "repo/settings/nav" .}} +
+
+ {{template "ng/base/alert" .}} +
+
+
+ {{.i18n.Tr "repo.settings.githooks"}} +
+
    +
  • {{.i18n.Tr "repo.settings.githooks_desc" | Str2html}}
  • + {{range .Hooks}} +
  • + {{if .IsActive}} + + {{else}} + + {{end}} + {{.Name}} + +
  • + {{end}} +
+
+
+
+
+
+
+
+{{template "ng/base/footer" .}} \ No newline at end of file diff --git a/templates/repo/settings/nav.tmpl b/templates/repo/settings/nav.tmpl index ef0765fe..16128f01 100644 --- a/templates/repo/settings/nav.tmpl +++ b/templates/repo/settings/nav.tmpl @@ -5,6 +5,7 @@
  • {{.i18n.Tr "repo.settings.options"}}
  • {{.i18n.Tr "repo.settings.collaboration"}}
  • {{.i18n.Tr "repo.settings.hooks"}}
  • +
  • {{.i18n.Tr "repo.settings.githooks"}}