git: delegate all server-side Git hooks (#1623)

master
Unknwon 2017-02-14 16:22:16 -05:00
parent 859009259a
commit 039dc33367
No known key found for this signature in database
GPG Key ID: 25B575AE3213B2B3
22 changed files with 337 additions and 185 deletions

View File

@ -18,7 +18,7 @@ The issue will be closed without any reasons if it does not satisfy any of follo
- [ ] Yes (provide example URL) - [ ] Yes (provide example URL)
- [ ] No - [ ] No
- [ ] Not relevant - [ ] Not relevant
- Log gist: - Log gist (usually found in `log/gogs.log`):
## Description ## Description

127
cmd/hook.go Normal file
View File

@ -0,0 +1,127 @@
// 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 cmd
import (
"bufio"
"bytes"
"os"
"os/exec"
"path/filepath"
"github.com/urfave/cli"
"github.com/gogits/gogs/models"
)
var (
CmdHook = cli.Command{
Name: "hook",
Usage: "Delegate commands to corresponding Git hooks",
Description: "All sub-commands should only be called by Git",
Flags: []cli.Flag{
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
},
Subcommands: []cli.Command{
subcmdHookPreReceive,
subcmdHookUpadte,
subcmdHookPostReceive,
},
}
subcmdHookPreReceive = cli.Command{
Name: "pre-receive",
Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPreReceive,
}
subcmdHookUpadte = cli.Command{
Name: "update",
Usage: "Delegate update Git hook",
Description: "This command should only be called by Git",
Action: runHookUpdate,
}
subcmdHookPostReceive = cli.Command{
Name: "post-receive",
Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPostReceive,
}
)
func runHookPreReceive(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
setup(c, "hooks/pre-receive.log")
buf := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
buf.Write(scanner.Bytes())
buf.WriteByte('\n')
}
customHooksPath := os.Getenv(_ENV_REPO_CUSTOM_HOOKS_PATH)
hookCmd := exec.Command(filepath.Join(customHooksPath, "pre-receive"))
hookCmd.Stdout = os.Stdout
hookCmd.Stdin = buf
hookCmd.Stderr = os.Stderr
if err := hookCmd.Run(); err != nil {
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err)
}
return nil
}
func runHookUpdate(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
setup(c, "hooks/update.log")
args := c.Args()
if len(args) != 3 {
fail("Arguments received are not equal to three", "Arguments received are not equal to three")
} else if len(args[0]) == 0 {
fail("First argument 'refName' is empty", "First argument 'refName' is empty")
}
uuid := os.Getenv(_ENV_UPDATE_TASK_UUID)
if err := models.AddUpdateTask(&models.UpdateTask{
UUID: uuid,
RefName: args[0],
OldCommitID: args[1],
NewCommitID: args[2],
}); err != nil {
fail("Internal error", "Fail to add update task '%s': %v", uuid, err)
}
customHooksPath := os.Getenv(_ENV_REPO_CUSTOM_HOOKS_PATH)
hookCmd := exec.Command(filepath.Join(customHooksPath, "update"), args...)
hookCmd.Stdout = os.Stdout
hookCmd.Stdin = os.Stdin
hookCmd.Stderr = os.Stderr
if err := hookCmd.Run(); err != nil {
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err)
}
return nil
}
func runHookPostReceive(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
setup(c, "hooks/post-receive.log")
customHooksPath := os.Getenv(_ENV_REPO_CUSTOM_HOOKS_PATH)
hookCmd := exec.Command(filepath.Join(customHooksPath, "post-receive"))
hookCmd.Stdout = os.Stdout
hookCmd.Stdin = os.Stdin
hookCmd.Stderr = os.Stderr
if err := hookCmd.Run(); err != nil {
fail("Internal error", "Fail to execute custom post-receive hook: %v", err)
}
return nil
}

View File

