]> source.dussan.org Git - gitea.git/commitdiff
Fix #264
authorUnknwon <joe2010xtmf@163.com>
Mon, 6 Oct 2014 21:50:00 +0000 (17:50 -0400)
committerUnknwon <joe2010xtmf@163.com>
Mon, 6 Oct 2014 21:50:00 +0000 (17:50 -0400)
18 files changed:
README.md
README_ZH.md
cmd/web.go
conf/app.ini
conf/locale/locale_en-US.ini
conf/locale/locale_zh-CN.ini
gogs.go
modules/git/hooks.go [new file with mode: 0644]
modules/git/utils.go
modules/middleware/repo.go
modules/setting/setting.go
public/ng/css/ui.css
public/ng/less/ui/form.less
routers/repo/setting.go
templates/.VERSION
templates/repo/settings/githook_edit.tmpl [new file with mode: 0644]
templates/repo/settings/githooks.tmpl [new file with mode: 0644]
templates/repo/settings/nav.tmpl

index b7ff264e35e7133be42ccd0987bb53006139f092..826d9b9e4278c007a4951b0a38e52bf66480afd6 100644 (file)
--- 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
 
index d704053f225803dbb3334ca8d5933082d4e0325b..3da3b6be76d2b4b6f9a445236b9fb9a113b607ae 100644 (file)
@@ -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、微博)
-- 多语言支持(英文、简体中文、德语、法语等等)
+- 多语言支持(英文、简体中文、德语、法语、荷兰语等等)
 
 ## 系统要求
 
index 72a58bc995f276d79852abe0d242fc2699cee4de..810e36d3bbd4ee0b238c7c41cc7680add583bb77 100644 (file)
@@ -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)
 
index 224f45dd621e77628d09995946c94923e4eef8eb..1ea92e9f3bd3d990c74415e97bad4194a178da86 100644 (file)
@@ -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
index 8e68fb980368c724638153ca5762a222a0007238..4fc8c359ff3ef328214db7d222d22af86f9be44c 100644 (file)
@@ -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 <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
+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 <code>POST</code> request to the URL below with details of any subscribed events. You can also specify which data format you'd like to receive (JSON, <code>x-www-form-urlencoded</code>, <em>etc</em>). More information can be found in <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
 settings.payload_url = Payload URL
index 360bf4bcc685ba042969949c4371b0ada9d3b5cd..dc4548487baae1fe602adaea6277c138103e64aa 100644 (file)
@@ -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 通知。查看 <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks 文档</a> 获取更多信息。
 settings.remove_hook_success = Web 钩子删除成功!
 settings.add_webhook_desc = 我们会通过 <code>POST</code> 请求将订阅事件信息发送至向指定 URL 地址。您可以设置不同的数据接收方式(JSON 或 <code>x-www-form-urlencoded</code>)。 请查阅 <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks 文档</a> 获取更多信息。
+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 c0f1d1e26a8be6192b3f4e0b7fe70b9f49cb3f20..43705061c95914abb91de1ff5ce978ff42e0a042 100644 (file)
--- 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 (file)
index 0000000..b8d15e5
--- /dev/null
@@ -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)
+}
index 26eef231914511e82289fba52e5fe94a509d0907..6abbca557b158fa13031632cc976b8c7212776e1 100644 (file)
@@ -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()
+}
index c6250f6d59036ace4dccf66a377816e2942ada85..78af58eac82e7d8e97e19ccb15ebf9f1d777cbf7 100644 (file)
@@ -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
+               }
+       }
+}
index 67e48108d9a072027cf14d41c5f877fc684cd998..b8fc4dec2e623b1d46a672ce74313083fe9cbaa1 100644 (file)
@@ -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{
index 9c3c8ded5d6d4ec603aae383b75ca0d46d5f7760..3ea6fcd48c9cc5ab53354795b30310ab1e4c2185 100644 (file)
@@ -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;
index 4a681994de99054e4bb74705f83852d999936280..b3de4273c850e9d128e2ced0c5545a1196f757f3 100644 (file)
 }
 
 // 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;
