Browse Source

refactoring: SSH and HTTP push procees is now unified

We used to handle SSH and HTTP push separately which produces
duplicated code, but now with post-receive hook, the process
is unified to one single place and much cleaner.
Thus, UpdateTask struct is removed.

Narrow down the range of Git HTTP routes to reduce condufsing
HTTP Basic Authentication window popup on browser.

By detecting <old-commit, new-commit, ref-name> inside post-receive
hook, Git HTTP doesn't need to read the whole content body anymore,
which completely solve the RAM problem reported in #636.
Unknwon 8 years ago
parent
commit
d521e716dd
12 changed files with 324 additions and 524 deletions
  1. 69 20
      cmd/hook.go
  2. 55 106
      cmd/serv.go
  3. 1 1
      cmd/web.go
  4. 1 1
      gogs.go
  5. 1 1
      models/access.go
  6. 1 1
      models/action.go
  7. 1 2
      models/models.go
  8. 3 35
      models/update.go
  9. 1 1
      modules/context/context.go
  10. 189 354
      routers/repo/http.go
  11. 1 1
      routers/repo/pull.go
  12. 1 1
      templates/.VERSION

+ 69 - 20
cmd/hook.go

@@ -7,14 +7,22 @@ package cmd
 import (
 import (
 	"bufio"
 	"bufio"
 	"bytes"
 	"bytes"
+	"crypto/tls"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"path/filepath"
 	"path/filepath"
+	"strings"
 
 
 	"github.com/Unknwon/com"
 	"github.com/Unknwon/com"
 	"github.com/urfave/cli"
 	"github.com/urfave/cli"
+	log "gopkg.in/clog.v1"
+
+	"github.com/gogits/git-module"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/httplib"
+	"github.com/gogits/gogs/modules/setting"
+	http "github.com/gogits/gogs/routers/repo"
 )
 )
 
 
 var (
 var (
@@ -56,7 +64,7 @@ func runHookPreReceive(c *cli.Context) error {
 	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 		return nil
 		return nil
 	}
 	}
-	setup(c, "hooks/pre-receive.log")
+	setup(c, "hooks/pre-receive.log", false)
 
 
 	buf := bytes.NewBuffer(nil)
 	buf := bytes.NewBuffer(nil)
 	scanner := bufio.NewScanner(os.Stdin)
 	scanner := bufio.NewScanner(os.Stdin)
@@ -65,12 +73,12 @@ func runHookPreReceive(c *cli.Context) error {
 		buf.WriteByte('\n')
 		buf.WriteByte('\n')
 	}
 	}
 
 
-	customHooksPath := os.Getenv(_ENV_REPO_CUSTOM_HOOKS_PATH)
+	customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "pre-receive")
 	if !com.IsFile(customHooksPath) {
 	if !com.IsFile(customHooksPath) {
 		return nil
 		return nil
 	}
 	}
 
 
-	hookCmd := exec.Command(filepath.Join(customHooksPath, "pre-receive"))
+	hookCmd := exec.Command(customHooksPath)
 	hookCmd.Stdout = os.Stdout
 	hookCmd.Stdout = os.Stdout
 	hookCmd.Stdin = buf
 	hookCmd.Stdin = buf
 	hookCmd.Stderr = os.Stderr
 	hookCmd.Stderr = os.Stderr
@@ -84,7 +92,7 @@ func runHookUpdate(c *cli.Context) error {
 	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 		return nil
 		return nil
 	}
 	}
-	setup(c, "hooks/update.log")
+	setup(c, "hooks/update.log", false)
 
 
 	args := c.Args()
 	args := c.Args()
 	if len(args) != 3 {
 	if len(args) != 3 {
@@ -93,22 +101,12 @@ func runHookUpdate(c *cli.Context) error {
 		fail("First argument 'refName' is empty", "First argument 'refName' is empty")
 		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)
+	customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "update")
 	if !com.IsFile(customHooksPath) {
 	if !com.IsFile(customHooksPath) {
 		return nil
 		return nil
 	}
 	}
 
 
-	hookCmd := exec.Command(filepath.Join(customHooksPath, "update"), args...)
+	hookCmd := exec.Command(customHooksPath, args...)
 	hookCmd.Stdout = os.Stdout
 	hookCmd.Stdout = os.Stdout
 	hookCmd.Stdin = os.Stdin
 	hookCmd.Stdin = os.Stdin
 	hookCmd.Stderr = os.Stderr
 	hookCmd.Stderr = os.Stderr
@@ -122,16 +120,67 @@ func runHookPostReceive(c *cli.Context) error {
 	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 		return nil
 		return nil
 	}
 	}
-	setup(c, "hooks/post-receive.log")
+	setup(c, "hooks/post-receive.log", true)
+
+	isWiki := strings.Contains(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/")
+
+	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)
+		// TODO: support news feeds for wiki
+		if isWiki {
+			continue
+		}
+
+		fields := bytes.Fields(scanner.Bytes())
+		if len(fields) != 3 {
+			continue
+		}
+
+		options := models.PushUpdateOptions{
+			OldCommitID:  string(fields[0]),
+			NewCommitID:  string(fields[1]),
+			RefFullName:  string(fields[2]),
+			PusherID:     com.StrTo(os.Getenv(http.ENV_AUTH_USER_ID)).MustInt64(),
+			PusherName:   os.Getenv(http.ENV_AUTH_USER_NAME),
+			RepoUserName: os.Getenv(http.ENV_REPO_OWNER_NAME),
+			RepoName:     os.Getenv(http.ENV_REPO_NAME),
+		}
+		if err := models.PushUpdate(options); err != nil {
+			log.Error(2, "PushUpdate: %v", err)
+		}
+
+		// Ask for running deliver hook and test pull request tasks.
+		reqURL := setting.LocalURL + options.RepoUserName + "/" + options.RepoName + "/tasks/trigger?branch=" +
+			strings.TrimPrefix(options.RefFullName, git.BRANCH_PREFIX) +
+			"&secret=" + os.Getenv(http.ENV_REPO_OWNER_SALT_MD5) +
+			"&pusher=" + os.Getenv(http.ENV_AUTH_USER_ID)
+		log.Trace("Trigger task: %s", reqURL)
+
+		resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
+			InsecureSkipVerify: true,
+		}).Response()
+		if err == nil {
+			resp.Body.Close()
+			if resp.StatusCode/100 != 2 {
+				log.Error(2, "Fail to trigger task: not 2xx response code")
+			}
+		} else {
+			log.Error(2, "Fail to trigger task: %v", err)
+		}
+	}
+
+	customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "post-receive")
 	if !com.IsFile(customHooksPath) {
 	if !com.IsFile(customHooksPath) {
 		return nil
 		return nil
 	}
 	}
 
 
