diff options
author | Unknwon <u@gogs.io> | 2019-10-24 01:51:46 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-24 01:51:46 -0700 |
commit | 01c8df01ec0608f1f25b2f1444adabb98fa5ee8a (patch) | |
tree | f8a7e5dd8d2a8c51e1ce2cabb9d33571a93314dd /internal/cmd/hook.go | |
parent | 613139e7bef81d3573e7988a47eb6765f3de347a (diff) |
internal: move packages under this directory (#5836)
* Rename pkg -> internal
* Rename routes -> route
* Move route -> internal/route
* Rename models -> db
* Move db -> internal/db
* Fix route2 -> route
* Move cmd -> internal/cmd
* Bump version
Diffstat (limited to 'internal/cmd/hook.go')
-rw-r--r-- | internal/cmd/hook.go | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/internal/cmd/hook.go b/internal/cmd/hook.go new file mode 100644 index 00000000..a6c79c3b --- /dev/null +++ b/internal/cmd/hook.go @@ -0,0 +1,277 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "bufio" + "bytes" + "crypto/tls" + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/unknwon/com" + "github.com/urfave/cli" + log "gopkg.in/clog.v1" + + "github.com/gogs/git-module" + + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/db/errors" + "gogs.io/gogs/internal/httplib" + "gogs.io/gogs/internal/mailer" + "gogs.io/gogs/internal/setting" + "gogs.io/gogs/internal/template" +) + +var ( + Hook = cli.Command{ + Name: "hook", + Usage: "Delegate commands to corresponding Git hooks", + Description: "All sub-commands should only be called by Git", + Flags: []cli.Flag{ + stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), + }, + Subcommands: []cli.Command{ + subcmdHookPreReceive, + subcmdHookUpadte, + subcmdHookPostReceive, + }, + } + + subcmdHookPreReceive = cli.Command{ + Name: "pre-receive", + Usage: "Delegate pre-receive Git hook", + Description: "This command should only be called by Git", + Action: runHookPreReceive, + } + subcmdHookUpadte = cli.Command{ + Name: "update", + Usage: "Delegate update Git hook", + Description: "This command should only be called by Git", + Action: runHookUpdate, + } + subcmdHookPostReceive = cli.Command{ + Name: "post-receive", + Usage: "Delegate post-receive Git hook", + Description: "This command should only be called by Git", + Action: runHookPostReceive, + } +) + +func runHookPreReceive(c *cli.Context) error { + if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { + return nil + } + setup(c, "hooks/pre-receive.log", true) + + isWiki := strings.Contains(os.Getenv(db.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') + + if isWiki { + continue + } + + fields := bytes.Fields(scanner.Bytes()) + if len(fields) != 3 { + continue + } + oldCommitID := string(fields[0]) + newCommitID := string(fields[1]) + branchName := strings.TrimPrefix(string(fields[2]), git.BRANCH_PREFIX) + + // Branch protection + repoID := com.StrTo(os.Getenv(db.ENV_REPO_ID)).MustInt64() + protectBranch, err := db.GetProtectBranchOfRepoByName(repoID, branchName) + if err != nil { + if errors.IsErrBranchNotExist(err) { + continue + } + fail("Internal error", "GetProtectBranchOfRepoByName [repo_id: %d, branch: %s]: %v", repoID, branchName, err) + } + if !protectBranch.Protected { + continue + } + + // Whitelist users can bypass require pull request check + bypassRequirePullRequest := false + + // Check if user is in whitelist when enabled + userID := com.StrTo(os.Getenv(db.ENV_AUTH_USER_ID)).MustInt64() + if protectBranch.EnableWhitelist { + if !db.IsUserInProtectBranchWhitelist(repoID, userID, branchName) { + fail(fmt.Sprintf("Branch '%s' is protected and you are not in the push whitelist", branchName), "") + } + + bypassRequirePullRequest = true + } + + // Check if branch allows direct push + if !bypassRequirePullRequest && protectBranch.RequirePullRequest { + fail(fmt.Sprintf("Branch '%s' is protected and commits must be merged through pull request", branchName), "") + } + + // check and deletion + if newCommitID == git.EMPTY_SHA { + fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "") + } + + // Check force push + output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID). + RunInDir(db.RepoPath(os.Getenv(db.ENV_REPO_OWNER_NAME), os.Getenv(db.ENV_REPO_NAME))) + 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), "") + } + } + + customHooksPath := filepath.Join(os.Getenv(db.ENV_REPO_CUSTOM_HOOKS_PATH), "pre-receive") + if !com.IsFile(customHooksPath) { + return nil + } + + var hookCmd *exec.Cmd + if setting.IsWindows { + hookCmd = exec.Command("bash.exe", "custom_hooks/pre-receive") + } else { + hookCmd = exec.Command(customHooksPath) + } + hookCmd.Dir = db.RepoPath(os.Getenv(db.ENV_REPO_OWNER_NAME), os.Getenv(db.ENV_REPO_NAME)) + hookCmd.Stdout = os.Stdout + hookCmd.Stdin = buf + hookCmd.Stderr = os.Stderr + if err := hookCmd.Run(); err != nil { + fail("Internal error", "Fail to execute custom pre-receive hook: %v", err) + } + return nil +} + +func runHookUpdate(c *cli.Context) error { + if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { + return nil + } + setup(c, "hooks/update.log", false) + + 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") + } + + customHooksPath := filepath.Join(os.Getenv(db.ENV_REPO_CUSTOM_HOOKS_PATH), "update") + if !com.IsFile(customHooksPath) { + return nil + } + + var hookCmd *exec.Cmd + if setting.IsWindows { + hookCmd = exec.Command("bash.exe", append([]string{"custom_hooks/update"}, args...)...) + } else { + hookCmd = exec.Command(customHooksPath, args...) + } + hookCmd.Dir = db.RepoPath(os.Getenv(db.ENV_REPO_OWNER_NAME), os.Getenv(db.ENV_REPO_NAME)) + hookCmd.Stdout = os.Stdout + hookCmd.Stdin = os.Stdin + hookCmd.Stderr = os.Stderr + if err := hookCmd.Run(); err != nil { + fail("Internal error", "Fail to execute custom pre-receive hook: %v", err) + } + return nil +} + +func runHookPostReceive(c *cli.Context) error { + if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { + return nil + } + setup(c, "hooks/post-receive.log", true) + + // Post-receive hook does more than just gather Git information, + // so we need to setup additional services for email notifications. + setting.NewPostReceiveHookServices() + mailer.NewContext() + mailer.InitMailRender(path.Join(setting.StaticRootPath, "templates/mail"), + path.Join(setting.CustomPath, "templates/mail"), template.NewFuncMap()) + + isWiki := strings.Contains(os.Getenv(db.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') + + // TODO: support news feeds for wiki + if isWiki { + continue + } + + fields := bytes.Fields(scanner.Bytes()) + if len(fields) != 3 { + continue + } + + options := db.PushUpdateOptions{ + OldCommitID: string(fields[0]), + NewCommitID: string(fields[1]), + RefFullName: string(fields[2]), + PusherID: com.StrTo(os.Getenv(db.ENV_AUTH_USER_ID)).MustInt64(), + PusherName: os.Getenv(db.ENV_AUTH_USER_NAME), + RepoUserName: os.Getenv(db.ENV_REPO_OWNER_NAME), + RepoName: os.Getenv(db.ENV_REPO_NAME), + } + if err := db.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=" + + template.EscapePound(strings.TrimPrefix(options.RefFullName, git.BRANCH_PREFIX)) + + "&secret=" + os.Getenv(db.ENV_REPO_OWNER_SALT_MD5) + + "&pusher=" + os.Getenv(db.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(db.ENV_REPO_CUSTOM_HOOKS_PATH), "post-receive") + if !com.IsFile(customHooksPath) { + return nil + } + + var hookCmd *exec.Cmd + if setting.IsWindows { + hookCmd = exec.Command("bash.exe", "custom_hooks/post-receive") + } else { + hookCmd = exec.Command(customHooksPath) + } + hookCmd.Dir = db.RepoPath(os.Getenv(db.ENV_REPO_OWNER_NAME), os.Getenv(db.ENV_REPO_NAME)) + hookCmd.Stdout = os.Stdout + 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) + } + return nil +} |