123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- // 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"
- "github.com/gogs/gogs/models"
- "github.com/gogs/gogs/models/errors"
- "github.com/gogs/gogs/pkg/httplib"
- "github.com/gogs/gogs/pkg/mailer"
- "github.com/gogs/gogs/pkg/setting"
- "github.com/gogs/gogs/pkg/template"
- http "github.com/gogs/gogs/routes/repo"
- )
- 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(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')
- 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(http.ENV_REPO_ID)).MustInt64()
- protectBranch, err := models.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(http.ENV_AUTH_USER_ID)).MustInt64()
- if protectBranch.EnableWhitelist {
- if !models.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(models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.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(http.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 = models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.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(http.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 = models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.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(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')
- // 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=" +
- template.EscapePound(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
- }
- var hookCmd *exec.Cmd
- if setting.IsWindows {
- hookCmd = exec.Command("bash.exe", "custom_hooks/post-receive")
- } else {
- hookCmd = exec.Command(customHooksPath)
- }
- hookCmd.Dir = models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.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
- }
|