-	hookCmd := exec.Command(filepath.Join(customHooksPath, "post-receive"))
+	hookCmd := exec.Command(customHooksPath)
 	hookCmd.Stdout = os.Stdout
 	hookCmd.Stdout = os.Stdout
-	hookCmd.Stdin = os.Stdin
+	hookCmd.Stdin = buf
 	hookCmd.Stderr = os.Stderr
 	hookCmd.Stderr = os.Stderr
 	if err := hookCmd.Run(); err != nil {
 	if err := hookCmd.Run(); err != nil {
 		fail("Internal error", "Fail to execute custom post-receive hook: %v", err)
 		fail("Internal error", "Fail to execute custom post-receive hook: %v", err)

+ 55 - 106
cmd/serv.go

@@ -5,7 +5,6 @@
 package cmd
 package cmd
 
 
 import (
 import (
-	"crypto/tls"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
@@ -14,21 +13,16 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/Unknwon/com"
 	"github.com/Unknwon/com"
-	"github.com/gogits/git-module"
-	gouuid "github.com/satori/go.uuid"
 	"github.com/urfave/cli"
 	"github.com/urfave/cli"
 	log "gopkg.in/clog.v1"
 	log "gopkg.in/clog.v1"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
-	"github.com/gogits/gogs/modules/base"
-	"github.com/gogits/gogs/modules/httplib"
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/modules/setting"
+	http "github.com/gogits/gogs/routers/repo"
 )
 )
 
 
 const (
 const (
-	_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"
+	_ACCESS_DENIED_MESSAGE = "Repository does not exist or you do not have access"
 )
 )
 
 
 var Serv = cli.Command{
 var Serv = cli.Command{
@@ -41,7 +35,20 @@ var Serv = cli.Command{
 	},
 	},
 }
 }
 
 
-func setup(c *cli.Context, logPath string) {
+func fail(userMessage, logMessage string, args ...interface{}) {
+	fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
+
+	if len(logMessage) > 0 {
+		if !setting.ProdMode {
+			fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
+		}
+		log.Fatal(3, logMessage, args...)
+	}
+
+	os.Exit(1)
+}
+
+func setup(c *cli.Context, logPath string, connectDB bool) {
 	if c.IsSet("config") {
 	if c.IsSet("config") {
 		setting.CustomConf = c.String("config")
 		setting.CustomConf = c.String("config")
 	} else if c.GlobalIsSet("config") {
 	} else if c.GlobalIsSet("config") {
@@ -49,8 +56,13 @@ func setup(c *cli.Context, logPath string) {
 	}
 	}
 
 
 	setting.NewContext()
 	setting.NewContext()
-	setting.NewService()
+
+	level := log.TRACE
+	if setting.ProdMode {
+		level = log.ERROR
+	}
 	log.New(log.FILE, log.FileConfig{
 	log.New(log.FILE, log.FileConfig{
+		Level:    level,
 		Filename: filepath.Join(setting.LogRootPath, logPath),
 		Filename: filepath.Join(setting.LogRootPath, logPath),
 		FileRotationConfig: log.FileRotationConfig{
 		FileRotationConfig: log.FileRotationConfig{
 			Rotate:  true,
 			Rotate:  true,
@@ -60,6 +72,10 @@ func setup(c *cli.Context, logPath string) {
 	})
 	})
 	log.Delete(log.CONSOLE) // Remove primary logger
 	log.Delete(log.CONSOLE) // Remove primary logger
 
 
+	if !connectDB {
+		return
+	}
+
 	models.LoadConfigs()
 	models.LoadConfigs()
 
 
 	if setting.UseSQLite3 {
 	if setting.UseSQLite3 {
@@ -67,7 +83,9 @@ func setup(c *cli.Context, logPath string) {
 		os.Chdir(workDir)
 		os.Chdir(workDir)
 	}
 	}
 
 
-	models.SetEngine()
+	if err := models.SetEngine(); err != nil {
+		fail("Internal error", "SetEngine: %v", err)
+	}
 }
 }
 
 
 func parseSSHCmd(cmd string) (string, string) {
 func parseSSHCmd(cmd string) (string, string) {
@@ -104,68 +122,8 @@ var (
 	}
 	}
 )
 )
 
 
-func fail(userMessage, logMessage string, args ...interface{}) {
-	fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
-
-	if len(logMessage) > 0 {
-		if !setting.ProdMode {
-			fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
-		}
-		log.Fatal(3, logMessage, args...)
-	}
-
-	log.Shutdown()
-	os.Exit(1)
-}
-
-func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string, isWiki bool) {
-	task, err := models.GetUpdateTaskByUUID(uuid)
-	if err != nil {
-		if models.IsErrUpdateTaskNotExist(err) {
-			log.Trace("No update task is presented: %s", uuid)
-			return
-		}
-		log.Fatal(2, "GetUpdateTaskByUUID: %v", err)
-	} else if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
-		log.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
-	}
-
-	if isWiki {
-		return
-	}
-
-	if err = models.PushUpdate(models.PushUpdateOptions{
-		RefFullName:  task.RefName,
-		OldCommitID:  task.OldCommitID,
-		NewCommitID:  task.NewCommitID,
-		PusherID:     user.ID,
-		PusherName:   user.Name,
-		RepoUserName: repoUser.Name,
-		RepoName:     reponame,
-	}); err != nil {
-		log.Error(2, "Update: %v", err)
-	}
-
-	// Ask for running deliver hook and test pull request tasks.
-	reqURL := setting.LocalURL + repoUser.Name + "/" + reponame + "/tasks/trigger?branch=" +
-		strings.TrimPrefix(task.RefName, git.BRANCH_PREFIX) + "&secret=" + base.EncodeMD5(repoUser.Salt) + "&pusher=" + com.ToStr(user.ID)
-	log.Trace("Trigger task: %s", reqURL)
-
-	resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
-		InsecureSkipVerify: true,
-	}).Response()
-	if err == nil {
-		resp.Body.Close()
-		if resp.StatusCode/100 != 2 {
-			log.Error(2, "Fail to trigger task: not 2xx response code")
-		}
-	} else {
-		log.Error(2, "Fail to trigger task: %v", err)
-	}
-}
-
 func runServ(c *cli.Context) error {
 func runServ(c *cli.Context) error {
-	setup(c, "serv.log")
+	setup(c, "serv.log", true)
 
 
 	if setting.SSH.Disabled {
 	if setting.SSH.Disabled {
 		println("Gogs: SSH has been disabled")
 		println("Gogs: SSH has been disabled")
@@ -189,31 +147,26 @@ func runServ(c *cli.Context) error {
 	if len(repoFields) != 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(repoFields[0])
-	reponame := strings.ToLower(strings.TrimSuffix(repoFields[1], ".git"))
+	ownerName := strings.ToLower(repoFields[0])
+	repoName := strings.TrimSuffix(strings.ToLower(repoFields[1]), ".git")
+	repoName = strings.TrimSuffix(repoName, ".wiki")
 
 
-	isWiki := false
-	if strings.HasSuffix(reponame, ".wiki") {
-		isWiki = true
-		reponame = reponame[:len(reponame)-5]
-	}
-
-	repoOwner, err := models.GetUserByName(username)
+	owner, err := models.GetUserByName(ownerName)
 	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", ownerName)
 		}
 		}
-		fail("Internal error", "Fail to get repository owner '%s': %v", username, err)
+		fail("Internal error", "Fail to get repository owner '%s': %v", ownerName, err)
 	}
 	}
 
 
-	repo, err := models.GetRepositoryByName(repoOwner.ID, reponame)
+	repo, err := models.GetRepositoryByName(owner.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", repoOwner.Name, reponame)
+			fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", owner.Name, repoName)
 		}
 		}
 		fail("Internal error", "Fail to get repository: %v", err)
 		fail("Internal error", "Fail to get repository: %v", err)
 	}
 	}
