// Copyright 2019 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
package private
import (
"fmt"
"net/http"
"os"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/util"
"gitea.com/macaron/macaron"
)
// HookPreReceive checks whether a individual commit is acceptable
func HookPreReceive ( ctx * macaron . Context ) {
ownerName := ctx . Params ( ":owner" )
repoName := ctx . Params ( ":repo" )
oldCommitID := ctx . QueryTrim ( "old" )
newCommitID := ctx . QueryTrim ( "new" )
refFullName := ctx . QueryTrim ( "ref" )
userID := ctx . QueryInt64 ( "userID" )
gitObjectDirectory := ctx . QueryTrim ( "gitObjectDirectory" )
gitAlternativeObjectDirectories := ctx . QueryTrim ( "gitAlternativeObjectDirectories" )
gitQuarantinePath := ctx . QueryTrim ( "gitQuarantinePath" )
prID := ctx . QueryInt64 ( "prID" )
isDeployKey := ctx . QueryBool ( "isDeployKey" )
branchName := strings . TrimPrefix ( refFullName , git . BranchPrefix )
repo , err := models . GetRepositoryByOwnerAndName ( ownerName , repoName )
if err != nil {
log . Error ( "Unable to get repository: %s/%s Error: %v" , ownerName , repoName , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : err . Error ( ) ,
} )
return
}
repo . OwnerName = ownerName
protectBranch , err := models . GetProtectedBranchBy ( repo . ID , branchName )
if err != nil {
log . Error ( "Unable to get protected branch: %s in %-v Error: %v" , branchName , repo , err )
ctx . JSON ( 500 , map [ string ] interface { } {
"err" : err . Error ( ) ,
} )
return
}
if protectBranch != nil && protectBranch . IsProtected ( ) {
// check and deletion
if newCommitID == git . EmptySHA {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from deletion" , branchName , repo )
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : fmt . Sprintf ( "branch %s is protected from deletion" , branchName ) ,
} )
return
}
// detect force push
if git . EmptySHA != oldCommitID {
env := os . Environ ( )
if gitAlternativeObjectDirectories != "" {
env = append ( env ,
private . GitAlternativeObjectDirectories + "=" + gitAlternativeObjectDirectories )
}
if gitObjectDirectory != "" {
env = append ( env ,
private . GitObjectDirectory + "=" + gitObjectDirectory )
}
if gitQuarantinePath != "" {
env = append ( env ,
private . GitQuarantinePath + "=" + gitQuarantinePath )
}
output , err := git . NewCommand ( "rev-list" , "--max-count=1" , oldCommitID , "^" + newCommitID ) . RunInDirWithEnv ( repo . RepoPath ( ) , env )
if err != nil {
log . Error ( "Unable to detect force push between: %s and %s in %-v Error: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : fmt . Sprintf ( "Fail to detect force push: %v" , err ) ,
} )
return
} else if len ( output ) > 0 {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from force push" , branchName , repo )
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : fmt . Sprintf ( "branch %s is protected from force push" , branchName ) ,
} )
return
}
}
canPush := false
if isDeployKey {
canPush = protectBranch . WhitelistDeployKeys
} else {
canPush = protectBranch . CanUserPush ( userID )
}
if ! canPush && prID > 0 {
pr , err := models . GetPullRequestByID ( prID )
if err != nil {
log . Error ( "Unable to get PullRequest %d Error: %v" , prID , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : fmt . Sprintf ( "Unable to get PullRequest %d Error: %v" , prID , err ) ,
} )
return
}
if ! protectBranch . HasEnoughApprovals ( pr ) {
log . Warn ( "Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d does not have enough approvals" , userID , branchName , repo , pr . Index )
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : fmt . Sprintf ( "protected branch %s can not be pushed to and pr #%d does not have enough approvals" , branchName , prID ) ,
} )
return
}
} else if ! canPush {
log . Warn ( "Forbidden: User %d cannot push to protected branch: %s in %-v" , userID , branchName , repo )
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : fmt . Sprintf ( "protected branch %s can not be pushed to" , branchName ) ,
} )
return
}
}
ctx . PlainText ( http . StatusOK , [ ] byte ( "ok" ) )
}
// HookPostReceive updates services and users
func HookPostReceive ( ctx * macaron . Context ) {
ownerName := ctx . Params ( ":owner" )
repoName := ctx . Params ( ":repo" )
oldCommitID := ctx . Query ( "old" )
newCommitID := ctx . Query ( "new" )
refFullName := ctx . Query ( "ref" )
userID := ctx . QueryInt64 ( "userID" )
userName := ctx . Query ( "username" )
branch := refFullName
if strings . HasPrefix ( refFullName , git . BranchPrefix ) {
branch = strings . TrimPrefix ( refFullName , git . BranchPrefix )
} else if strings . HasPrefix ( refFullName , git . TagPrefix ) {
branch = strings . TrimPrefix ( refFullName , git . TagPrefix )
}
// Only trigger activity updates for changes to branches or
// tags. Updates to other refs (eg, refs/notes, refs/changes,
// or other less-standard refs spaces are ignored since there
// may be a very large number of them).
if strings . HasPrefix ( refFullName , git . BranchPrefix ) || strings . HasPrefix ( refFullName , git . TagPrefix ) {
repo , err := models . GetRepositoryByOwnerAndName ( ownerName , repoName )
if err != nil {
log . Error ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : fmt . Sprintf ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
} )
return
}
if err := repofiles . PushUpdate ( repo , branch , repofiles . PushUpdateOptions {
RefFullName : refFullName ,
OldCommitID : oldCommitID ,
NewCommitID : newCommitID ,
PusherID : userID ,
PusherName : userName ,
RepoUserName : ownerName ,
RepoName : repoName ,
} ) ; err != nil {
log . Error ( "Failed to Update: %s/%s Branch: %s Error: %v" , ownerName , repoName , branch , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : fmt . Sprintf ( "Failed to Update: %s/%s Branch: %s Error: %v" , ownerName , repoName , branch , err ) ,
} )
return
}
}
if newCommitID != git . EmptySHA && strings . HasPrefix ( refFullName , git . BranchPrefix ) {
repo , err := models . GetRepositoryByOwnerAndName ( ownerName , repoName )
if err != nil {
log . Error ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : fmt . Sprintf ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
} )
return
}
repo . OwnerName = ownerName
pullRequestAllowed := repo . AllowsPulls ( )
if ! pullRequestAllowed {
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
"message" : false ,
} )
return
}
baseRepo := repo
if repo . IsFork {
if err := repo . GetBaseRepo ( ) ; err != nil {
log . Error ( "Failed to get Base Repository of Forked repository: %-v Error: %v" , repo , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : fmt . Sprintf ( "Failed to get Base Repository of Forked repository: %-v Error: %v" , repo , err ) ,
} )
return
}
baseRepo = repo . BaseRepo
}
if ! repo . IsFork && branch == baseRepo . DefaultBranch {
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
"message" : false ,
} )
return
}
pr , err := models . GetUnmergedPullRequest ( repo . ID , baseRepo . ID , branch , baseRepo . DefaultBranch )
if err != nil && ! models . IsErrPullRequestNotExist ( err ) {
log . Error ( "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v" , repo , branch , baseRepo , baseRepo . DefaultBranch , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"err" : fmt . Sprintf (
"Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v" , repo , branch , baseRepo , baseRepo . DefaultBranch , err ) ,
} )
return
}
if pr == nil {
if repo . IsFork {
branch = fmt . Sprintf ( "%s:%s" , repo . OwnerName , branch )
}
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
"message" : true ,
"create" : true ,
"branch" : branch ,
"url" : fmt . Sprintf ( "%s/compare/%s...%s" , baseRepo . HTMLURL ( ) , util . PathEscapeSegments ( baseRepo . DefaultBranch ) , util . PathEscapeSegments ( branch ) ) ,
} )
} else {
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
"message" : true ,
"create" : false ,
"branch" : branch ,
"url" : fmt . Sprintf ( "%s/pulls/%d" , baseRepo . HTMLURL ( ) , pr . Index ) ,
} )
}
return
}
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
"message" : false ,
} )
}