This commit is contained in:
Unknwon 2014-10-06 17:50:00 -04:00
parent 91e5c24a31
commit 64c68220d2
18 changed files with 317 additions and 19 deletions

View File

@ -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) ![Demo](https://gowalker.org/public/gogs_demo.gif)
##### Current version: 0.5.4 Beta ##### Current version: 0.5.5 Beta
### NOTICES ### NOTICES
@ -44,7 +44,7 @@ The goal of this project is to make the easiest, fastest and most painless way t
- Slack webhook integration - Slack webhook integration
- Supports MySQL, PostgreSQL and SQLite3 - Supports MySQL, PostgreSQL and SQLite3
- Social account login(GitHub, Google, QQ, Weibo) - 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 ## System Requirements

View File

@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。
![Demo](https://gowalker.org/public/gogs_demo.gif) ![Demo](https://gowalker.org/public/gogs_demo.gif)
##### 当前版本0.5.4 Beta ##### 当前版本0.5.5 Beta
## 开发目的 ## 开发目的
@ -35,7 +35,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
- Slack Web 钩子集成 - Slack Web 钩子集成
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库 - 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
- 社交帐号登录GitHub、Google、QQ、微博 - 社交帐号登录GitHub、Google、QQ、微博
- 多语言支持(英文、简体中文、德语、法语等等) - 多语言支持(英文、简体中文、德语、法语、荷兰语等等)
## 系统要求 ## 系统要求

View File

@ -313,6 +313,12 @@ func runWeb(*cli.Context) {
r.Get("/hooks/:id", repo.WebHooksEdit) r.Get("/hooks/:id", repo.WebHooksEdit)
r.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) r.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
r.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) 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) }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner)

View File

@ -70,6 +70,8 @@ ENABLE_CACHE_AVATAR = false
ENABLE_NOTIFY_MAIL = false ENABLE_NOTIFY_MAIL = false
; More detail: https://github.com/gogits/gogs/issues/165 ; More detail: https://github.com/gogits/gogs/issues/165
ENABLE_REVERSE_PROXY_AUTHENTICATION = false ENABLE_REVERSE_PROXY_AUTHENTICATION = false
; Repository Git hooks
ENABLE_GIT_HOOKS = false
[webhook] [webhook]
; Cron task interval in minutes ; Cron task interval in minutes

View File

@ -287,6 +287,7 @@ settings = Settings
settings.options = Options settings.options = Options
settings.collaboration = Collaboration settings.collaboration = Collaboration
settings.hooks = Webhooks settings.hooks = Webhooks
settings.githooks = Git Hooks
settings.deploy_keys = Deploy Keys settings.deploy_keys = Deploy Keys
settings.basic_settings = Basic Settings settings.basic_settings = Basic Settings
settings.danger_zone = Danger Zone 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.remove_collaborator_success = Collaborator has been removed.
settings.add_webhook = Add Webhook 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.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.remove_hook_success = Webhook has been removed.
settings.add_webhook_desc = Well 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.add_webhook_desc = Well 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 settings.payload_url = Payload URL

View File

@ -287,6 +287,7 @@ settings = 仓库设置
settings.options = 基本设置 settings.options = 基本设置
settings.collaboration = 管理协作者 settings.collaboration = 管理协作者
settings.hooks = 管理 Web 钩子 settings.hooks = 管理 Web 钩子
settings.githooks = 管理 Git 钩子
settings.deploy_keys = 管理部署密钥 settings.deploy_keys = 管理部署密钥
settings.basic_settings = 基本设置 settings.basic_settings = 基本设置
settings.danger_zone = 危险操作区 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.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.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.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.payload_url = 推送地址
settings.content_type = 数据格式 settings.content_type = 数据格式
settings.secret = 密钥文本 settings.secret = 密钥文本

View File

@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.5.4.1005 Beta" const APP_VER = "0.5.5.1006 Beta"
func init() { func init() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())

111
modules/git/hooks.go Normal file
View File

@ -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)
}

View File