-	repo.Owner = repoOwner
+	repo.Owner = owner
 
 
 	requestMode, ok := allowedCommands[verb]
 	requestMode, ok := allowedCommands[verb]
 	if !ok {
 	if !ok {
@@ -262,6 +215,7 @@ func runServ(c *cli.Context) error {
 			}
 			}
 		}
 		}
 	} else {
 	} else {
+		setting.NewService()
 		// Check if the key can access to the repository in case of it is a deploy key (a deploy keys != user key).
 		// Check if the key can access to the repository in case of it is a deploy key (a deploy keys != user key).
 		// A deploy key doesn't represent a signed in user, so in a site with Service.RequireSignInView activated
 		// A deploy key doesn't represent a signed in user, so in a site with Service.RequireSignInView activated
 		// we should give read access only in repositories where this deploy key is in use. In other case, a server
 		// we should give read access only in repositories where this deploy key is in use. In other case, a server
@@ -271,9 +225,18 @@ func runServ(c *cli.Context) error {
 		}
 		}
 	}
 	}
 
 
-	uuid := gouuid.NewV4().String()
-	os.Setenv(_ENV_UPDATE_TASK_UUID, uuid)
-	os.Setenv(_ENV_REPO_CUSTOM_HOOKS_PATH, filepath.Join(repo.RepoPath(), "custom_hooks"))
+	// Update user key activity.
+	if key.ID > 0 {
+		key, err := models.GetPublicKeyByID(key.ID)
+		if err != nil {
+			fail("Internal error", "GetPublicKeyByID: %v", err)
+		}
+
+		key.Updated = time.Now()
+		if err = models.UpdatePublicKey(key); err != nil {
+			fail("Internal error", "UpdatePublicKey: %v", err)
+		}
+	}
 
 
 	// Special handle for Windows.
 	// Special handle for Windows.
 	if setting.IsWindows {
 	if setting.IsWindows {
@@ -287,6 +250,9 @@ func runServ(c *cli.Context) error {
 	} else {
 	} else {
 		gitCmd = exec.Command(verb, repoFullName)
 		gitCmd = exec.Command(verb, repoFullName)
 	}
 	}
+	if requestMode == models.ACCESS_MODE_WRITE {
+		gitCmd.Env = append(os.Environ(), http.ComposeHookEnvs(repo.RepoPath(), owner.Name, owner.Salt, repo.Name, user)...)
+	}
 	gitCmd.Dir = setting.RepoRootPath
 	gitCmd.Dir = setting.RepoRootPath
 	gitCmd.Stdout = os.Stdout
 	gitCmd.Stdout = os.Stdout
 	gitCmd.Stdin = os.Stdin
 	gitCmd.Stdin = os.Stdin
@@ -295,22 +261,5 @@ func runServ(c *cli.Context) error {
 		fail("Internal error", "Fail to execute git command: %v", err)
 		fail("Internal error", "Fail to execute git command: %v", err)
 	}
 	}
 
 
-	if requestMode == models.ACCESS_MODE_WRITE {
-		handleUpdateTask(uuid, user, repoOwner, reponame, isWiki)
-	}
-
-	// Update user key activity.
-	if key.ID > 0 {
-		key, err := models.GetPublicKeyByID(key.ID)
-		if err != nil {
-			fail("Internal error", "GetPublicKeyByID: %v", err)
-		}
-
-		key.Updated = time.Now()
-		if err = models.UpdatePublicKey(key); err != nil {
-			fail("Internal error", "UpdatePublicKey: %v", err)
-		}
-	}
-
 	return nil
 	return nil
 }
 }

+ 1 - 1
cmd/web.go

@@ -623,8 +623,8 @@ func runWeb(ctx *cli.Context) error {
 		}, ignSignIn, context.RepoAssignment(true), context.RepoRef())
 		}, ignSignIn, context.RepoAssignment(true), context.RepoRef())
 
 
 		m.Group("/:reponame", func() {
 		m.Group("/:reponame", func() {
-			m.Any("/*", ignSignInAndCsrf, repo.HTTP)
 			m.Head("/tasks/trigger", repo.TriggerTask)
 			m.Head("/tasks/trigger", repo.TriggerTask)
+			m.Route("\\.git/*", "GET,POST", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
 		})
 		})
 	})
 	})
 	// ***** END: Repository *****
 	// ***** END: Repository *****