@ -14,7 +14,7 @@ import (
"time" "time"
"github.com/Unknwon/com" "github.com/Unknwon/com"
git "github.com/gogits/git-module" "github.com/gogits/git-module"
gouuid "github.com/satori/go.uuid" gouuid "github.com/satori/go.uuid"
"github.com/urfave/cli" "github.com/urfave/cli"
log "gopkg.in/clog.v1" log "gopkg.in/clog.v1"
@ -26,10 +26,12 @@ import (
) )
const ( const (
_ACCESS_DENIED_MESSAGE = "Repository does not exist or you do not have access" _ACCESS_DENIED_MESSAGE = "Repository does not exist or you do not have access"
_ENV_UPDATE_TASK_UUID = "UPDATE_TASK_UUID"
_ENV_REPO_CUSTOM_HOOKS_PATH = "REPO_CUSTOM_HOOKS_PATH"
) )
var CmdServ = cli.Command{ var Serv = cli.Command{
Name: "serv", Name: "serv",
Usage: "This command should only be called by SSH shell", Usage: "This command should only be called by SSH shell",
Description: `Serv provide access auth for repositories`, Description: `Serv provide access auth for repositories`,
@ -39,7 +41,13 @@ var CmdServ = cli.Command{
}, },
} }
func setup(logPath string) { func setup(c *cli.Context, logPath string) {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
} else if c.GlobalIsSet("config") {
setting.CustomConf = c.GlobalString("config")
}
setting.NewContext() setting.NewContext()
setting.NewService() setting.NewService()
log.New(log.FILE, log.FileConfig{ log.New(log.FILE, log.FileConfig{
@ -54,7 +62,7 @@ func setup(logPath string) {
models.LoadConfigs() models.LoadConfigs()
if setting.UseSQLite3 || setting.UseTiDB { if setting.UseSQLite3 {
workDir, _ := setting.WorkDir() workDir, _ := setting.WorkDir()
os.Chdir(workDir) os.Chdir(workDir)
} }
@ -62,7 +70,7 @@ func setup(logPath string) {
models.SetEngine() models.SetEngine()
} }
func parseCmd(cmd string) (string, string) { func parseSSHCmd(cmd string) (string, string) {
ss := strings.SplitN(cmd, " ", 2) ss := strings.SplitN(cmd, " ", 2)
if len(ss) != 2 { if len(ss) != 2 {
return "", "" return "", ""
@ -157,11 +165,7 @@ func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string,
} }
func runServ(c *cli.Context) error { func runServ(c *cli.Context) error {
if c.IsSet("config") { setup(c, "serv.log")
setting.CustomConf = c.String("config")
}
setup("serv.log")
if setting.SSH.Disabled { if setting.SSH.Disabled {
println("Gogs: SSH has been disabled") println("Gogs: SSH has been disabled")
@ -172,21 +176,21 @@ func runServ(c *cli.Context) error {
fail("Not enough arguments", "Not enough arguments") fail("Not enough arguments", "Not enough arguments")
} }
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") sshCmd := os.Getenv("SSH_ORIGINAL_COMMAND")
if len(cmd) == 0 { if len(sshCmd) == 0 {
println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.") println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.")
println("If this is unexpected, please log in with password and setup Gogs under another user.") println("If this is unexpected, please log in with password and setup Gogs under another user.")
return nil return nil
} }
verb, args := parseCmd(cmd) verb, args := parseSSHCmd(sshCmd)
repoPath := strings.ToLower(strings.Trim(args, "'")) repoFullName := strings.ToLower(strings.Trim(args, "'"))
rr := strings.SplitN(repoPath, "/", 2) repoFields := strings.SplitN(repoFullName, "/", 2)
if len(rr) != 2 { if len(repoFields) != 2 {
fail("Invalid repository path", "Invalid repository path: %v", args) fail("Invalid repository path", "Invalid repository path: %v", args)
} }
username := strings.ToLower(rr[0]) username := strings.ToLower(repoFields[0])
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) reponame := strings.ToLower(strings.TrimSuffix(repoFields[1], ".git"))
isWiki := false isWiki := false
if strings.HasSuffix(reponame, ".wiki") { if strings.HasSuffix(reponame, ".wiki") {
@ -194,29 +198,30 @@ func runServ(c *cli.Context) error {
reponame = reponame[:len(reponame)-5] reponame = reponame[:len(reponame)-5]
} }
repoUser, err := models.GetUserByName(username) repoOwner, err := models.GetUserByName(username)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
fail("Repository owner does not exist", "Unregistered owner: %s", username) fail("Repository owner does not exist", "Unregistered owner: %s", username)
} }
fail("Internal error", "Failed to get repository owner (%s): %v", username, err) fail("Internal error", "Fail to get repository owner '%s': %v", username, err)
} }
repo, err := models.GetRepositoryByName(repoUser.ID, reponame) repo, err := models.GetRepositoryByName(repoOwner.ID, reponame)
if err != nil { if err != nil {
if models.IsErrRepoNotExist(err) { if models.IsErrRepoNotExist(err) {
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoUser.Name, reponame) fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoOwner.Name, reponame)
} }
fail("Internal error", "Failed to get repository: %v", err) fail("Internal error", "Fail to get repository: %v", err)
} }
repo.Owner = repoOwner
requestedMode, has := allowedCommands[verb] requestMode, ok := allowedCommands[verb]
if !has { if !ok {
fail("Unknown git command", "Unknown git command %s", verb) fail("Unknown git command", "Unknown git command '%s'", verb)
} }
// Prohibit push to mirror repositories. // Prohibit push to mirror repositories.
if requestedMode > models.ACCESS_MODE_READ && repo.IsMirror { if requestMode > models.ACCESS_MODE_READ && repo.IsMirror {
fail("mirror repository is read-only", "") fail("mirror repository is read-only", "")
} }
@ -225,33 +230,35 @@ func runServ(c *cli.Context) error {
key, err := models.GetPublicKeyByID(com.StrTo(strings.TrimPrefix(c.Args()[0], "key-")).MustInt64()) key, err := models.GetPublicKeyByID(com.StrTo(strings.TrimPrefix(c.Args()[0], "key-")).MustInt64())
if err != nil { if err != nil {
fail("Invalid key ID", "Invalid key ID [%s]: %v", c.Args()[0], err) fail("Invalid key ID", "Invalid key ID '%s': %v", c.Args()[0], err)
} }
if requestedMode == models.ACCESS_MODE_WRITE || repo.IsPrivate { if requestMode == models.ACCESS_MODE_WRITE || repo.IsPrivate {
// Check deploy key or user key. // Check deploy key or user key.
if key.IsDeployKey() { if key.IsDeployKey() {
if key.Mode < requestedMode { if key.Mode < requestMode {
fail("Key permission denied", "Cannot push with deployment key: %d", key.ID) fail("Key permission denied", "Cannot push with deployment key: %d", key.ID)
} }
checkDeployKey(key, repo) checkDeployKey(key, repo)
} else { } else {
user, err = models.GetUserByKeyID(key.ID) user, err = models.GetUserByKeyID(key.ID)
if err != nil { if err != nil {
fail("internal error", "Failed to get user by key ID(%d): %v", key.ID, err) fail("Internal error", "Fail to get user by key ID '%d': %v", key.ID, err)
} }
mode, err := models.AccessLevel(user, repo) mode, err := models.AccessLevel(user, repo)
if err != nil { if err != nil {
fail("Internal error", "Fail to check access: %v", err) fail("Internal error", "Fail to check access: %v", err)
} else if mode < requestedMode { }
if mode < requestMode {
clientMessage := _ACCESS_DENIED_MESSAGE clientMessage := _ACCESS_DENIED_MESSAGE
if mode >= models.ACCESS_MODE_READ { if mode >= models.ACCESS_MODE_READ {
clientMessage = "You do not have sufficient authorization for this action" clientMessage = "You do not have sufficient authorization for this action"
} }
fail(clientMessage, fail(clientMessage,
"User %s does not have level %v access to repository %s", "User '%s' does not have level '%v' access to repository '%s'",
user.Name, requestedMode, repoPath) user.Name, requestMode, repoFullName)
} }
} }
} else { } else {
@ -265,30 +272,31 @@ func runServ(c *cli.Context) error {
} }
uuid := gouuid.NewV4().String() uuid := gouuid.NewV4().String()
os.Setenv("uuid", uuid) os.Setenv(_ENV_UPDATE_TASK_UUID, uuid)
os.Setenv(_ENV_REPO_CUSTOM_HOOKS_PATH, filepath.Join(repo.RepoPath(), "custom_hooks"))
// Special handle for Windows. // Special handle for Windows.
if setting.IsWindows { if setting.IsWindows {
verb = strings.Replace(verb, "-", " ", 1) verb = strings.Replace(verb, "-", " ", 1)
} }
var gitcmd *exec.Cmd var gitCmd *exec.Cmd
verbs := strings.Split(verb, " ") verbs := strings.Split(verb, " ")
if len(verbs) == 2 { if len(verbs) == 2 {
gitcmd = exec.Command(verbs[0], verbs[1], repoPath) gitCmd = exec.Command(verbs[0], verbs[1], repoFullName)
} else { } else {
gitcmd = exec.Command(verb, repoPath) gitCmd = exec.Command(verb, repoFullName)
} }
gitcmd.Dir = setting.RepoRootPath gitCmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout gitCmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin gitCmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr gitCmd.Stderr = os.Stderr
if err = gitcmd.Run(); err != nil { if err = gitCmd.Run(); err != nil {
fail("Internal error", "Failed to execute git command: %v", err) fail("Internal error", "Fail to execute git command: %v", err)
} }
if requestedMode == models.ACCESS_MODE_WRITE { if requestMode == models.ACCESS_MODE_WRITE {
handleUpdateTask(uuid, user, repoUser, reponame, isWiki) handleUpdateTask(uuid, user, repoOwner, reponame, isWiki)
} }
// Update user key activity. // Update user key activity.

View File

@ -1,58 +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 cmd
import (
"os"
"github.com/urfave/cli"
log "gopkg.in/clog.v1"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/setting"
)
var CmdUpdate = cli.Command{
Name: "update",
Usage: "This command should only be called by Git hook",
Description: `Update get pushed info and insert into database`,
Action: runUpdate,
Flags: []cli.Flag{
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
},
}
func runUpdate(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
setup("update.log")
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
log.Trace("SSH_ORIGINAL_COMMAND is empty")
return nil
}
args := c.Args()
if len(args) != 3 {
log.Fatal(2, "Arguments received are not equal to three")
} else if len(args[0]) == 0 {
log.Fatal(2, "First argument 'refName' is empty, shouldn't use")
}
task := models.UpdateTask{
UUID: os.Getenv("uuid"),
RefName: args[0],
OldCommitID: args[1],
NewCommitID: args[2],
}
if err := models.AddUpdateTask(&task); err != nil {
log.Fatal(2, "AddUpdateTask: %v", err)
}
return nil
}

View File

@ -84,6 +84,7 @@ func checkVersion() {
} }
// Check dependency version. // Check dependency version.
// LEGACY [0.11]: no need to check version as we check in vendor into version control
checkers := []VerChecker{ checkers := []VerChecker{
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.6.0"}, {"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.6.0"},
{"github.com/go-macaron/binding", binding.Version, "0.3.2"}, {"github.com/go-macaron/binding", binding.Version, "0.3.2"},
@ -94,7 +95,7 @@ func checkVersion() {
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"}, {"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
{"gopkg.in/ini.v1", ini.Version, "1.8.4"}, {"gopkg.in/ini.v1", ini.Version, "1.8.4"},
{"gopkg.in/macaron.v1", macaron.Version, "1.1.7"}, {"gopkg.in/macaron.v1", macaron.Version, "1.1.7"},
{"github.com/gogits/git-module", git.Version, "0.4.6"}, {"github.com/gogits/git-module", git.Version, "0.4.7"},
{"github.com/gogits/go-gogs-client", gogs.Version, "0.12.1"}, {"github.com/gogits/go-gogs-client", gogs.Version, "0.12.1"},
} }
for _, c := range checkers { for _, c := range checkers {

View File

@ -918,8 +918,8 @@ dashboard.git_gc_repos = Do garbage collection on repositories
dashboard.git_gc_repos_success = All repositories have done garbage collection successfully. dashboard.git_gc_repos_success = All repositories have done garbage collection successfully.
dashboard.resync_all_sshkeys = Rewrite '.ssh/authorized_keys' file (caution: non-Gogs keys will be lost) dashboard.resync_all_sshkeys = Rewrite '.ssh/authorized_keys' file (caution: non-Gogs keys will be lost)
dashboard.resync_all_sshkeys_success = All public keys have been rewritten successfully. dashboard.resync_all_sshkeys_success = All public keys have been rewritten successfully.
dashboard.resync_all_update_hooks = Rewrite all update hook of repositories (needed when custom config path is changed) dashboard.resync_all_hooks = Resync pre-receive, update and post-receive hooks of all repositories.
dashboard.resync_all_update_hooks_success = All repositories' update hook have been rewritten successfully. dashboard.resync_all_hooks_success = All repositories' pre-receive, update and post-receive hooks have been resynced successfully.
dashboard.reinit_missing_repos = Reinitialize all repository records that lost Git files dashboard.reinit_missing_repos = Reinitialize all repository records that lost Git files
dashboard.reinit_missing_repos_success = All repository records that lost Git files have been reinitialized successfully. dashboard.reinit_missing_repos_success = All repository records that lost Git files have been reinitialized successfully.

View File

@ -16,7 +16,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.9.146.0214" const APP_VER = "0.9.147.0214"
func init() { func init() {
setting.AppVer = APP_VER setting.AppVer = APP_VER
@ -29,8 +29,8 @@ func main() {
app.Version = APP_VER app.Version = APP_VER
app.Commands = []cli.Command{ app.Commands = []cli.Command{
cmd.CmdWeb, cmd.CmdWeb,
cmd.CmdServ, cmd.Serv,
cmd.CmdUpdate, cmd.CmdHook,
cmd.CmdDump, cmd.CmdDump,
cmd.CmdCert, cmd.CmdCert,
cmd.CmdAdmin, cmd.CmdAdmin,

View File

@ -468,7 +468,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
} }
if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil { if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil {
log.Error(4, "updateIssuesCommit: %v", err) log.Error(4, "UpdateIssuesCommit: %v", err)
} }
} }

View File

@ -72,6 +72,8 @@ var migrations = []Migration{
// v13 -> v14:v0.9.87 // v13 -> v14:v0.9.87
NewMigration("set comment updated with created", setCommentUpdatedWithCreated), NewMigration("set comment updated with created", setCommentUpdatedWithCreated),
// v14 -> v15:v0.9.147
NewMigration("generate and migrate Git hooks", generateAndMigrateGitHooks),
} }
// Migrate database to current version // Migrate database to current version

82
models/migrations/v15.go Normal file
View File

@ -0,0 +1,82 @@
// 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 migrations
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/setting"
)
func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
type Repository struct {
ID int64
OwnerID int64
Name string
}
type User struct {
ID int64
Name string
}
var (
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
}
)
// Cleanup old update.log files.
filepath.Walk(setting.LogRootPath, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() && strings.HasPrefix(filepath.Base(path), "update.log") {
os.Remove(path)
}
return nil
})
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
user := new(User)
has, err := x.Where("id = ?", repo.OwnerID).Get(user)
if err != nil {
return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err)
} else if !has {
return nil
}
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git"
hookDir := filepath.Join(repoPath, "hooks")
customHookDir := filepath.Join(repoPath, "custom_hooks")
for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(customHookDir, hookName)
// Gogs didn't allow user to set custom update hook thus no migration for it.
// In case user runs this migration multiple times, and custom hook exists,
// we assume it's been migrated already.
if hookName != "update" && com.IsFile(oldHookPath) && !com.IsExist(newHookPath) {
os.MkdirAll(customHookDir, os.ModePerm)
if err = os.Rename(oldHookPath, newHookPath); err != nil {
return fmt.Errorf("move hook file to custom directory '%s' -> '%s': %v", oldHookPath, newHookPath, err)
}
}
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil {
return fmt.Errorf("write hook file '%s': %v", oldHookPath, err)
}
}
return nil
})
}

