2017-02-23 06:40:44 +03:00
// Copyright 2017 The Gitea 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 (
2017-02-25 17:54:40 +03:00
"bufio"
"bytes"
2017-02-23 06:40:44 +03:00
"fmt"
"os"
2017-05-04 08:42:02 +03:00
"path/filepath"
2017-02-25 17:54:40 +03:00
"strconv"
"strings"
2017-02-23 06:40:44 +03:00
2017-02-25 17:54:40 +03:00
"code.gitea.io/git"
2017-02-23 06:40:44 +03:00
"code.gitea.io/gitea/models"
2017-02-25 17:54:40 +03:00
"code.gitea.io/gitea/modules/log"
2017-05-04 08:42:02 +03:00
"code.gitea.io/gitea/modules/private"
2017-02-25 17:54:40 +03:00
"code.gitea.io/gitea/modules/setting"
2017-02-23 06:40:44 +03:00
"github.com/urfave/cli"
)
var (
// CmdHook represents the available hooks sub-command.
CmdHook = cli . Command {
Name : "hook" ,
Usage : "Delegate commands to corresponding Git hooks" ,
Description : "This should only be called by Git" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "config, c" ,
Value : "custom/conf/app.ini" ,
Usage : "Custom configuration file path" ,
} ,
} ,
Subcommands : [ ] cli . Command {
subcmdHookPreReceive ,
2018-01-13 01:16:49 +03:00
subcmdHookUpdate ,
2017-02-23 06:40:44 +03:00
subcmdHookPostReceive ,
} ,
}
subcmdHookPreReceive = cli . Command {
Name : "pre-receive" ,
Usage : "Delegate pre-receive Git hook" ,
Description : "This command should only be called by Git" ,
Action : runHookPreReceive ,
}
2018-01-13 01:16:49 +03:00
subcmdHookUpdate = cli . Command {
2017-02-23 06:40:44 +03:00
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 ,
}
)
2017-05-04 08:42:02 +03:00
func hookSetup ( logPath string ) {
setting . NewContext ( )
log . NewGitLogger ( filepath . Join ( setting . LogRootPath , logPath ) )
models . LoadConfigs ( )
}
2017-02-23 06:40:44 +03:00
func runHookPreReceive ( c * cli . Context ) error {
if len ( os . Getenv ( "SSH_ORIGINAL_COMMAND" ) ) == 0 {
return nil
}
2017-02-25 17:54:40 +03:00
2017-03-01 18:01:03 +03:00
if c . IsSet ( "config" ) {
setting . CustomConf = c . String ( "config" )
} else if c . GlobalIsSet ( "config" ) {
setting . CustomConf = c . GlobalString ( "config" )
}
2017-05-04 08:42:02 +03:00
hookSetup ( "hooks/pre-receive.log" )
2017-02-23 06:40:44 +03:00
2017-02-25 17:54:40 +03:00
// the environment setted on serv command
repoID , _ := strconv . ParseInt ( os . Getenv ( models . ProtectedBranchRepoID ) , 10 , 64 )
isWiki := ( os . Getenv ( models . EnvRepoIsWiki ) == "true" )
2017-09-14 11:16:22 +03:00
username := os . Getenv ( models . EnvRepoUsername )
reponame := os . Getenv ( models . EnvRepoName )
userIDStr := os . Getenv ( models . EnvPusherID )
repoPath := models . RepoPath ( username , reponame )
2017-02-25 17:54:40 +03:00
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
}
2017-09-14 11:16:22 +03:00
oldCommitID := string ( fields [ 0 ] )
2017-02-25 17:54:40 +03:00
newCommitID := string ( fields [ 1 ] )
refFullName := string ( fields [ 2 ] )
branchName := strings . TrimPrefix ( refFullName , git . BranchPrefix )
2017-05-04 08:42:02 +03:00
protectBranch , err := private . GetProtectedBranchBy ( repoID , branchName )
2017-02-25 17:54:40 +03:00
if err != nil {
log . GitLogger . Fatal ( 2 , "retrieve protected branches information failed" )
}
2017-09-14 11:16:22 +03:00
if protectBranch != nil && protectBranch . IsProtected ( ) {
// detect force push
if git . EmptySHA != oldCommitID {
2017-10-28 22:36:20 +03:00
output , err := git . NewCommand ( "rev-list" , "--max-count=1" , oldCommitID , "^" + newCommitID ) . RunInDir ( repoPath )
2017-09-14 11:16:22 +03:00
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 ) , "" )
}
}
// check and deletion
if newCommitID == git . EmptySHA {
fail ( fmt . Sprintf ( "branch %s is protected from deletion" , branchName ) , "" )
} else {
userID , _ := strconv . ParseInt ( userIDStr , 10 , 64 )
canPush , err := private . CanUserPush ( protectBranch . ID , userID )
if err != nil {
fail ( "Internal error" , "Fail to detect user can push: %v" , err )
} else if ! canPush {
2017-05-04 08:42:02 +03:00
fail ( fmt . Sprintf ( "protected branch %s can not be pushed to" , branchName ) , "" )
}
2017-03-01 18:01:03 +03:00
}
2017-02-25 17:54:40 +03:00
}
}
2017-02-23 06:40:44 +03:00
return nil
}
func runHookUpdate ( c * cli . Context ) error {
if len ( os . Getenv ( "SSH_ORIGINAL_COMMAND" ) ) == 0 {
return nil
}
2017-03-01 18:01:03 +03:00
if c . IsSet ( "config" ) {
setting . CustomConf = c . String ( "config" )
} else if c . GlobalIsSet ( "config" ) {
setting . CustomConf = c . GlobalString ( "config" )
}
2017-05-04 08:42:02 +03:00
hookSetup ( "hooks/update.log" )
2017-02-23 06:40:44 +03:00
return nil
}
func runHookPostReceive ( c * cli . Context ) error {
if len ( os . Getenv ( "SSH_ORIGINAL_COMMAND" ) ) == 0 {
return nil
}
2017-03-01 18:01:03 +03:00
if c . IsSet ( "config" ) {
setting . CustomConf = c . String ( "config" )
} else if c . GlobalIsSet ( "config" ) {
setting . CustomConf = c . GlobalString ( "config" )
}
2017-05-04 08:42:02 +03:00
hookSetup ( "hooks/post-receive.log" )
2017-02-23 06:40:44 +03:00
2017-02-25 17:54:40 +03:00
// the environment setted on serv command
repoUser := os . Getenv ( models . EnvRepoUsername )
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 ] )
2017-05-04 08:42:02 +03:00
if err := private . PushUpdate ( models . PushUpdateOptions {
2017-02-25 17:54:40 +03:00
RefFullName : refFullName ,
OldCommitID : oldCommitID ,
NewCommitID : newCommitID ,
PusherID : pusherID ,
PusherName : pusherName ,
RepoUserName : repoUser ,
RepoName : repoName ,
} ) ; err != nil {
log . GitLogger . Error ( 2 , "Update: %v" , err )
}
}
2017-02-23 06:40:44 +03:00
return nil
}