+ 1 - 1
gogs.go

@@ -16,7 +16,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
-const APP_VER = "0.9.151.0216"
+const APP_VER = "0.9.152.0216"
 
 
 func init() {
 func init() {
 	setting.AppVer = APP_VER
 	setting.AppVer = APP_VER

+ 1 - 1
models/access.go

@@ -86,7 +86,7 @@ func AccessLevel(u *User, repo *Repository) (AccessMode, error) {
 
 
 func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) {
 func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) {
 	mode, err := accessLevel(e, u, repo)
 	mode, err := accessLevel(e, u, repo)
-	return testMode <= mode, err
+	return mode >= testMode, err
 }
 }
 
 
 // HasAccess returns true if someone has the request access level. User can be nil!
 // HasAccess returns true if someone has the request access level. User can be nil!

+ 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(2, "UpdateIssuesCommit: %v", err)
 		}
 		}
 	}
 	}
 
 

+ 1 - 2
models/models.go

@@ -66,7 +66,7 @@ func init() {
 		new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
 		new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
 		new(Label), new(IssueLabel), new(Milestone),
 		new(Label), new(IssueLabel), new(Milestone),
 		new(Mirror), new(Release), new(LoginSource), new(Webhook),
 		new(Mirror), new(Release), new(LoginSource), new(Webhook),
-		new(UpdateTask), new(HookTask),
+		new(HookTask),
 		new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
 		new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
 		new(Notice), new(EmailAddress))
 		new(Notice), new(EmailAddress))
 
 
@@ -254,7 +254,6 @@ func GetStatistic() (stats Statistic) {
 	stats.Counter.Label, _ = x.Count(new(Label))
 	stats.Counter.Label, _ = x.Count(new(Label))
 	stats.Counter.HookTask, _ = x.Count(new(HookTask))
 	stats.Counter.HookTask, _ = x.Count(new(HookTask))
 	stats.Counter.Team, _ = x.Count(new(Team))
 	stats.Counter.Team, _ = x.Count(new(Team))
-	stats.Counter.UpdateTask, _ = x.Count(new(UpdateTask))
 	stats.Counter.Attachment, _ = x.Count(new(Attachment))
 	stats.Counter.Attachment, _ = x.Count(new(Attachment))
 	return
 	return
 }
 }

+ 3 - 35
models/update.go

@@ -15,38 +15,6 @@ import (
 	git "github.com/gogits/git-module"
 	git "github.com/gogits/git-module"
 )
 )
 
 