View File

@ -88,8 +88,6 @@ func LoadConfigs() {
setting.UsePostgreSQL = true setting.UsePostgreSQL = true
case "mssql": case "mssql":
setting.UseMSSQL = true setting.UseMSSQL = true
case "tidb":
setting.UseTiDB = true
} }
DbCfg.Host = sec.Key("HOST").String() DbCfg.Host = sec.Key("HOST").String()
DbCfg.Name = sec.Key("NAME").String() DbCfg.Name = sec.Key("NAME").String()

View File

@ -36,10 +36,6 @@ import (
"github.com/gogits/gogs/modules/sync" "github.com/gogits/gogs/modules/sync"
) )
const (
_TPL_UPDATE_HOOK = "#!/usr/bin/env %s\n%s update $1 $2 $3 --config='%s'\n"
)
var repoWorkingPool = sync.NewExclusivePool() var repoWorkingPool = sync.NewExclusivePool()
var ( var (
@ -125,6 +121,7 @@ func NewRepoContext() {
if version.Compare("1.7.1", setting.Git.Version, ">") { if version.Compare("1.7.1", setting.Git.Version, ">") {
log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1") log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1")
} }
git.HookDir = "custom_hooks"
// Git requires setting user.name and user.email in order to commit changes. // Git requires setting user.name and user.email in order to commit changes.
for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} { for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} {
@ -715,20 +712,33 @@ func cleanUpMigrateGitConfig(configPath string) error {
return nil return nil
} }
func createUpdateHook(repoPath string) error { var hooksTpls = map[string]string{
return git.SetUpdateHook(repoPath, "pre-receive": "#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n",
fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf)) "update": "#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n",
"post-receive": "#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n",
}
func createDelegateHooks(repoPath string) (err error) {
for _, name := range git.HookNames {
hookPath := filepath.Join(repoPath, "hooks", name)
if err = ioutil.WriteFile(hookPath,
[]byte(fmt.Sprintf(hooksTpls[name], setting.ScriptType, setting.AppPath, setting.CustomConf)),
os.ModePerm); err != nil {
return fmt.Errorf("create delegate hook '%s': %v", hookPath, err)
}
}
return nil
} }
// Finish migrating repository and/or wiki with things that don't need to be done for mirrors. // Finish migrating repository and/or wiki with things that don't need to be done for mirrors.
func CleanUpMigrateInfo(repo *Repository) (*Repository, error) { func CleanUpMigrateInfo(repo *Repository) (*Repository, error) {
repoPath := repo.RepoPath() repoPath := repo.RepoPath()
if err := createUpdateHook(repoPath); err != nil { if err := createDelegateHooks(repoPath); err != nil {
return repo, fmt.Errorf("createUpdateHook: %v", err) return repo, fmt.Errorf("createDelegateHooks: %v", err)
} }
if repo.HasWiki() { if repo.HasWiki() {
if err := createUpdateHook(repo.WikiPath()); err != nil { if err := createDelegateHooks(repo.WikiPath()); err != nil {
return repo, fmt.Errorf("createUpdateHook (wiki): %v", err) return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err)
} }
} }
@ -737,7 +747,7 @@ func CleanUpMigrateInfo(repo *Repository) (*Repository, error) {
} }
if repo.HasWiki() { if repo.HasWiki() {
if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil { if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil {
return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %v", err) return repo, fmt.Errorf("cleanUpMigrateGitConfig.(wiki): %v", err)
} }
} }
@ -862,8 +872,8 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C
// Init bare new repository. // Init bare new repository.
if err = git.InitRepository(repoPath, true); err != nil { if err = git.InitRepository(repoPath, true); err != nil {
return fmt.Errorf("InitRepository: %v", err) return fmt.Errorf("InitRepository: %v", err)
} else if err = createUpdateHook(repoPath); err != nil { } else if err = createDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createUpdateHook: %v", err) return fmt.Errorf("createDelegateHooks: %v", err)
} }
tmpDir := filepath.Join(os.TempDir(), "gogs-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) tmpDir := filepath.Join(os.TempDir(), "gogs-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond()))
@ -1648,12 +1658,12 @@ func ReinitMissingRepositories() error {
return nil return nil
} }
// RewriteRepositoryUpdateHook rewrites all repositories' update hook. // SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks
func RewriteRepositoryUpdateHook() error { // to make sure the binary and custom conf path are up-to-date.
func SyncRepositoryHooks() error {
return x.Where("id > 0").Iterate(new(Repository), return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error { func(idx int, bean interface{}) error {
repo := bean.(*Repository) return createDelegateHooks(bean.(*Repository).RepoPath())
return createUpdateHook(repo.RepoPath())
}) })
} }
@ -2098,21 +2108,21 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
repoPath := RepoPath(u.Name, repo.Name) repoPath := RepoPath(u.Name, repo.Name)
_, stderr, err := process.ExecTimeout(10*time.Minute, _, stderr, err := process.ExecTimeout(10*time.Minute,
fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name), fmt.Sprintf("ForkRepository 'git clone': %s/%s", u.Name, repo.Name),
"git", "clone", "--bare", oldRepo.RepoPath(), repoPath) "git", "clone", "--bare", oldRepo.RepoPath(), repoPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("git clone: %v", stderr) return nil, fmt.Errorf("git clone: %v", stderr)
} }
_, stderr, err = process.ExecDir(-1, _, stderr, err = process.ExecDir(-1,
repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath), repoPath, fmt.Sprintf("ForkRepository 'git update-server-info': %s", repoPath),
"git", "update-server-info") "git", "update-server-info")
if err != nil { if err != nil {
return nil, fmt.Errorf("git update-server-info: %v", err) return nil, fmt.Errorf("git update-server-info: %v", err)
} }
if err = createUpdateHook(repoPath); err != nil { if err = createDelegateHooks(repoPath); err != nil {
return nil, fmt.Errorf("createUpdateHook: %v", err) return nil, fmt.Errorf("createDelegateHooks: %v", err)
} }
return repo, sess.Commit() return repo, sess.Commit()

