* 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 importtags/v1.2.0-rc1
| @@ -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 | ||||
| } | } | ||||
| } | } | ||||