Browse Source

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

Unknwon 8 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)
   - [ ] Yes (provide example URL)
   - [ ] No
   - [ ] No
   - [ ] Not relevant
   - [ ] Not relevant
-- Log gist:
+- Log gist (usually found in `log/gogs.log`):
 
 
 ## Description
 ## 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"
 	"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") {
-		setting.CustomConf = c.String("config")
-	}
-
-	setup("serv.log")
+	setup(c, "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")
-	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("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)
-	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)
 		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
 	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]
-	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.
 	// 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.Name, requestedMode, repoPath)
+					"User '%s' does not have level '%v' access to repository '%s'",
+					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.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.
 	// 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.
 	// 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 {

+ 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.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_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 = 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.
 
 

+ 3 - 3
gogs.go

@@ -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.CmdUpdate,
+		cmd.Serv,
+		cmd.CmdHook,
 		cmd.CmdDump,
 		cmd.CmdDump,
 		cmd.CmdCert,
 		cmd.CmdCert,
 		cmd.CmdAdmin,
 		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 {
 		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
 	// 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 - 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
 		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()

+ 32 - 22
models/repo.go

@@ -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 {
-	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.
 // 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 {
-		return repo, fmt.Errorf("createUpdateHook: %v", err)
+	if err := createDelegateHooks(repoPath); err != nil {
+		return repo, fmt.Errorf("createDelegateHooks: %v", err)
 	}
 	}
 	if repo.HasWiki() {
 	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 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 {
-		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()))
 	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.
-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),
 	return x.Where("id > 0").Iterate(new(Repository),
 		func(idx int, bean interface{}) error {
 		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)
 	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 {
-		return nil, fmt.Errorf("createUpdateHook: %v", err)
+	if err = createDelegateHooks(repoPath); err != nil {
+		return nil, fmt.Errorf("createDelegateHooks: %v", err)
 	}
 	}
 
 
 	return repo, sess.Commit()
 	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 {
 	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 {
-		return fmt.Errorf("createUpdateHook: %v", err)
+	} else if err = createDelegateHooks(repo.WikiPath()); err != nil {
+		return fmt.Errorf("createDelegateHooks: %v", err)
 	}
 	}
 	return nil
 	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
 	UseMySQL      bool
 	UsePostgreSQL bool
 	UsePostgreSQL bool
 	UseMSSQL      bool
 	UseMSSQL      bool
-	UseTiDB       bool
 
 
 	// Webhook settings
 	// Webhook settings
 	Webhook struct {
 	Webhook struct {

+ 4 - 4
routers/admin/admin.go

@@ -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:
-			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:
 		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()

+ 1 - 1
routers/install.go

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

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

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

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

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

@@ -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 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 (
 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))
-	for i, name := range hookNames {
+	hooks := make([]*Hook, len(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)
-}

+ 3 - 3
vendor/vendor.json

@@ -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",
-			"revisionTime": "2017-01-31T23:38:55Z"
+			"revision": "4d18cee9bde82bffe8c91747f1585afbf06311b2",
+			"revisionTime": "2017-02-14T20:50:54Z"
 		},
 		},
 		{
 		{
 			"checksumSHA1": "SdCLcPmklkXjPVMGkG1pYNmuO2Q=",
 			"checksumSHA1": "SdCLcPmklkXjPVMGkG1pYNmuO2Q=",

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