* move all push update to git hook post-receive and protected branch check to git hook pre-receive * add SSH_ORIGINAL_COMMAND check back * remove all unused codes * fix the importmaster
@@ -5,11 +5,22 @@ | |||||
package cmd | package cmd | ||||
import ( | import ( | ||||
"bufio" | |||||
"bytes" | |||||
"crypto/tls" | |||||
"fmt" | "fmt" | ||||
"os" | "os" | ||||
"strconv" | |||||
"strings" | |||||
"code.gitea.io/git" | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/base" | |||||
"code.gitea.io/gitea/modules/httplib" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/setting" | |||||
"github.com/Unknwon/com" | |||||
"github.com/urfave/cli" | "github.com/urfave/cli" | ||||
) | ) | ||||
@@ -57,10 +68,59 @@ 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 | ||||
} | } | ||||
if err := setup("hooks/pre-receive.log"); err != nil { | if err := setup("hooks/pre-receive.log"); err != nil { | ||||
fail("Hook pre-receive init failed", fmt.Sprintf("setup: %v", err)) | fail("Hook pre-receive init failed", fmt.Sprintf("setup: %v", err)) | ||||
} | } | ||||
// the environment setted on serv command | |||||
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64) | |||||
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true") | |||||
buf := bytes.NewBuffer(nil) | |||||
scanner := bufio.NewScanner(os.Stdin) | |||||
for scanner.Scan() { | |||||
buf.Write(scanner.Bytes()) | |||||
buf.WriteByte('\n') | |||||
// TODO: support news feeds for wiki | |||||
if isWiki { | |||||
continue | |||||
} | |||||
fields := bytes.Fields(scanner.Bytes()) | |||||
if len(fields) != 3 { | |||||
continue | |||||
} | |||||
oldCommitID := string(fields[0]) | |||||
newCommitID := string(fields[1]) | |||||
refFullName := string(fields[2]) | |||||
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) | |||||
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName) | |||||
if err != nil { | |||||
log.GitLogger.Fatal(2, "retrieve protected branches information failed") | |||||
} | |||||
if protectBranch != nil { | |||||
fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "") | |||||
} | |||||
// check and deletion | |||||
if newCommitID == git.EmptySHA { | |||||
fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "") | |||||
} | |||||
// Check force push | |||||
output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID).Run() | |||||
if err != nil { | |||||
fail("Internal error", "Fail to detect force push: %v", err) | |||||
} else if len(output) > 0 { | |||||
fail(fmt.Sprintf("Branch '%s' is protected from force push", branchName), "") | |||||
} | |||||
} | |||||
return nil | return nil | ||||
} | } | ||||
@@ -73,23 +133,6 @@ func runHookUpdate(c *cli.Context) error { | |||||
fail("Hook update init failed", fmt.Sprintf("setup: %v", err)) | fail("Hook update init failed", fmt.Sprintf("setup: %v", err)) | ||||
} | } | ||||
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(envUpdateTaskUUID) | |||||
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) | |||||
} | |||||
return nil | return nil | ||||
} | } | ||||
@@ -102,5 +145,63 @@ func runHookPostReceive(c *cli.Context) error { | |||||
fail("Hook post-receive init failed", fmt.Sprintf("setup: %v", err)) | fail("Hook post-receive init failed", fmt.Sprintf("setup: %v", err)) | ||||
} | } | ||||
// the environment setted on serv command | |||||
repoUser := os.Getenv(models.EnvRepoUsername) | |||||
repoUserSalt := os.Getenv(models.EnvRepoUserSalt) | |||||
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true") | |||||
repoName := os.Getenv(models.EnvRepoName) | |||||
pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) | |||||
pusherName := os.Getenv(models.EnvPusherName) | |||||
buf := bytes.NewBuffer(nil) | |||||
scanner := bufio.NewScanner(os.Stdin) | |||||
for scanner.Scan() { | |||||
buf.Write(scanner.Bytes()) | |||||
buf.WriteByte('\n') | |||||
// TODO: support news feeds for wiki | |||||
if isWiki { | |||||
continue | |||||
} | |||||
fields := bytes.Fields(scanner.Bytes()) | |||||
if len(fields) != 3 { | |||||
continue | |||||
} | |||||
oldCommitID := string(fields[0]) | |||||
newCommitID := string(fields[1]) | |||||
refFullName := string(fields[2]) | |||||
if err := models.PushUpdate(models.PushUpdateOptions{ | |||||
RefFullName: refFullName, | |||||
OldCommitID: oldCommitID, | |||||
NewCommitID: newCommitID, | |||||
PusherID: pusherID, | |||||
PusherName: pusherName, | |||||
RepoUserName: repoUser, | |||||
RepoName: repoName, | |||||
}); err != nil { | |||||
log.GitLogger.Error(2, "Update: %v", err) | |||||
} | |||||
// Ask for running deliver hook and test pull request tasks. | |||||
reqURL := setting.LocalURL + repoUser + "/" + repoName + "/tasks/trigger?branch=" + | |||||
strings.TrimPrefix(refFullName, git.BranchPrefix) + "&secret=" + base.EncodeMD5(repoUserSalt) + "&pusher=" + com.ToStr(pusherID) | |||||
log.GitLogger.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.GitLogger.Error(2, "Failed to trigger task: not 2xx response code") | |||||
} | |||||
} else { | |||||
log.GitLogger.Error(2, "Failed to trigger task: %v", err) | |||||
} | |||||
} | |||||
return nil | return nil | ||||
} | } |
@@ -6,7 +6,6 @@ | |||||
package cmd | package cmd | ||||
import ( | import ( | ||||
"crypto/tls" | |||||
"encoding/json" | "encoding/json" | ||||
"fmt" | "fmt" | ||||
"os" | "os" | ||||
@@ -15,22 +14,17 @@ import ( | |||||
"strings" | "strings" | ||||
"time" | "time" | ||||
"code.gitea.io/git" | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/base" | |||||
"code.gitea.io/gitea/modules/httplib" | |||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
"github.com/dgrijalva/jwt-go" | "github.com/dgrijalva/jwt-go" | ||||
gouuid "github.com/satori/go.uuid" | |||||
"github.com/urfave/cli" | "github.com/urfave/cli" | ||||
) | ) | ||||
const ( | const ( | ||||
accessDenied = "Repository does not exist or you do not have access" | accessDenied = "Repository does not exist or you do not have access" | ||||
lfsAuthenticateVerb = "git-lfs-authenticate" | lfsAuthenticateVerb = "git-lfs-authenticate" | ||||
envUpdateTaskUUID = "GITEA_UUID" | |||||
) | ) | ||||
// CmdServ represents the available serv sub-command. | // CmdServ represents the available serv sub-command. | ||||
@@ -96,52 +90,6 @@ func fail(userMessage, logMessage string, args ...interface{}) { | |||||
os.Exit(1) | 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.GitLogger.Trace("No update task is presented: %s", uuid) | |||||
return | |||||
} | |||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err) | |||||
} else if err = models.DeleteUpdateTaskByUUID(uuid); err != nil { | |||||
log.GitLogger.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.GitLogger.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.BranchPrefix) + "&secret=" + base.EncodeMD5(repoUser.Salt) + "&pusher=" + com.ToStr(user.ID) | |||||
log.GitLogger.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.GitLogger.Error(2, "Failed to trigger task: not 2xx response code") | |||||
} | |||||
} else { | |||||
log.GitLogger.Error(2, "Failed to trigger task: %v", err) | |||||
} | |||||
} | |||||
func runServ(c *cli.Context) error { | func runServ(c *cli.Context) error { | ||||
if c.IsSet("config") { | if c.IsSet("config") { | ||||
setting.CustomConf = c.String("config") | setting.CustomConf = c.String("config") | ||||
@@ -187,6 +135,7 @@ func runServ(c *cli.Context) error { | |||||
if len(rr) != 2 { | if len(rr) != 2 { | ||||
fail("Invalid repository path", "Invalid repository path: %v", args) | fail("Invalid repository path", "Invalid repository path: %v", args) | ||||
} | } | ||||
username := strings.ToLower(rr[0]) | username := strings.ToLower(rr[0]) | ||||
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) | reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) | ||||
@@ -196,6 +145,14 @@ func runServ(c *cli.Context) error { | |||||
reponame = reponame[:len(reponame)-5] | reponame = reponame[:len(reponame)-5] | ||||
} | } | ||||
os.Setenv(models.EnvRepoUsername, username) | |||||
if isWiki { | |||||
os.Setenv(models.EnvRepoIsWiki, "true") | |||||
} else { | |||||
os.Setenv(models.EnvRepoIsWiki, "false") | |||||
} | |||||
os.Setenv(models.EnvRepoName, reponame) | |||||
repoUser, err := models.GetUserByName(username) | repoUser, err := models.GetUserByName(username) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrUserNotExist(err) { | if models.IsErrUserNotExist(err) { | ||||
@@ -204,6 +161,8 @@ func runServ(c *cli.Context) error { | |||||
fail("Internal error", "Failed to get repository owner (%s): %v", username, err) | fail("Internal error", "Failed to get repository owner (%s): %v", username, err) | ||||
} | } | ||||
os.Setenv(models.EnvRepoUserSalt, repoUser.Salt) | |||||
repo, err := models.GetRepositoryByName(repoUser.ID, reponame) | repo, err := models.GetRepositoryByName(repoUser.ID, reponame) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrRepoNotExist(err) { | if models.IsErrRepoNotExist(err) { | ||||
@@ -286,7 +245,8 @@ func runServ(c *cli.Context) error { | |||||
user.Name, requestedMode, repoPath) | user.Name, requestedMode, repoPath) | ||||
} | } | ||||
os.Setenv("GITEA_PUSHER_NAME", user.Name) | |||||
os.Setenv(models.EnvPusherName, user.Name) | |||||
os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID)) | |||||
} | } | ||||
} | } | ||||
@@ -323,11 +283,6 @@ func runServ(c *cli.Context) error { | |||||
return nil | return nil | ||||
} | } | ||||
uuid := gouuid.NewV4().String() | |||||
os.Setenv(envUpdateTaskUUID, uuid) | |||||
// Keep the old env variable name for backward compability | |||||
os.Setenv("uuid", uuid) | |||||
// Special handle for Windows. | // Special handle for Windows. | ||||
if setting.IsWindows { | if setting.IsWindows { | ||||
verb = strings.Replace(verb, "-", " ", 1) | verb = strings.Replace(verb, "-", " ", 1) | ||||
@@ -341,7 +296,6 @@ func runServ(c *cli.Context) error { | |||||
gitcmd = exec.Command(verb, repoPath) | gitcmd = exec.Command(verb, repoPath) | ||||
} | } | ||||
os.Setenv(models.ProtectedBranchAccessMode, requestedMode.String()) | |||||
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID)) | os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID)) | ||||
gitcmd.Dir = setting.RepoRootPath | gitcmd.Dir = setting.RepoRootPath | ||||
@@ -352,10 +306,6 @@ func runServ(c *cli.Context) error { | |||||
fail("Internal error", "Failed to execute git command: %v", err) | fail("Internal error", "Failed to execute git command: %v", err) | ||||
} | } | ||||
if requestedMode == models.AccessModeWrite { | |||||
handleUpdateTask(uuid, user, repoUser, reponame, isWiki) | |||||
} | |||||
// Update user key activity. | // Update user key activity. | ||||
if keyID > 0 { | if keyID > 0 { | ||||
key, err := models.GetPublicKeyByID(keyID) | key, err := models.GetPublicKeyByID(keyID) | ||||
@@ -1,83 +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" | |||||
"strconv" | |||||
"strings" | |||||
"github.com/urfave/cli" | |||||
"code.gitea.io/git" | |||||
"code.gitea.io/gitea/models" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/setting" | |||||
) | |||||
// CmdUpdate represents the available update sub-command. | |||||
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{ | |||||
cli.StringFlag{ | |||||
Name: "config, c", | |||||
Value: "custom/conf/app.ini", | |||||
Usage: "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.GitLogger.Trace("SSH_ORIGINAL_COMMAND is empty") | |||||
return nil | |||||
} | |||||
args := c.Args() | |||||
if len(args) != 3 { | |||||
log.GitLogger.Fatal(2, "Arguments received are not equal to three") | |||||
} else if len(args[0]) == 0 { | |||||
log.GitLogger.Fatal(2, "First argument 'refName' is empty, shouldn't use") | |||||
} | |||||
// protected branch check | |||||
branchName := strings.TrimPrefix(args[0], git.BranchPrefix) | |||||
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64) | |||||
log.GitLogger.Trace("pushing to %d %v", repoID, branchName) | |||||
accessMode := models.ParseAccessMode(os.Getenv(models.ProtectedBranchAccessMode)) | |||||
// skip admin or owner AccessMode | |||||
if accessMode == models.AccessModeWrite { | |||||
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName) | |||||
if err != nil { | |||||
log.GitLogger.Fatal(2, "retrieve protected branches information failed") | |||||
} | |||||
if protectBranch != nil { | |||||
log.GitLogger.Fatal(2, "protected branches can not be pushed to") | |||||
} | |||||
} | |||||
task := models.UpdateTask{ | |||||
UUID: os.Getenv("GITEA_UUID"), | |||||
RefName: args[0], | |||||
OldCommitID: args[1], | |||||
NewCommitID: args[2], | |||||
} | |||||
if err := models.AddUpdateTask(&task); err != nil { | |||||
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err) | |||||
} | |||||
return nil | |||||
} |
@@ -40,5 +40,4 @@ func main() { | |||||
if err != nil { | if err != nil { | ||||
log.Fatal(4, "Failed to run app with %s: %v", os.Args, err) | log.Fatal(4, "Failed to run app with %s: %v", os.Args, err) | ||||
} | } | ||||
} | } |
@@ -1,20 +0,0 @@ | |||||
- | |||||
id: 1 | |||||
uuid: uuid1 | |||||
ref_name: refName1 | |||||
old_commit_id: oldCommitId1 | |||||
new_commit_id: newCommitId1 | |||||
- | |||||
id: 2 | |||||
uuid: uuid2 | |||||
ref_name: refName2 | |||||
old_commit_id: oldCommitId2 | |||||
new_commit_id: newCommitId2 | |||||
- | |||||
id: 3 | |||||
uuid: uuid3 | |||||
ref_name: refName3 | |||||
old_commit_id: oldCommitId3 | |||||
new_commit_id: newCommitId3 |
@@ -100,7 +100,6 @@ func init() { | |||||
new(Release), | new(Release), | ||||
new(LoginSource), | new(LoginSource), | ||||
new(Webhook), | new(Webhook), | ||||
new(UpdateTask), | |||||
new(HookTask), | new(HookTask), | ||||
new(Team), | new(Team), | ||||
new(OrgUser), | new(OrgUser), | ||||
@@ -316,7 +315,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 | ||||
} | } | ||||
@@ -15,40 +15,15 @@ import ( | |||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
) | ) | ||||
// UpdateTask defines an UpdateTask | |||||
type UpdateTask struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
UUID string `xorm:"index"` | |||||
RefName string | |||||
OldCommitID string | |||||
NewCommitID string | |||||
} | |||||
// AddUpdateTask adds an UpdateTask | |||||
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 | |||||
} | |||||
// DeleteUpdateTaskByUUID deletes an UpdateTask from the database | |||||
func DeleteUpdateTaskByUUID(uuid string) error { | |||||
_, err := x.Delete(&UpdateTask{UUID: uuid}) | |||||
return err | |||||
} | |||||
// env keys for git hooks need | |||||
const ( | |||||
EnvRepoName = "GITEA_REPO_NAME" | |||||
EnvRepoUsername = "GITEA_REPO_USER_NAME" | |||||
EnvRepoUserSalt = "GITEA_REPO_USER_SALT" | |||||
EnvRepoIsWiki = "GITEA_REPO_IS_WIKI" | |||||
EnvPusherName = "GITEA_PUSHER_NAME" | |||||
EnvPusherID = "GITEA_PUSHER_ID" | |||||
) | |||||
// 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 { | ||||
@@ -14,40 +14,6 @@ import ( | |||||
"github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
) | ) | ||||
func TestAddUpdateTask(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | |||||
task := &UpdateTask{ | |||||
UUID: "uuid4", | |||||
RefName: "refName4", | |||||
OldCommitID: "oldCommitId4", | |||||
NewCommitID: "newCommitId4", | |||||
} | |||||
assert.NoError(t, AddUpdateTask(task)) | |||||
AssertExistsAndLoadBean(t, task) | |||||
} | |||||
func TestGetUpdateTaskByUUID(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | |||||
task, err := GetUpdateTaskByUUID("uuid1") | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, "uuid1", task.UUID) | |||||
assert.Equal(t, "refName1", task.RefName) | |||||
assert.Equal(t, "oldCommitId1", task.OldCommitID) | |||||
assert.Equal(t, "newCommitId1", task.NewCommitID) | |||||
_, err = GetUpdateTaskByUUID("invalid") | |||||
assert.Error(t, err) | |||||
assert.True(t, IsErrUpdateTaskNotExist(err)) | |||||
} | |||||
func TestDeleteUpdateTaskByUUID(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | |||||
assert.NoError(t, DeleteUpdateTaskByUUID("uuid1")) | |||||
AssertNotExistsBean(t, &UpdateTask{UUID: "uuid1"}) | |||||
assert.NoError(t, DeleteUpdateTaskByUUID("invalid")) | |||||
} | |||||
func TestCommitToPushCommit(t *testing.T) { | func TestCommitToPushCommit(t *testing.T) { | ||||
now := time.Now() | now := time.Now() | ||||
sig := &git.Signature{ | sig := &git.Signature{ | ||||
@@ -8,20 +8,15 @@ 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" | ||||
"code.gitea.io/git" | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
"code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
@@ -59,7 +54,7 @@ func HTTP(ctx *context.Context) { | |||||
isWiki := false | isWiki := false | ||||
if strings.HasSuffix(reponame, ".wiki") { | if strings.HasSuffix(reponame, ".wiki") { | ||||
isWiki = true | isWiki = true | ||||
reponame = reponame[:len(reponame) - 5] | |||||
reponame = reponame[:len(reponame)-5] | |||||
} | } | ||||
repoUser, err := models.GetUserByName(username) | repoUser, err := models.GetUserByName(username) | ||||
@@ -89,6 +84,7 @@ func HTTP(ctx *context.Context) { | |||||
authUser *models.User | authUser *models.User | ||||
authUsername string | authUsername string | ||||
authPasswd string | authPasswd string | ||||
environ []string | |||||
) | ) | ||||
// check access | // check access | ||||
@@ -182,94 +178,42 @@ func HTTP(ctx *context.Context) { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | |||||
callback := func(rpc string, input []byte) { | |||||
if rpc != "receive-pack" || isWiki { | |||||
return | |||||
environ = []string{ | |||||
models.EnvRepoUsername + "=" + username, | |||||
models.EnvRepoName + "=" + reponame, | |||||
models.EnvRepoUserSalt + "=" + repoUser.Salt, | |||||
models.EnvPusherName + "=" + authUser.Name, | |||||
models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID), | |||||
models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID), | |||||
} | } | ||||
var lastLine int64 | |||||
for { | |||||
head := input[lastLine: lastLine + 2] | |||||
if head[0] == '0' && head[1] == '0' { | |||||
size, err := strconv.ParseInt(string(input[lastLine + 2:lastLine + 4]), 16, 32) | |||||
if err != nil { | |||||
log.Error(4, "%v", err) | |||||
return | |||||
} | |||||
if size == 0 { | |||||
//fmt.Println(string(input[lastLine:])) | |||||
break | |||||
} | |||||
line := input[lastLine: lastLine + size] | |||||
idx := bytes.IndexRune(line, '\000') | |||||
if idx > -1 { | |||||
line = line[:idx] | |||||
} | |||||
fields := strings.Fields(string(line)) | |||||
if len(fields) >= 3 { | |||||
oldCommitID := fields[0][4:] | |||||
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.BranchPrefix), true) | |||||
} | |||||
} | |||||
lastLine = lastLine + size | |||||
} else { | |||||
break | |||||
} | |||||
} | |||||
} | |||||
params := make(map[string]string) | |||||
if askAuth { | |||||
params[models.ProtectedBranchUserID] = fmt.Sprintf("%d", authUser.ID) | |||||
if err == nil { | |||||
params[models.ProtectedBranchAccessMode] = accessMode.String() | |||||
if isWiki { | |||||
environ = append(environ, models.EnvRepoIsWiki+"=true") | |||||
} else { | |||||
environ = append(environ, models.EnvRepoIsWiki+"=false") | |||||
} | } | ||||
params[models.ProtectedBranchRepoID] = fmt.Sprintf("%d", repo.ID) | |||||
} | } | ||||
HTTPBackend(ctx, &serviceConfig{ | HTTPBackend(ctx, &serviceConfig{ | ||||
UploadPack: true, | UploadPack: true, | ||||
ReceivePack: true, | ReceivePack: true, | ||||
Params: params, | |||||
OnSucceed: callback, | |||||
Env: environ, | |||||
})(ctx.Resp, ctx.Req.Request) | })(ctx.Resp, ctx.Req.Request) | ||||
runtime.GC() | |||||
} | } | ||||
type serviceConfig struct { | type serviceConfig struct { | ||||
UploadPack bool | UploadPack bool | ||||
ReceivePack bool | ReceivePack bool | ||||
Params map[string]string | |||||
OnSucceed func(rpc string, input []byte) | |||||
Env []string | |||||
} | } | ||||
type serviceHandler struct { | type serviceHandler struct { | ||||
cfg *serviceConfig | |||||
w http.ResponseWriter | |||||
r *http.Request | |||||
dir string | |||||
file string | |||||
cfg *serviceConfig | |||||
w http.ResponseWriter | |||||
r *http.Request | |||||
dir string | |||||
file string | |||||
environ []string | |||||
} | } | ||||
func (h *serviceHandler) setHeaderNoCache() { | func (h *serviceHandler) setHeaderNoCache() { | ||||
@@ -278,42 +222,6 @@ func (h *serviceHandler) setHeaderNoCache() { | |||||
h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") | h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") | ||||
} | } | ||||
func (h *serviceHandler) getBranch(input []byte) string { | |||||
var lastLine int64 | |||||
var branchName string | |||||
for { | |||||
head := input[lastLine : lastLine+2] | |||||
if head[0] == '0' && head[1] == '0' { | |||||
size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32) | |||||
if err != nil { | |||||
log.Error(4, "%v", err) | |||||
return branchName | |||||
} | |||||
if size == 0 { | |||||
//fmt.Println(string(input[lastLine:])) | |||||
break | |||||
} | |||||
line := input[lastLine : lastLine+size] | |||||
idx := bytes.IndexRune(line, '\000') | |||||
if idx > -1 { | |||||
line = line[:idx] | |||||
} | |||||
fields := strings.Fields(string(line)) | |||||
if len(fields) >= 3 { | |||||
refFullName := fields[2] | |||||
branchName = strings.TrimPrefix(refFullName, git.BranchPrefix) | |||||
} | |||||
lastLine = lastLine + size | |||||
} else { | |||||
break | |||||
} | |||||
} | |||||
return branchName | |||||
} | |||||
func (h *serviceHandler) setHeaderCacheForever() { | func (h *serviceHandler) setHeaderCacheForever() { | ||||
now := time.Now().Unix() | now := time.Now().Unix() | ||||
expires := now + 31536000 | expires := now + 31536000 | ||||
@@ -370,7 +278,7 @@ func gitCommand(dir string, args ...string) []byte { | |||||
func getGitConfig(option, dir string) string { | func getGitConfig(option, dir string) string { | ||||
out := string(gitCommand(dir, "config", option)) | out := string(gitCommand(dir, "config", option)) | ||||
return out[0: len(out) - 1] | |||||
return out[0 : len(out)-1] | |||||
} | } | ||||
func getConfigSetting(service, dir string) bool { | func getConfigSetting(service, dir string) bool { | ||||
@@ -414,13 +322,8 @@ func serviceRPC(h serviceHandler, service string) { | |||||
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 ( | |||||
reqBody = h.r.Body | |||||
input []byte | |||||
br io.Reader | |||||
err error | |||||
branchName string | |||||
) | |||||
var err error | |||||
var reqBody = h.r.Body | |||||
// Handle GZIP. | // Handle GZIP. | ||||
if h.r.Header.Get("Content-Encoding") == "gzip" { | if h.r.Header.Get("Content-Encoding") == "gzip" { | ||||
@@ -432,52 +335,23 @@ func serviceRPC(h serviceHandler, service string) { | |||||
} | } | ||||
} | } | ||||
if h.cfg.OnSucceed != nil { | |||||
input, err = ioutil.ReadAll(reqBody) | |||||
if err != nil { | |||||
log.GitLogger.Error(2, "fail to read request body: %v", err) | |||||
h.w.WriteHeader(http.StatusInternalServerError) | |||||
return | |||||
} | |||||
branchName = h.getBranch(input) | |||||
br = bytes.NewReader(input) | |||||
} else { | |||||
br = reqBody | |||||
} | |||||
// check protected branch | |||||
repoID, _ := strconv.ParseInt(h.cfg.Params[models.ProtectedBranchRepoID], 10, 64) | |||||
accessMode := models.ParseAccessMode(h.cfg.Params[models.ProtectedBranchAccessMode]) | |||||
// skip admin or owner AccessMode | |||||
if accessMode == models.AccessModeWrite { | |||||
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName) | |||||
if err != nil { | |||||
log.GitLogger.Error(2, "fail to get protected branch information: %v", err) | |||||
h.w.WriteHeader(http.StatusInternalServerError) | |||||
return | |||||
} | |||||
if protectBranch != nil { | |||||
log.GitLogger.Error(2, "protected branches can not be pushed to") | |||||
h.w.WriteHeader(http.StatusForbidden) | |||||
return | |||||
} | |||||
} | |||||
// set this for allow pre-receive and post-receive execute | |||||
h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service) | |||||
var stderr bytes.Buffer | |||||
cmd := exec.Command("git", service, "--stateless-rpc", h.dir) | cmd := exec.Command("git", service, "--stateless-rpc", h.dir) | ||||
cmd.Dir = h.dir | cmd.Dir = h.dir | ||||
if service == "receive-pack" { | |||||
cmd.Env = append(os.Environ(), h.environ...) | |||||
} | |||||
cmd.Stdout = h.w | cmd.Stdout = h.w | ||||
cmd.Stdin = br | |||||
cmd.Stdin = reqBody | |||||
cmd.Stderr = &stderr | |||||
if err := cmd.Run(); err != nil { | if err := cmd.Run(); err != nil { | ||||
log.GitLogger.Error(2, "fail to serve RPC(%s): %v", service, err) | |||||
log.GitLogger.Error(2, "fail to serve RPC(%s): %v - %v", service, err, stderr) | |||||
h.w.WriteHeader(http.StatusInternalServerError) | h.w.WriteHeader(http.StatusInternalServerError) | ||||
return | return | ||||
} | } | ||||
if h.cfg.OnSucceed != nil { | |||||
h.cfg.OnSucceed(service, input) | |||||
} | |||||
} | } | ||||
func serviceUploadPack(h serviceHandler) { | func serviceUploadPack(h serviceHandler) { | ||||
@@ -501,7 +375,7 @@ func updateServerInfo(dir string) []byte { | |||||
} | } | ||||
func packetWrite(str string) []byte { | func packetWrite(str string) []byte { | ||||
s := strconv.FormatInt(int64(len(str) + 4), 16) | |||||
s := strconv.FormatInt(int64(len(str)+4), 16) | |||||
if len(s)%4 != 0 { | if len(s)%4 != 0 { | ||||
s = strings.Repeat("0", 4-len(s)%4) + s | s = strings.Repeat("0", 4-len(s)%4) + s | ||||
} | } | ||||
@@ -593,7 +467,7 @@ func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc { | |||||
return | return | ||||
} | } | ||||
route.handler(serviceHandler{cfg, w, r, dir, file}) | |||||
route.handler(serviceHandler{cfg, w, r, dir, file, cfg.Env}) | |||||
return | return | ||||
} | } | ||||
} | } | ||||