View File

@ -64,8 +64,8 @@ func (repo *Repository) InitWiki() error {
if err := git.InitRepository(repo.WikiPath(), true); err != nil { if err := git.InitRepository(repo.WikiPath(), true); err != nil {
return fmt.Errorf("InitRepository: %v", err) return fmt.Errorf("InitRepository: %v", err)
} else if err = createUpdateHook(repo.WikiPath()); err != nil { } else if err = createDelegateHooks(repo.WikiPath()); err != nil {
return fmt.Errorf("createUpdateHook: %v", err) return fmt.Errorf("createDelegateHooks: %v", err)
} }
return nil return nil
} }

File diff suppressed because one or more lines are too long

View File

@ -106,7 +106,6 @@ var (
UseMySQL bool UseMySQL bool
UsePostgreSQL bool UsePostgreSQL bool
UseMSSQL bool UseMSSQL bool
UseTiDB bool
// Webhook settings // Webhook settings
Webhook struct { Webhook struct {

View File

@ -121,7 +121,7 @@ const (
CLEAN_MISSING_REPOS CLEAN_MISSING_REPOS
GIT_GC_REPOS GIT_GC_REPOS
SYNC_SSH_AUTHORIZED_KEY SYNC_SSH_AUTHORIZED_KEY
SYNC_REPOSITORY_UPDATE_HOOK SYNC_REPOSITORY_HOOKS
REINIT_MISSING_REPOSITORY REINIT_MISSING_REPOSITORY
) )
@ -152,9 +152,9 @@ func Dashboard(ctx *context.Context) {
case SYNC_SSH_AUTHORIZED_KEY: case SYNC_SSH_AUTHORIZED_KEY:
success = ctx.Tr("admin.dashboard.resync_all_sshkeys_success") success = ctx.Tr("admin.dashboard.resync_all_sshkeys_success")
err = models.RewriteAllPublicKeys() err = models.RewriteAllPublicKeys()
case SYNC_REPOSITORY_UPDATE_HOOK: case SYNC_REPOSITORY_HOOKS:
success = ctx.Tr("admin.dashboard.resync_all_update_hooks_success") success = ctx.Tr("admin.dashboard.resync_all_hooks_success")
err = models.RewriteRepositoryUpdateHook() err = models.SyncRepositoryHooks()
case REINIT_MISSING_REPOSITORY: case REINIT_MISSING_REPOSITORY:
success = ctx.Tr("admin.dashboard.reinit_missing_repos_success") success = ctx.Tr("admin.dashboard.reinit_missing_repos_success")
err = models.ReinitMissingRepositories() err = models.ReinitMissingRepositories()

View File

@ -65,7 +65,7 @@ func GlobalInit() {
highlight.NewContext() highlight.NewContext()
markdown.BuildSanitizer() markdown.BuildSanitizer()
if err := models.NewEngine(); err != nil { if err := models.NewEngine(); err != nil {
log.Fatal(4, "Fail to initialize ORM engine: %v", err) log.Fatal(2, "Fail to initialize ORM engine: %v", err)
} }
models.HasEngine = true models.HasEngine = true

View File

@ -1 +1 @@
0.9.146.0214 0.9.147.0214

View File

@ -40,7 +40,7 @@
<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=5">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td> <td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=5">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
</tr> </tr>
<tr> <tr>
<td>{{.i18n.Tr "admin.dashboard.resync_all_update_hooks"}}</td> <td>{{.i18n.Tr "admin.dashboard.resync_all_hooks"}}</td>
<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=6">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td> <td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=6">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
</tr> </tr>
<tr> <tr>

View File

@ -10,7 +10,7 @@ import (
"time" "time"
) )
const _VERSION = "0.4.6" const _VERSION = "0.4.7"
func Version() string { func Version() string {
return _VERSION return _VERSION

View File

@ -10,16 +10,18 @@ import (
"os" "os"
"path" "path"
"strings" "strings"
"github.com/Unknwon/com"
) )
// hookNames is a list of Git server hooks' name that are supported. var (
var hookNames = []string{ // Direcotry of hook file. Can be changed to "custom_hooks" for very purpose.
"pre-receive", HookDir = "hooks"
// "update", // HookNames is a list of Git server hooks' name that are supported.
"post-receive", HookNames = []string{
} "pre-receive",
"update",
"post-receive",
}
)
var ( var (
ErrNotValidHook = errors.New("not a valid Git hook") ErrNotValidHook = errors.New("not a valid Git hook")
@ -27,7 +29,7 @@ var (
// IsValidHookName returns true if given name is a valid Git hook. // IsValidHookName returns true if given name is a valid Git hook.
func IsValidHookName(name string) bool { func IsValidHookName(name string) bool {
for _, hn := range hookNames { for _, hn := range HookNames {
if hn == name { if hn == name {
return true return true
} }
@ -51,7 +53,7 @@ func GetHook(repoPath, name string) (*Hook, error) {
} }
h := &Hook{ h := &Hook{
name: name, name: name,
path: path.Join(repoPath, "hooks", name), path: path.Join(repoPath, HookDir, name),
} }
if isFile(h.path) { if isFile(h.path) {
data, err := ioutil.ReadFile(h.path) data, err := ioutil.ReadFile(h.path)
@ -74,7 +76,7 @@ func (h *Hook) Name() string {
return h.name return h.name
} }
// Update updates hook settings. // Update updates content hook file.
func (h *Hook) Update() error { func (h *Hook) Update() error {
if len(strings.TrimSpace(h.Content)) == 0 { if len(strings.TrimSpace(h.Content)) == 0 {
if isExist(h.path) { if isExist(h.path) {
@ -91,8 +93,8 @@ func ListHooks(repoPath string) (_ []*Hook, err error) {
return nil, errors.New("hooks path does not exist") return nil, errors.New("hooks path does not exist")
} }
hooks := make([]*Hook, len(hookNames)) hooks := make([]*Hook, len(HookNames))
for i, name := range hookNames { for i, name := range HookNames {
hooks[i], err = GetHook(repoPath, name) hooks[i], err = GetHook(repoPath, name)
if err != nil { if err != nil {
return nil, err return nil, err
@ -100,22 +102,3 @@ func ListHooks(repoPath string) (_ []*Hook, err error) {
} }
return hooks, nil return hooks, nil
} }
const (
HOOK_PATH_UPDATE = "hooks/update"
)
// SetUpdateHook writes given content to update hook of the reposiotry.
func SetUpdateHook(repoPath, content string) (err error) {
log("Setting update hook: %s", repoPath)
hookPath := path.Join(repoPath, HOOK_PATH_UPDATE)
if com.IsExist(hookPath) {
err = os.Remove(hookPath)
} else {
err = os.MkdirAll(path.Dir(hookPath), os.ModePerm)
}
if err != nil {
return err
}
return ioutil.WriteFile(hookPath, []byte(content), 0777)
}

6
vendor/vendor.json vendored
View File

@ -159,10 +159,10 @@
"revisionTime": "2016-08-10T03:50:02Z" "revisionTime": "2016-08-10T03:50:02Z"
}, },
{ {
"checksumSHA1": "ZHQdOAFE192O5dAsLx0drW+VP8U=", "checksumSHA1": "eH7yo/XLaT4A9yurJ0rrRxdbBTE=",
"path": "github.com/gogits/git-module", "path": "github.com/gogits/git-module",
"revision": "172cbc21accbf0085a58fd0832f46a9f694130e8", "revision": "4d18cee9bde82bffe8c91747f1585afbf06311b2",
"revisionTime": "2017-01-31T23:38:55Z" "revisionTime": "2017-02-14T20:50:54Z"
}, },
{ {
"checksumSHA1": "SdCLcPmklkXjPVMGkG1pYNmuO2Q=", "checksumSHA1": "SdCLcPmklkXjPVMGkG1pYNmuO2Q=",