| @@ -5,7 +5,7 @@ Gogs(Go Git Service) is a painless self-hosted Git Service written in Go. | |||
|  | |||
| ##### 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 | |||
| @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。 | |||
|  | |||
| ##### 当前版本:0.5.4 Beta | |||
| ##### 当前版本:0.5.5 Beta | |||
| ## 开发目的 | |||
| @@ -35,7 +35,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自 | |||
| - Slack Web 钩子集成 | |||
| - 支持 MySQL、PostgreSQL 以及 SQLite3 数据库 | |||
| - 社交帐号登录(GitHub、Google、QQ、微博) | |||
| - 多语言支持(英文、简体中文、德语、法语等等) | |||
| - 多语言支持(英文、简体中文、德语、法语、荷兰语等等) | |||
| ## 系统要求 | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 = 密钥文本 | |||
| @@ -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()) | |||
| @@ -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) | |||
| } | |||
| @@ -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() | |||
| } | |||
| @@ -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 | |||
| } | |||
| } | |||
| } | |||
| @@ -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{ | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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") | |||
| } | |||
| @@ -1 +1 @@ | |||
| 0.5.4.1005 Beta | |||
| 0.5.5.1006 Beta | |||
| @@ -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" .}} | |||
| @@ -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" .}} | |||
| @@ -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> | |||