index 48089787fe9fe2bede5dead1d8978bd05e63697a..0e58029ec3eb46ee80064f3240b502fc3bf639ee 100644 (file)
@@ -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")
+}
index 7760f35b0d7a2a1276e65890c4a3f2796fad5f7f..e551dcfe409b549f7a8a8d7fa71435253ed23697 100644 (file)
@@ -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 (file)
index 0000000..23fc26e
--- /dev/null
@@ -0,0 +1,41 @@
+{{template "ng/base/head" .}}
+{{template "ng/base/header" .}}
+<div id="repo-wrapper">
+    {{template "repo/header" .}}
+       <div id="setting-wrapper" class="main-wrapper">
+           <div id="repo-setting" class="container clear">
+               {{template "repo/settings/nav" .}}
+               <div class="grid-4-5 left">
+                   <div class="setting-content">
+                       {{template "ng/base/alert" .}}
+                       <div id="setting-content">
+                           <div id="repo-hooks-panel" class="panel panel-radius">
+                               <div class="panel-header">
+                                       <strong>{{.i18n.Tr "repo.settings.githooks"}}</strong>
+                               </div>
+                               <form class="form form-align panel-body" id="repo-setting-form" action="{{.RepoLink}}/settings/hooks/git/{{.Hook.Name}}" method="post">
+                                   {{.CsrfTokenHtml}}
+                                       <div class="text-center panel-desc">{{.i18n.Tr "repo.settings.githook_edit_desc"}}</div>
+                                   {{with .Hook}}
+                                   <div class="field">
+                                       <label>{{$.i18n.Tr "repo.settings.githook_name"}}</label>
+                                       <label class="text-left">{{.Name}}</label>
+                                   </div>
+                                                   <div class="field clear">
+                                                       <label class="left" for="content">{{$.i18n.Tr "repo.settings.githook_content"}}</label>
+                                                       <textarea class="ipt-textarea ipt-large ipt-radius" id="content" name="content" cols="60" rows="20" wrap="off">{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
+                                                   </div>
+                                   <div class="field">
+                                       <span class="form-label"></span>
+                                       <button class="btn btn-green btn-large btn-radius" id="change-reponame-btn" href="#change-reponame-modal">{{$.i18n.Tr "repo.settings.update_githook"}}</button>
+                                   </div>
+                                   {{end}}
+                               </form>
+                           </div>
+                       </div>
+                   </div>
+               </div>
+           </div>
+       </div>
+</div>
+{{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 (file)
index 0000000..a059b0e
--- /dev/null
@@ -0,0 +1,37 @@
+{{template "ng/base/head" .}}
+{{template "ng/base/header" .}}
+<div id="repo-wrapper">
+    {{template "repo/header" .}}
+       <div id="setting-wrapper" class="main-wrapper">
+           <div id="repo-setting" class="container clear">
+               {{template "repo/settings/nav" .}}
+               <div class="grid-4-5 left">
+                   <div class="setting-content">
+                       {{template "ng/base/alert" .}}
+                       <div id="setting-content">
+                           <div id="repo-hooks-panel" class="panel panel-radius">
+                               <div class="panel-header">
+                                       <strong>{{.i18n.Tr "repo.settings.githooks"}}</strong>
+                               </div>
+                               <ul class="panel-body setting-list">
+                               <li>{{.i18n.Tr "repo.settings.githooks_desc" | Str2html}}</li>
+                               {{range .Hooks}}
+                                                               <li>
+                                                                       {{if .IsActive}}
+                                                                       <span class="left text-success"><i class="octicon octicon-check"></i></span>
+                                                                       {{else}}
+                                                                       <span class="left text-grey"><i class="octicon octicon-primitive-dot"></i></span>
+                                                                       {{end}}
+                                                                       <span>{{.Name}}</span>
+                                               <a href="{{$.RepoLink}}/settings/hooks/git/{{.Name}}" class="text-blue right"><i class="fa fa-pencil"></i></a>
+                                                               </li>
+                               {{end}}
+                               </ul>
+                           </div>
+                       </div>
+                   </div>
+               </div>
+           </div>
+       </div>
+</div>
+{{template "ng/base/footer" .}}
\ No newline at end of file
index ef0765feac56d94c47956e16b5988dbdfc28bd0d..16128f0151065fa6adcdf0fd4c132f15ef339ab4 100644 (file)
@@ -5,6 +5,7 @@
             <li {{if .PageIsSettingsOptions}}class="current"{{end}}><a href="{{.RepoLink}}/settings">{{.i18n.Tr "repo.settings.options"}}</a></li>
             <li {{if .PageIsSettingsCollaboration}}class="current"{{end}}><a href="{{.RepoLink}}/settings/collaboration">{{.i18n.Tr "repo.settings.collaboration"}}</a></li>
             <li {{if .PageIsSettingsHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks">{{.i18n.Tr "repo.settings.hooks"}}</a></li>
+            <li {{if .PageIsSettingsGitHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks/git">{{.i18n.Tr "repo.settings.githooks"}}</a></li>
             <!-- <li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.i18n.Tr "repo.settings.deploy_keys"}}</a></li> -->
         </ul>
     </div>