@ -7,6 +7,7 @@ package git
import ( import (
"bytes" "bytes"
"container/list" "container/list"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
@ -46,3 +47,23 @@ func RefEndName(refStr string) string {
func filepathFromSHA1(rootdir, sha1 string) string { func filepathFromSHA1(rootdir, sha1 string) string {
return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:]) 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()
}

View 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
}
}
}

View File

@ -275,6 +275,7 @@ var Service struct {
LdapAuth bool LdapAuth bool
ActiveCodeLives int ActiveCodeLives int
ResetPwdCodeLives int ResetPwdCodeLives int
EnableGitHooks bool
} }
func newService() { func newService() {
@ -284,6 +285,7 @@ func newService() {
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW") Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR") Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION") Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION")
Service.EnableGitHooks = Cfg.MustBool("service", "ENABLE_GIT_HOOKS")
} }
var logLevels = map[string]string{ var logLevels = map[string]string{

View File

@ -480,6 +480,10 @@ dt {
.ipt-large { .ipt-large {
font-size: 14.4px; font-size: 14.4px;
} }
.ipt-textarea {
height: auto !important;
width: auto;
}
.ipt-disabled, .ipt-disabled,
input[disabled] { input[disabled] {
background-color: #f2f2f2 !important; background-color: #f2f2f2 !important;

View File

@ -116,25 +116,24 @@
} }
// input form elements // input form elements
.ipt { .ipt {
&:focus { &:focus {
border-color: @iptFocusBorderColor; border-color: @iptFocusBorderColor;
} }
} }
.ipt-radius { .ipt-radius {
border-radius: .25em; border-radius: .25em;
} }
.ipt-small { .ipt-small {
font-size: .8*@baseFontSize; font-size: .8*@baseFontSize;
} }
.ipt-large { .ipt-large {
font-size: 1.2*@baseFontSize; font-size: 1.2*@baseFontSize;
} }
.ipt-textarea {
height: auto !important;
width: auto;
}
.ipt-disabled, .ipt-disabled,
input[disabled] { input[disabled] {
background-color: @iptDisabledColor !important; background-color: @iptDisabledColor !important;
@ -144,14 +143,12 @@ input[disabled] {
color: #888; color: #888;
cursor: not-allowed; cursor: not-allowed;
} }
.ipt-readonly, .ipt-readonly,
input[readonly] { input[readonly] {
&:focus { &:focus {
background-color: @iptDisabledColor !important; background-color: @iptDisabledColor !important;
} }
} }
.ipt-error { .ipt-error {
border-color: @iptErrorBorderColor !important; border-color: @iptErrorBorderColor !important;
background-color: @iptErrorFocusColor !important; background-color: @iptErrorFocusColor !important;

View File

@ -16,6 +16,7 @@ import (
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/mailer" "github.com/gogits/gogs/modules/mailer"
"github.com/gogits/gogs/modules/middleware" "github.com/gogits/gogs/modules/middleware"
@ -26,6 +27,8 @@ const (
SETTINGS_OPTIONS base.TplName = "repo/settings/options" SETTINGS_OPTIONS base.TplName = "repo/settings/options"
COLLABORATION base.TplName = "repo/settings/collaboration" COLLABORATION base.TplName = "repo/settings/collaboration"
HOOKS base.TplName = "repo/settings/hooks" 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" HOOK_NEW base.TplName = "repo/settings/hook_new"
ORG_HOOK_NEW base.TplName = "org/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") 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")
}

View File

@ -1 +1 @@
0.5.4.1005 Beta 0.5.5.1006 Beta

View File

@ -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" .}}

View File

@ -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" .}}

View File

@ -5,6 +5,7 @@
<li {{if .PageIsSettingsOptions}}class="current"{{end}}><a href="{{.RepoLink}}/settings">{{.i18n.Tr "repo.settings.options"}}</a></li> <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 .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 .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> --> <!-- <li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.i18n.Tr "repo.settings.deploy_keys"}}</a></li> -->
</ul> </ul>
</div> </div>