-type UpdateTask struct {
-	ID          int64  `xorm:"pk autoincr"`
-	UUID        string `xorm:"index"`
-	RefName     string
-	OldCommitID string
-	NewCommitID string
-}
-
-func AddUpdateTask(task *UpdateTask) error {
-	_, err := x.Insert(task)
-	return err
-}
-
-// GetUpdateTaskByUUID returns update task by given UUID.
-func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
-	task := &UpdateTask{
-		UUID: uuid,
-	}
-	has, err := x.Get(task)
-	if err != nil {
-		return nil, err
-	} else if !has {
-		return nil, ErrUpdateTaskNotExist{uuid}
-	}
-	return task, nil
-}
-
-func DeleteUpdateTaskByUUID(uuid string) error {
-	_, err := x.Delete(&UpdateTask{UUID: uuid})
-	return err
-}
-
 // CommitToPushCommit transforms a git.Commit to PushCommit type.
 // CommitToPushCommit transforms a git.Commit to PushCommit type.
 func CommitToPushCommit(commit *git.Commit) *PushCommit {
 func CommitToPushCommit(commit *git.Commit) *PushCommit {
 	return &PushCommit{
 	return &PushCommit{
@@ -74,13 +42,13 @@ func ListToPushCommits(l *list.List) *PushCommits {
 }
 }
 
 
 type PushUpdateOptions struct {
 type PushUpdateOptions struct {
+	OldCommitID  string
+	NewCommitID  string
+	RefFullName  string
 	PusherID     int64
 	PusherID     int64
 	PusherName   string
 	PusherName   string
 	RepoUserName string
 	RepoUserName string
 	RepoName     string
 	RepoName     string
-	RefFullName  string
-	OldCommitID  string
-	NewCommitID  string
 }
 }
 
 
 // PushUpdate must be called for any push actions in order to
 // PushUpdate must be called for any push actions in order to

+ 1 - 1
modules/context/context.go

@@ -112,7 +112,7 @@ func (ctx *Context) NotFound() {
 // or error context description for logging purpose of 500 server error.
 // or error context description for logging purpose of 500 server error.
 func (ctx *Context) NotFoundOrServerError(title string, errck func(error) bool, err error) {
 func (ctx *Context) NotFoundOrServerError(title string, errck func(error) bool, err error) {
 	if errck(err) {
 	if errck(err) {
-		ctx.Handle(404, title, err)
+		ctx.NotFound()
 		return
 		return
 	}
 	}
 
 

+ 189 - 354
routers/repo/http.go

@@ -1,4 +1,4 @@
-// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2017 The Gogs Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
@@ -8,21 +8,18 @@ import (
 	"bytes"
 	"bytes"
 	"compress/gzip"
 	"compress/gzip"
 	"fmt"
 	"fmt"
-	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"path"
 	"path"
 	"regexp"
 	"regexp"
-	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
+	"github.com/Unknwon/com"
 	log "gopkg.in/clog.v1"
 	log "gopkg.in/clog.v1"
-
-	git "github.com/gogits/git-module"
+	"gopkg.in/macaron.v1"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/base"
@@ -30,59 +27,54 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
-func HTTP(ctx *context.Context) {
-	username := ctx.Params(":username")
-	reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
-
-	var isPull bool
-	service := ctx.Query("service")
-	if service == "git-receive-pack" ||
-		strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
-		isPull = false
-	} else if service == "git-upload-pack" ||
-		strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
-		isPull = true
-	} else {
-		isPull = (ctx.Req.Method == "GET")
-	}
+const (
+	ENV_AUTH_USER_ID           = "AUTH_USER_ID"
+	ENV_AUTH_USER_NAME         = "AUTH_USER_NAME"
+	ENV_REPO_OWNER_NAME        = "REPO_OWNER_NAME"
+	ENV_REPO_OWNER_SALT_MD5    = "REPO_OWNER_SALT_MD5"
+	ENV_REPO_NAME              = "REPO_NAME"
+	ENV_REPO_CUSTOM_HOOKS_PATH = "REPO_CUSTOM_HOOKS_PATH"
+)
 
 
-	isWiki := false
-	if strings.HasSuffix(reponame, ".wiki") {
-		isWiki = true
-		reponame = reponame[:len(reponame)-5]
-	}
+type HTTPContext struct {
+	*context.Context
+	OwnerName string
+	OwnerSalt string
+	RepoName  string
+	AuthUser  *models.User
+}
 
 
-	repoUser, err := models.GetUserByName(username)
-	if err != nil {
-		if models.IsErrUserNotExist(err) {
-			ctx.Handle(http.StatusNotFound, "GetUserByName", nil)
-		} else {
-			ctx.Handle(http.StatusInternalServerError, "GetUserByName", err)
+func HTTPContexter() macaron.Handler {
+	return func(ctx *context.Context) {
+		ownerName := ctx.Params(":username")
+		repoName := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
+		repoName = strings.TrimSuffix(repoName, ".wiki")
+
+		isPull := ctx.Query("service") == "git-upload-pack" ||
+			strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") ||
+			ctx.Req.Method == "GET"
+
+		owner, err := models.GetUserByName(ownerName)
+		if err != nil {
+			ctx.NotFoundOrServerError("GetUserByName", models.IsErrUserNotExist, err)
+			return
 		}
 		}
-		return
-	}
 
 
-	repo, err := models.GetRepositoryByName(repoUser.ID, reponame)
-	if err != nil {
-		if models.IsErrRepoNotExist(err) {
-			ctx.Handle(http.StatusNotFound, "GetRepositoryByName", nil)
-		} else {
-			ctx.Handle(http.StatusInternalServerError, "GetRepositoryByName", err)
+		repo, err := models.GetRepositoryByName(owner.ID, repoName)
+		if err != nil {
+			ctx.NotFoundOrServerError("GetRepositoryByName", models.IsErrRepoNotExist, err)
+			return
 		}
 		}
-		return
-	}
 
 
-	// Only public pull don't need auth.
-	isPublicPull := !repo.IsPrivate && isPull
-	var (
-		askAuth      = !isPublicPull || setting.Service.RequireSignInView
-		authUser     *models.User
-		authUsername string
-		authPasswd   string
-	)
+		// Authentication is not required for pulling from public repositories.
+		if isPull && !repo.IsPrivate && !setting.Service.RequireSignInView {
+			ctx.Map(&HTTPContext{
+				Context: ctx,
+			})
+			return
+		}
 
 
-	// check access
-	if askAuth {
+		// Handle HTTP Basic Authentication
 		authHead := ctx.Req.Header.Get("Authorization")
 		authHead := ctx.Req.Header.Get("Authorization")
 		if len(authHead) == 0 {
 		if len(authHead) == 0 {
 			ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
 			ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
@@ -91,174 +83,78 @@ func HTTP(ctx *context.Context) {
 		}
 		}
 
 
 		auths := strings.Fields(authHead)
 		auths := strings.Fields(authHead)
-		// currently check basic auth
-		// TODO: support digit auth
-		// FIXME: middlewares/context.go did basic auth check already,
-		// maybe could use that one.
 		if len(auths) != 2 || auths[0] != "Basic" {
 		if len(auths) != 2 || auths[0] != "Basic" {
-			ctx.HandleText(http.StatusUnauthorized, "no basic auth and digit auth")
+			ctx.Error(http.StatusUnauthorized)
 			return
 			return
 		}
 		}
-		authUsername, authPasswd, err = base.BasicAuthDecode(auths[1])
+		authUsername, authPassword, err := base.BasicAuthDecode(auths[1])
 		if err != nil {
 		if err != nil {
-			ctx.HandleText(http.StatusUnauthorized, "no basic auth and digit auth")
+			ctx.Error(http.StatusUnauthorized)
 			return
 			return
 		}
 		}
 
 
-		authUser, err = models.UserSignIn(authUsername, authPasswd)
-		if err != nil {
-			if !models.IsErrUserNotExist(err) {
-				ctx.Handle(http.StatusInternalServerError, "UserSignIn error: %v", err)
-				return
-			}
+		authUser, err := models.UserSignIn(authUsername, authPassword)
+		if err != nil && !models.IsErrUserNotExist(err) {
+			ctx.Handle(http.StatusInternalServerError, "UserSignIn: %v", err)
+			return
+		}
 
 
-			// Assume username now is a token.
+		// If username and password combination failed, try again using username as a token.
+		if authUser == nil {
 			token, err := models.GetAccessTokenBySHA(authUsername)
 			token, err := models.GetAccessTokenBySHA(authUsername)
 			if err != nil {
 			if err != nil {
-				if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) {
-					ctx.HandleText(http.StatusUnauthorized, "invalid token")
-				} else {
-					ctx.Handle(http.StatusInternalServerError, "GetAccessTokenBySha", err)
-				}
+				ctx.NotFoundOrServerError("GetAccessTokenBySHA", models.IsErrAccessTokenNotExist, err)
 				return
 				return
 			}
 			}
 			token.Updated = time.Now()
 			token.Updated = time.Now()
-			if err = models.UpdateAccessToken(token); err != nil {
-				ctx.Handle(http.StatusInternalServerError, "UpdateAccessToken", err)
-			}
+
 			authUser, err = models.GetUserByID(token.UID)
 			authUser, err = models.GetUserByID(token.UID)
 			if err != nil {
 			if err != nil {
+				// Once we found token, we're supposed to find its related user,
+				// thus any error is unexpected.
 				ctx.Handle(http.StatusInternalServerError, "GetUserByID", err)
 				ctx.Handle(http.StatusInternalServerError, "GetUserByID", err)
 				return
 				return
 			}
 			}
 		}
 		}
 
 
-		if !isPublicPull {
-			var tp = models.ACCESS_MODE_WRITE
-			if isPull {
-				tp = models.ACCESS_MODE_READ
-			}
-
-			has, err := models.HasAccess(authUser, repo, tp)
-			if err != nil {
-				ctx.Handle(http.StatusInternalServerError, "HasAccess", err)
-				return
-			} else if !has {
-				if tp == models.ACCESS_MODE_READ {
-					has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE)
-					if err != nil {
-						ctx.Handle(http.StatusInternalServerError, "HasAccess2", err)
-						return
-					} else if !has {
-						ctx.HandleText(http.StatusForbidden, "User permission denied")
-						return
-					}
-				} else {
-					ctx.HandleText(http.StatusForbidden, "User permission denied")
-					return
-				}
-			}
-
-			if !isPull && repo.IsMirror {
-				ctx.HandleText(http.StatusForbidden, "mirror repository is read-only")
-				return
-			}
+		mode := models.ACCESS_MODE_WRITE
+		if isPull {
+			mode = models.ACCESS_MODE_READ
 		}
 		}
-	}
-
-	callback := func(rpc string, input *os.File) {
-		if rpc != "receive-pack" || isWiki {
+		has, err := models.HasAccess(authUser, repo, mode)
+		if err != nil {
+			ctx.Handle(http.StatusInternalServerError, "HasAccess", err)
+			return
+		} else if !has {
+			ctx.HandleText(http.StatusForbidden, "User permission denied")
 			return
 			return
 		}
 		}
 
 
-		var (
-			head = make([]byte, 4) // 00+size
-			n    int
-			err  error
-		)
-		for {
-			n, err = input.Read(head)
-			if err != nil && err != io.EOF {
-				log.Error(4, "read head: %v", err)
-				return
-			} else if n < 4 {
-				break
-			}
-
-			if head[0] == '0' && head[1] == '0' {
-				size, err := strconv.ParseInt(string(head[2:4]), 16, 32)
-				if err != nil {
-					log.Error(4, "parse size: %v", err)
-					return
-				}
-
-				if size == 0 {
-					//fmt.Println(string(input[lastLine:]))
-					break
-				}
-
-				line := make([]byte, size)
-				n, err = input.Read(line)
-				if err != nil {
-					log.Error(4, "read line: %v", err)
-					return
-				} else if n < int(size) {
-					log.Error(4, "didn't read enough bytes: expect %d got %d", size, n)
-					break
-				}
-
-				idx := bytes.IndexRune(line, '\000')
-				if idx > -1 {
-					line = line[:idx]
-				}
-
-				fields := strings.Fields(string(line))
-				if len(fields) >= 3 {
-					oldCommitId := fields[0]
-					newCommitId := fields[1]
-					refFullName := fields[2]
-
-					// FIXME: handle error.
-					if err = models.PushUpdate(models.PushUpdateOptions{
-						RefFullName:  refFullName,
-						OldCommitID:  oldCommitId,
-						NewCommitID:  newCommitId,
-						PusherID:     authUser.ID,
-						PusherName:   authUser.Name,
-						RepoUserName: username,
-						RepoName:     reponame,
-					}); err == nil {
-						go models.AddTestPullRequestTask(authUser, repo.ID, strings.TrimPrefix(refFullName, git.BRANCH_PREFIX), true)
-					}
-
-				}
-			} else {
-				break
-			}
+		if !isPull && repo.IsMirror {
+			ctx.HandleText(http.StatusForbidden, "Mirror repository is read-only")
+			return
 		}
 		}
-	}
-
-	HTTPBackend(ctx, &serviceConfig{
-		UploadPack:  true,
-		ReceivePack: true,
-		OnSucceed:   callback,
-	})(ctx.Resp, ctx.Req.Request)
-
-	runtime.GC()
-}
 
 
-type serviceConfig struct {
-	UploadPack  bool
-	ReceivePack bool
-	OnSucceed   func(rpc string, input *os.File)
+		ctx.Map(&HTTPContext{
+			Context:   ctx,
+			OwnerName: ownerName,
+			OwnerSalt: owner.Salt,
+			RepoName:  repoName,
+			AuthUser:  authUser,
+		})
+	}
 }
 }
 
 
 type serviceHandler struct {
 type serviceHandler struct {
-	cfg  *serviceConfig
 	w    http.ResponseWriter
 	w    http.ResponseWriter
 	r    *http.Request
 	r    *http.Request
 	dir  string
 	dir  string
 	file string
 	file string
+
+	authUser  *models.User
+	ownerName string
+	ownerSalt string
+	repoName  string
 }
 }
 
 
 func (h *serviceHandler) setHeaderNoCache() {
 func (h *serviceHandler) setHeaderNoCache() {
@@ -277,7 +173,6 @@ func (h *serviceHandler) setHeaderCacheForever() {
 
 
 func (h *serviceHandler) sendFile(contentType string) {
 func (h *serviceHandler) sendFile(contentType string) {
 	reqFile := path.Join(h.dir, h.file)
 	reqFile := path.Join(h.dir, h.file)
-
 	fi, err := os.Stat(reqFile)
 	fi, err := os.Stat(reqFile)
 	if os.IsNotExist(err) {
 	if os.IsNotExist(err) {
 		h.w.WriteHeader(http.StatusNotFound)
 		h.w.WriteHeader(http.StatusNotFound)
@@ -290,152 +185,57 @@ func (h *serviceHandler) sendFile(contentType string) {
 	http.ServeFile(h.w, h.r, reqFile)
 	http.ServeFile(h.w, h.r, reqFile)
 }
 }
 
 
-type route struct {
-	reg     *regexp.Regexp
-	method  string
-	handler func(serviceHandler)
-}
-
-var routes = []route{
-	{regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
-	{regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
-	{regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
-	{regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
-	{regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
-	{regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
-	{regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
-	{regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
-	{regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
-	{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
-	{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
-}
-
-// FIXME: use process module
-func gitCommand(dir string, args ...string) []byte {
-	cmd := exec.Command("git", args...)
-	cmd.Dir = dir
-	out, err := cmd.Output()
-	if err != nil {
-		log.Error(4, fmt.Sprintf("Git: %v - %s", err, out))
-	}
-	return out
-}
-
-func getGitConfig(option, dir string) string {
-	out := string(gitCommand(dir, "config", option))
-	return out[0 : len(out)-1]
-}
-
-func getConfigSetting(service, dir string) bool {
-	service = strings.Replace(service, "-", "", -1)
-	setting := getGitConfig("http."+service, dir)
-
-	if service == "uploadpack" {
-		return setting != "false"
+func ComposeHookEnvs(repoPath, ownerName, ownerSalt, repoName string, authUser *models.User) []string {
+	envs := []string{
+		"SSH_ORIGINAL_COMMAND=1",
+		ENV_AUTH_USER_ID + "=" + com.ToStr(authUser.ID),
+		ENV_AUTH_USER_NAME + "=" + authUser.Name,
+		ENV_REPO_OWNER_NAME + "=" + ownerName,
+		ENV_REPO_OWNER_SALT_MD5 + "=" + base.EncodeMD5(ownerSalt),
+		ENV_REPO_NAME + "=" + repoName,
+		ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(repoPath, "custom_hooks"),
 	}
 	}
-
-	return setting == "true"
-}
-
-func hasAccess(service string, h serviceHandler, checkContentType bool) bool {
-	if checkContentType {
-		if h.r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", service) {
-			return false
-		}
-	}
-
-	if !(service == "upload-pack" || service == "receive-pack") {
-		return false
-	}
-	if service == "receive-pack" {
-		return h.cfg.ReceivePack
-	}
-	if service == "upload-pack" {
-		return h.cfg.UploadPack
-	}
-
-	return getConfigSetting(service, h.dir)
+	return envs
 }
 }
 
 
 func serviceRPC(h serviceHandler, service string) {
 func serviceRPC(h serviceHandler, service string) {
 	defer h.r.Body.Close()
 	defer h.r.Body.Close()
 
 
-	if !hasAccess(service, h, true) {
+	if h.r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", service) {
 		h.w.WriteHeader(http.StatusUnauthorized)
 		h.w.WriteHeader(http.StatusUnauthorized)
 		return
 		return
 	}
 	}
 	h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
 	h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
 
 
 	var (
 	var (
-		reqBody     = h.r.Body
-		tmpFilename string
-		br          io.Reader
-		err         error
+		reqBody = h.r.Body
+		err     error
 	)
 	)
 
 
 	// Handle GZIP.
 	// Handle GZIP.
 	if h.r.Header.Get("Content-Encoding") == "gzip" {
 	if h.r.Header.Get("Content-Encoding") == "gzip" {
 		reqBody, err = gzip.NewReader(reqBody)
 		reqBody, err = gzip.NewReader(reqBody)
 		if err != nil {
 		if err != nil {
-			log.Error(2, "Git: fail to create gzip reader: %v", err)
+			log.Error(2, "HTTP.Get: fail to create gzip reader: %v", err)
 			h.w.WriteHeader(http.StatusInternalServerError)
 			h.w.WriteHeader(http.StatusInternalServerError)
 			return
 			return
 		}
 		}
 	}
 	}
 
 
-	if h.cfg.OnSucceed != nil {
-		tmpfile, err := ioutil.TempFile("", "gogs")
-		if err != nil {
-			log.Error(2, "Git: fail to create temporary file: %v", err)
-			h.w.WriteHeader(http.StatusInternalServerError)
-			return
-		}
-		defer os.Remove(tmpfile.Name())
-
-		_, err = io.Copy(tmpfile, reqBody)
-		if err != nil {
-			log.Error(2, "Git: fail to save request body: %v", err)
-			h.w.WriteHeader(http.StatusInternalServerError)
-			return
-		}
-		tmpfile.Close()
-
-		tmpFilename = tmpfile.Name()
-		tmpfile, err = os.Open(tmpFilename)
-		if err != nil {
-			log.Error(2, "Git: fail to open temporary file: %v", err)
-			h.w.WriteHeader(http.StatusInternalServerError)
-			return
-		}
-		defer tmpfile.Close()
-
-		br = tmpfile
-	} else {
-		br = reqBody
-	}
-
 	var stderr bytes.Buffer
 	var stderr bytes.Buffer
 	cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
 	cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
+	if service == "receive-pack" {
+		cmd.Env = append(os.Environ(), ComposeHookEnvs(h.dir, h.ownerName, h.ownerSalt, h.repoName, h.authUser)...)
+	}
 	cmd.Dir = h.dir
 	cmd.Dir = h.dir
 	cmd.Stdout = h.w
 	cmd.Stdout = h.w
 	cmd.Stderr = &stderr
 	cmd.Stderr = &stderr
-	cmd.Stdin = br
-	if err := cmd.Run(); err != nil {
-		log.Error(2, "Git: fail to serve RPC '%s': %v - %s", service, err, stderr)
+	cmd.Stdin = reqBody
+	if err = cmd.Run(); err != nil {
+		log.Error(2, "HTTP.serviceRPC: fail to serve RPC '%s': %v - %s", service, err, stderr)
 		h.w.WriteHeader(http.StatusInternalServerError)
 		h.w.WriteHeader(http.StatusInternalServerError)
 		return
 		return
 	}
 	}
-
-	if h.cfg.OnSucceed != nil {
-		input, err := os.Open(tmpFilename)
-		if err != nil {
-			log.Error(2, "Git: fail to open temporary file: %v", err)
-			h.w.WriteHeader(http.StatusInternalServerError)
-			return
-		}
-		defer input.Close()
-		h.cfg.OnSucceed(service, input)
-	}
 }
 }
 
 
 func serviceUploadPack(h serviceHandler) {
 func serviceUploadPack(h serviceHandler) {
@@ -451,7 +251,18 @@ func getServiceType(r *http.Request) string {
 	if !strings.HasPrefix(serviceType, "git-") {
 	if !strings.HasPrefix(serviceType, "git-") {
 		return ""
 		return ""
 	}
 	}
-	return strings.Replace(serviceType, "git-", "", 1)
+	return strings.TrimPrefix(serviceType, "git-")
+}
+
+// FIXME: use process module
+func gitCommand(dir string, args ...string) []byte {
+	cmd := exec.Command("git", args...)
+	cmd.Dir = dir
+	out, err := cmd.Output()
+	if err != nil {
+		log.Error(2, fmt.Sprintf("Git: %v - %s", err, out))
+	}
+	return out
 }
 }
 
 
 func updateServerInfo(dir string) []byte {
 func updateServerInfo(dir string) []byte {
@@ -468,19 +279,19 @@ func packetWrite(str string) []byte {
 
 
 func getInfoRefs(h serviceHandler) {
 func getInfoRefs(h serviceHandler) {
 	h.setHeaderNoCache()
 	h.setHeaderNoCache()
-	if hasAccess(getServiceType(h.r), h, false) {
-		service := getServiceType(h.r)
-		refs := gitCommand(h.dir, service, "--stateless-rpc", "--advertise-refs", ".")
-
-		h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
-		h.w.WriteHeader(http.StatusOK)
-		h.w.Write(packetWrite("# service=git-" + service + "\n"))
-		h.w.Write([]byte("0000"))
-		h.w.Write(refs)
-	} else {
+	service := getServiceType(h.r)
+	if service != "upload-pack" && service != "receive-pack" {
 		updateServerInfo(h.dir)
 		updateServerInfo(h.dir)
 		h.sendFile("text/plain; charset=utf-8")
 		h.sendFile("text/plain; charset=utf-8")
+		return
 	}
 	}
+
+	refs := gitCommand(h.dir, service, "--stateless-rpc", "--advertise-refs", ".")
+	h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
+	h.w.WriteHeader(http.StatusOK)
+	h.w.Write(packetWrite("# service=git-" + service + "\n"))
+	h.w.Write([]byte("0000"))
+	h.w.Write(refs)
 }
 }
 
 
 func getTextFile(h serviceHandler) {
 func getTextFile(h serviceHandler) {
@@ -508,55 +319,79 @@ func getIdxFile(h serviceHandler) {
 	h.sendFile("application/x-git-packed-objects-toc")
 	h.sendFile("application/x-git-packed-objects-toc")
 }
 }
 
 
-func getGitRepoPath(subdir string) (string, error) {
-	if !strings.HasSuffix(subdir, ".git") {
-		subdir += ".git"
+var routes = []struct {
+	reg     *regexp.Regexp
+	method  string
+	handler func(serviceHandler)
+}{
+	{regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
+	{regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
+	{regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
+	{regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
+	{regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
+	{regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
+	{regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
+	{regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
+	{regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
+	{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
+	{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
+}
+
+func getGitRepoPath(dir string) (string, error) {
+	if !strings.HasSuffix(dir, ".git") {
+		dir += ".git"
 	}
 	}
 
 
-	fpath := path.Join(setting.RepoRootPath, subdir)
-	if _, err := os.Stat(fpath); os.IsNotExist(err) {
+	filename := path.Join(setting.RepoRootPath, dir)
+	if _, err := os.Stat(filename); os.IsNotExist(err) {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	return fpath, nil
+	return filename, nil
 }
 }
 
 
-func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		for _, route := range routes {
-			r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
-			if m := route.reg.FindStringSubmatch(r.URL.Path); m != nil {
-				if setting.Repository.DisableHTTPGit {
-					w.WriteHeader(http.StatusForbidden)
-					w.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
-					return
-				}
-
-				if route.method != r.Method {
-					if r.Proto == "HTTP/1.1" {
-						w.WriteHeader(http.StatusMethodNotAllowed)
-						w.Write([]byte("Method Not Allowed"))
-					} else {
-						w.WriteHeader(http.StatusBadRequest)
-						w.Write([]byte("Bad Request"))
-					}
-					return
-				}
-
-				file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
-				dir, err := getGitRepoPath(m[1])
-				if err != nil {
-					log.Error(4, "Git: getGitRepoPath: %v", err)
-					ctx.Handle(http.StatusNotFound, "HTTPBackend", err)
-					return
-				}
-
-				route.handler(serviceHandler{cfg, w, r, dir, file})
-				return
-			}
+func HTTP(ctx *HTTPContext) {
+	for _, route := range routes {
+		reqPath := strings.ToLower(ctx.Req.URL.Path)
+		m := route.reg.FindStringSubmatch(reqPath)
+		if m == nil {
+			continue
+		}
+
+		// We perform check here because routes matched in cmd/web.go is wider than needed,
+		// but we only want to output this message only if user is really trying to access
+		// Git HTTP endpoints.
+		if setting.Repository.DisableHTTPGit {
+			ctx.HandleText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is not disabled")
+			return
+		}
+
+		if route.method != ctx.Req.Method {
+			ctx.NotFound()
+			return
 		}
 		}
 
 
-		ctx.Handle(http.StatusNotFound, "HTTPBackend", nil)
+		file := strings.TrimPrefix(reqPath, m[1]+"/")
+		dir, err := getGitRepoPath(m[1])
+		if err != nil {
+			log.Warn("HTTP.getGitRepoPath: %v", err)
+			ctx.NotFound()
+			return
+		}
+
+		route.handler(serviceHandler{
+			w:    ctx.Resp,
+			r:    ctx.Req.Request,
+			dir:  dir,
+			file: file,
+
+			authUser:  ctx.AuthUser,
+			ownerName: ctx.OwnerName,
+			ownerSalt: ctx.OwnerSalt,
+			repoName:  ctx.RepoName,
+		})
 		return
 		return
 	}
 	}
+
+	ctx.NotFound()
 }
 }

+ 1 - 1
routers/repo/pull.go

@@ -740,7 +740,7 @@ func TriggerTask(ctx *context.Context) {
 		return
 		return
 	}
 	}
 
 
-	log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
+	log.Trace("TriggerTask '%s/%s' by '%s'", repo.Name, branch, pusher.Name)
 
 
 	go models.HookQueue.Add(repo.ID)
 	go models.HookQueue.Add(repo.ID)
 	go models.AddTestPullRequestTask(pusher, repo.ID, branch, true)
 	go models.AddTestPullRequestTask(pusher, repo.ID, branch, true)

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.9.151.0216
+0.9.152.0216