aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorUnknwon <u@gogs.io>2017-02-16 16:33:49 -0500
committerUnknwon <u@gogs.io>2017-02-16 16:33:49 -0500
commitd521e716dd59617dbbb637a3e8028bf4a5c6f849 (patch)
tree24ba49f7b169a16c3e1f12ed880bda58a6ac6c58 /cmd
parent3b49a99b6088f2b1e1ba7539b4a69930dfb6de16 (diff)
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.
Diffstat (limited to 'cmd')
-rw-r--r--cmd/hook.go89
-rw-r--r--cmd/serv.go161
-rw-r--r--cmd/web.go2
3 files changed, 125 insertions, 127 deletions
diff --git a/cmd/hook.go b/cmd/hook.go
index 1a494373..cb8b5112 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -7,14 +7,22 @@ package cmd
import (
"bufio"
"bytes"
+ "crypto/tls"
"os"
"os/exec"
"path/filepath"
+ "strings"
"github.com/Unknwon/com"
"github.com/urfave/cli"
+ log "gopkg.in/clog.v1"
+
+ "github.com/gogits/git-module"
"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 (
@@ -56,7 +64,7 @@ func runHookPreReceive(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
- setup(c, "hooks/pre-receive.log")
+ setup(c, "hooks/pre-receive.log", false)
buf := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(os.Stdin)
@@ -65,12 +73,12 @@ func runHookPreReceive(c *cli.Context) error {
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) {
return nil
}
- hookCmd := exec.Command(filepath.Join(customHooksPath, "pre-receive"))
+ hookCmd := exec.Command(customHooksPath)
hookCmd.Stdout = os.Stdout
hookCmd.Stdin = buf
hookCmd.Stderr = os.Stderr
@@ -84,7 +92,7 @@ func runHookUpdate(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
- setup(c, "hooks/update.log")
+ setup(c, "hooks/update.log", false)
args := c.Args()
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")
}
- 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) {
return nil
}
- hookCmd := exec.Command(filepath.Join(customHooksPath, "update"), args...)
+ hookCmd := exec.Command(customHooksPath, args...)
hookCmd.Stdout = os.Stdout
hookCmd.Stdin = os.Stdin
hookCmd.Stderr = os.Stderr
@@ -122,16 +120,67 @@ func runHookPostReceive(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
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) {
return nil
}
- hookCmd := exec.Command(filepath.Join(customHooksPath, "post-receive"))
+ hookCmd := exec.Command(customHooksPath)
hookCmd.Stdout = os.Stdout
- hookCmd.Stdin = os.Stdin
+ hookCmd.Stdin = buf
hookCmd.Stderr = os.Stderr
if err := hookCmd.Run(); err != nil {
fail("Internal error", "Fail to execute custom post-receive hook: %v", err)
diff --git a/cmd/serv.go b/cmd/serv.go
index b15f4e41..57a674e5 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -5,7 +5,6 @@
package cmd
import (
- "crypto/tls"
"fmt"
"os"
"os/exec"
@@ -14,21 +13,16 @@ import (
"time"
"github.com/Unknwon/com"
- "github.com/gogits/git-module"
- gouuid "github.com/satori/go.uuid"
"github.com/urfave/cli"
log "gopkg.in/clog.v1"
"github.com/gogits/gogs/models"
- "github.com/gogits/gogs/modules/base"
- "github.com/gogits/gogs/modules/httplib"
"github.com/gogits/gogs/modules/setting"
+ http "github.com/gogits/gogs/routers/repo"
)
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{
@@ -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") {
setting.CustomConf = c.String("config")
} else if c.GlobalIsSet("config") {
@@ -49,8 +56,13 @@ func setup(c *cli.Context, logPath string) {
}
setting.NewContext()
- setting.NewService()
+
+ level := log.TRACE
+ if setting.ProdMode {
+ level = log.ERROR
+ }
log.New(log.FILE, log.FileConfig{
+ Level: level,
Filename: filepath.Join(setting.LogRootPath, logPath),
FileRotationConfig: log.FileRotationConfig{
Rotate: true,
@@ -60,6 +72,10 @@ func setup(c *cli.Context, logPath string) {
})
log.Delete(log.CONSOLE) // Remove primary logger
+ if !connectDB {
+ return
+ }
+
models.LoadConfigs()
if setting.UseSQLite3 {
@@ -67,7 +83,9 @@ func setup(c *cli.Context, logPath string) {
os.Chdir(workDir)
}
- models.SetEngine()
+ if err := models.SetEngine(); err != nil {
+ fail("Internal error", "SetEngine: %v", err)
+ }
}
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 {
- setup(c, "serv.log")
+ setup(c, "serv.log", true)
if setting.SSH.Disabled {
println("Gogs: SSH has been disabled")
@@ -189,31 +147,26 @@ func runServ(c *cli.Context) error {
if len(repoFields) != 2 {
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 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 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)
}
- repo.Owner = repoOwner
+ repo.Owner = owner
requestMode, ok := allowedCommands[verb]
if !ok {
@@ -262,6 +215,7 @@ func runServ(c *cli.Context) error {
}
}
} 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).
// 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
@@ -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.
if setting.IsWindows {
@@ -287,6 +250,9 @@ func runServ(c *cli.Context) error {
} else {
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.Stdout = os.Stdout
gitCmd.Stdin = os.Stdin
@@ -295,22 +261,5 @@ func runServ(c *cli.Context) error {
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
}
diff --git a/cmd/web.go b/cmd/web.go
index aa4d84b0..ea7d155f 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -623,8 +623,8 @@ func runWeb(ctx *cli.Context) error {
}, ignSignIn, context.RepoAssignment(true), context.RepoRef())
m.Group("/:reponame", func() {
- m.Any("/*", ignSignInAndCsrf, repo.HTTP)
m.Head("/tasks/trigger", repo.TriggerTask)
+ m.Route("\\.git/*", "GET,POST", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
})
})
// ***** END: Repository *****