Browse Source

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

Unknwon 7 years ago
parent
commit
039dc33367

+ 1 - 1
.github/ISSUE_TEMPLATE.md

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

+ 127 - 0
cmd/hook.go

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

+ 55 - 47
cmd/serve.go → cmd/serv.go

@@ -14,7 +14,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
-	git "github.com/gogits/git-module"
+	"github.com/gogits/git-module"
 	gouuid "github.com/satori/go.uuid"
 	"github.com/urfave/cli"
 	log "gopkg.in/clog.v1"
@@ -26,10 +26,12 @@ import (
 )
 
 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",
 	Usage:       "This command should only be called by SSH shell",
 	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.NewService()
 	log.New(log.FILE, log.FileConfig{
@@ -54,7 +62,7 @@ func setup(logPath string) {
 
 	models.LoadConfigs()
 
-	if setting.UseSQLite3 || setting.UseTiDB {
+	if setting.UseSQLite3 {
 		workDir, _ := setting.WorkDir()
 		os.Chdir(workDir)
 	}
@@ -62,7 +70,7 @@ func setup(logPath string) {
 	models.SetEngine()
 }
 
-func parseCmd(cmd string) (string, string) {
+func parseSSHCmd(cmd string) (string, string) {
 	ss := strings.SplitN(cmd, " ", 2)
 	if len(ss) != 2 {
 		return "", ""
@@ -157,11 +165,7 @@ func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string,
 }
 
 func runServ(c *cli.Context) error {
-	if c.IsSet("config") {
-		setting.CustomConf = c.String("config")
-	}
-
-	setup("serv.log")
+	setup(c, "serv.log")
 
 	if setting.SSH.Disabled {
 		println("Gogs: SSH has been disabled")
@@ -172,21 +176,21 @@ func runServ(c *cli.Context) error {
 		fail("Not enough arguments", "Not enough arguments")
 	}
 
-	cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
-	if len(cmd) == 0 {
+	sshCmd := os.Getenv("SSH_ORIGINAL_COMMAND")
+	if len(sshCmd) == 0 {
 		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.")
 		return nil
 	}
 
-	verb, args := parseCmd(cmd)
-	repoPath := strings.ToLower(strings.Trim(args, "'"))
-	rr := strings.SplitN(repoPath, "/", 2)
-	if len(rr) != 2 {
+	verb, args := parseSSHCmd(sshCmd)
+	repoFullName := strings.ToLower(strings.Trim(args, "'"))
+	repoFields := strings.SplitN(repoFullName, "/", 2)
+	if len(repoFields) != 2 {
 		fail("Invalid repository path", "Invalid repository path: %v", args)
 	}
-	username := strings.ToLower(rr[0])
-	reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
+	username := strings.ToLower(repoFields[0])
+	reponame := strings.ToLower(strings.TrimSuffix(repoFields[1], ".git"))
 
 	isWiki := false
 	if strings.HasSuffix(reponame, ".wiki") {
@@ -194,29 +198,30 @@ func runServ(c *cli.Context) error {
 		reponame = reponame[:len(reponame)-5]
 	}
 
-	repoUser, err := models.GetUserByName(username)
+	repoOwner, err := models.GetUserByName(username)
 	if err != nil {
 		if models.IsErrUserNotExist(err) {
 			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 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]
-	if !has {
-		fail("Unknown git command", "Unknown git command %s", verb)
+	requestMode, ok := allowedCommands[verb]
+	if !ok {
+		fail("Unknown git command", "Unknown git command '%s'", verb)
 	}
 
 	// 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", "")
 	}
 
@@ -225,33 +230,35 @@ func runServ(c *cli.Context) error {
 
 	key, err := models.GetPublicKeyByID(com.StrTo(strings.TrimPrefix(c.Args()[0], "key-")).MustInt64())
 	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.
 		if key.IsDeployKey() {
-			if key.Mode < requestedMode {
+			if key.Mode < requestMode {
 				fail("Key permission denied", "Cannot push with deployment key: %d", key.ID)
 			}
 			checkDeployKey(key, repo)
 		} else {
 			user, err = models.GetUserByKeyID(key.ID)
 			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)
 			if err != nil {
 				fail("Internal error", "Fail to check access: %v", err)
-			} else if mode < requestedMode {
+			}
+
+			if mode < requestMode {
 				clientMessage := _ACCESS_DENIED_MESSAGE
 				if mode >= models.ACCESS_MODE_READ {
 					clientMessage = "You do not have sufficient authorization for this action"
 				}
 				fail(clientMessage,
-					"User %s does not have level %v access to repository %s",
-					user.Name, requestedMode, repoPath)
+					"User '%s' does not have level '%v' access to repository '%s'",
+					user.Name, requestMode, repoFullName)
 			}
 		}
 	} else {
@@ -265,30 +272,31 @@ func runServ(c *cli.Context) error {
 	}
 
 	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.
 	if setting.IsWindows {
 		verb = strings.Replace(verb, "-", " ", 1)
 	}
 
-	var gitcmd *exec.Cmd
+	var gitCmd *exec.Cmd
 	verbs := strings.Split(verb, " ")
 	if len(verbs) == 2 {
-		gitcmd = exec.Command(verbs[0], verbs[1], repoPath)
+		gitCmd = exec.Command(verbs[0], verbs[1], repoFullName)
 	} else {
-		gitcmd = exec.Command(verb, repoPath)
+		gitCmd = exec.Command(verb, repoFullName)
 	}
-	gitcmd.Dir = setting.RepoRootPath
-	gitcmd.Stdout = os.Stdout
-	gitcmd.Stdin = os.Stdin
-	gitcmd.Stderr = os.Stderr
-	if err = gitcmd.Run(); err != nil {
-		fail("Internal error", "Failed to execute git command: %v", err)
+	gitCmd.Dir = setting.RepoRootPath
+	gitCmd.Stdout = os.Stdout
+	gitCmd.Stdin = os.Stdin
+	gitCmd.Stderr = os.Stderr
+	if err = gitCmd.Run(); err != nil {
+		fail("Internal error", "Fail to execute git command: %v", err)
 	}
 
-	if requestedMode == models.ACCESS_MODE_WRITE {
-		handleUpdateTask(uuid, user, repoUser, reponame, isWiki)
+	if requestMode == models.ACCESS_MODE_WRITE {
+		handleUpdateTask(uuid, user, repoOwner, reponame, isWiki)
 	}
 
 	// Update user key activity.

+ 0 - 58
cmd/update.go

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

+ 2 - 1
cmd/web.go

@@ -84,6 +84,7 @@ func checkVersion() {
 	}
 
 	// Check dependency version.
+	// LEGACY [0.11]: no need to check version as we check in vendor into version control
 	checkers := []VerChecker{
 		{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.6.0"},
 		{"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"},
 		{"gopkg.in/ini.v1", ini.Version, "1.8.4"},
 		{"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"},
 	}
 	for _, c := range checkers {

+ 2 - 2
conf/locale/locale_en-US.ini

@@ -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.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_update_hooks = Rewrite all update hook of repositories (needed when custom config path is changed)
-dashboard.resync_all_update_hooks_success = All repositories' update hook have been rewritten successfully.
+dashboard.resync_all_hooks = Resync pre-receive, update and post-receive hooks of all repositories.
+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_success = All repository records that lost Git files have been reinitialized successfully.
 

+ 3 - 3
gogs.go

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

+ 1 - 1
models/action.go

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

+ 2 - 0
models/migrations/migrations.go

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

+ 82 - 0
models/migrations/v15.go

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

+ 0 - 2
models/models.go

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

+ 32 - 22
models/repo.go

@@ -36,10 +36,6 @@ import (
 	"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 (
@@ -125,6 +121,7 @@ func NewRepoContext() {
 	if version.Compare("1.7.1", setting.Git.Version, ">") {
 		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.
 	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
 }
 
-func createUpdateHook(repoPath string) error {
-	return git.SetUpdateHook(repoPath,
-		fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf))
+var hooksTpls = map[string]string{
+	"pre-receive":  "#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n",
+	"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.
 func CleanUpMigrateInfo(repo *Repository) (*Repository, error) {
 	repoPath := repo.RepoPath()
-	if err := createUpdateHook(repoPath); err != nil {
-		return repo, fmt.Errorf("createUpdateHook: %v", err)
+	if err := createDelegateHooks(repoPath); err != nil {
+		return repo, fmt.Errorf("createDelegateHooks: %v", err)
 	}
 	if repo.HasWiki() {
-		if err := createUpdateHook(repo.WikiPath()); err != nil {
-			return repo, fmt.Errorf("createUpdateHook (wiki): %v", err)
+		if err := createDelegateHooks(repo.WikiPath()); err != nil {
+			return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err)
 		}
 	}
 
@@ -737,7 +747,7 @@ func CleanUpMigrateInfo(repo *Repository) (*Repository, error) {
 	}
 	if repo.HasWiki() {
 		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.
 	if err = git.InitRepository(repoPath, true); err != nil {
 		return fmt.Errorf("InitRepository: %v", err)
-	} else if err = createUpdateHook(repoPath); err != nil {
-		return fmt.Errorf("createUpdateHook: %v", err)
+	} else if err = createDelegateHooks(repoPath); err != nil {
+		return fmt.Errorf("createDelegateHooks: %v", err)
 	}
 
 	tmpDir := filepath.Join(os.TempDir(), "gogs-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond()))
@@ -1648,12 +1658,12 @@ func ReinitMissingRepositories() error {
 	return nil
 }
 
-// RewriteRepositoryUpdateHook rewrites all repositories' update hook.
-func RewriteRepositoryUpdateHook() error {
+// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks
+// to make sure the binary and custom conf path are up-to-date.
+func SyncRepositoryHooks() error {
 	return x.Where("id > 0").Iterate(new(Repository),
 		func(idx int, bean interface{}) error {
-			repo := bean.(*Repository)
-			return createUpdateHook(repo.RepoPath())
+			return createDelegateHooks(bean.(*Repository).RepoPath())
 		})
 }
 
@@ -2098,21 +2108,21 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
 
 	repoPath := RepoPath(u.Name, repo.Name)
 	_, 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)
 	if err != nil {
 		return nil, fmt.Errorf("git clone: %v", stderr)
 	}
 
 	_, 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")
 	if err != nil {
 		return nil, fmt.Errorf("git update-server-info: %v", err)
 	}
 
-	if err = createUpdateHook(repoPath); err != nil {
-		return nil, fmt.Errorf("createUpdateHook: %v", err)
+	if err = createDelegateHooks(repoPath); err != nil {
+		return nil, fmt.Errorf("createDelegateHooks: %v", err)
 	}
 
 	return repo, sess.Commit()

+ 2 - 2
models/wiki.go

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

File diff suppressed because it is too large
+ 0 - 0
modules/bindata/bindata.go


+ 0 - 1
modules/setting/setting.go

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

+ 4 - 4
routers/admin/admin.go

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

+ 1 - 1
routers/install.go

@@ -65,7 +65,7 @@ func GlobalInit() {
 		highlight.NewContext()
 		markdown.BuildSanitizer()
 		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
 

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.9.146.0214
+0.9.147.0214

+ 1 - 1
templates/admin/dashboard.tmpl

@@ -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>
 							</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>
 							</tr>
 							<tr>

+ 1 - 1
vendor/github.com/gogits/git-module/git.go

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

+ 15 - 32
vendor/github.com/gogits/git-module/hook.go

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

+ 3 - 3
vendor/vendor.json

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

Some files were not shown because too many files changed in this diff