2019-06-01 23:00:21 +08:00
// 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 (
2020-01-15 16:32:57 +08:00
"bufio"
"context"
2019-06-01 23:00:21 +08:00
"fmt"
2020-01-15 16:32:57 +08:00
"io"
2019-06-01 23:00:21 +08:00
"net/http"
"os"
"strings"
"code.gitea.io/gitea/models"
2021-01-26 23:36:53 +08:00
gitea_context "code.gitea.io/gitea/modules/context"
2019-06-01 23:00:21 +08:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
2020-10-31 05:59:02 +08:00
repo_module "code.gitea.io/gitea/modules/repository"
2020-03-08 21:34:38 +08:00
"code.gitea.io/gitea/modules/setting"
2019-06-01 23:00:21 +08:00
"code.gitea.io/gitea/modules/util"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
2021-07-28 17:42:56 +08:00
"code.gitea.io/gitea/services/agit"
2020-01-11 15:29:34 +08:00
pull_service "code.gitea.io/gitea/services/pull"
2020-09-11 22:14:48 +08:00
repo_service "code.gitea.io/gitea/services/repository"
2019-06-01 23:00:21 +08:00
)
2020-01-15 16:32:57 +08:00
func verifyCommits ( oldCommitID , newCommitID string , repo * git . Repository , env [ ] string ) error {
stdoutReader , stdoutWriter , err := os . Pipe ( )
if err != nil {
log . Error ( "Unable to create os.Pipe for %s" , repo . Path )
return err
}
defer func ( ) {
_ = stdoutReader . Close ( )
_ = stdoutWriter . Close ( )
} ( )
2020-07-30 01:53:04 +08:00
// This is safe as force pushes are already forbidden
2020-01-15 16:32:57 +08:00
err = git . NewCommand ( "rev-list" , oldCommitID + "..." + newCommitID ) .
RunInDirTimeoutEnvFullPipelineFunc ( env , - 1 , repo . Path ,
stdoutWriter , nil , nil ,
func ( ctx context . Context , cancel context . CancelFunc ) error {
_ = stdoutWriter . Close ( )
err := readAndVerifyCommitsFromShaReader ( stdoutReader , repo , env )
if err != nil {
log . Error ( "%v" , err )
cancel ( )
}
_ = stdoutReader . Close ( )
return err
} )
if err != nil && ! isErrUnverifiedCommit ( err ) {
log . Error ( "Unable to check commits from %s to %s in %s: %v" , oldCommitID , newCommitID , repo . Path , err )
}
return err
}
func readAndVerifyCommitsFromShaReader ( input io . ReadCloser , repo * git . Repository , env [ ] string ) error {
scanner := bufio . NewScanner ( input )
for scanner . Scan ( ) {
line := scanner . Text ( )
err := readAndVerifyCommit ( line , repo , env )
if err != nil {
log . Error ( "%v" , err )
return err
}
}
return scanner . Err ( )
}
func readAndVerifyCommit ( sha string , repo * git . Repository , env [ ] string ) error {
stdoutReader , stdoutWriter , err := os . Pipe ( )
if err != nil {
log . Error ( "Unable to create pipe for %s: %v" , repo . Path , err )
return err
}
defer func ( ) {
_ = stdoutReader . Close ( )
_ = stdoutWriter . Close ( )
} ( )
2020-12-17 22:00:47 +08:00
hash := git . MustIDFromString ( sha )
2020-01-15 16:32:57 +08:00
return git . NewCommand ( "cat-file" , "commit" , sha ) .
RunInDirTimeoutEnvFullPipelineFunc ( env , - 1 , repo . Path ,
stdoutWriter , nil , nil ,
func ( ctx context . Context , cancel context . CancelFunc ) error {
_ = stdoutWriter . Close ( )
commit , err := git . CommitFromReader ( repo , hash , stdoutReader )
if err != nil {
return err
}
verification := models . ParseCommitWithSignature ( commit )
if ! verification . Verified {
cancel ( )
return & errUnverifiedCommit {
commit . ID . String ( ) ,
}
}
return nil
} )
}
type errUnverifiedCommit struct {
sha string
}
func ( e * errUnverifiedCommit ) Error ( ) string {
return fmt . Sprintf ( "Unverified commit: %s" , e . sha )
}
func isErrUnverifiedCommit ( err error ) bool {
_ , ok := err . ( * errUnverifiedCommit )
return ok
}
2019-06-01 23:00:21 +08:00
// HookPreReceive checks whether a individual commit is acceptable
2021-01-26 23:36:53 +08:00
func HookPreReceive ( ctx * gitea_context . PrivateContext ) {
opts := web . GetForm ( ctx ) . ( * private . HookOptions )
2019-06-01 23:00:21 +08:00
ownerName := ctx . Params ( ":owner" )
repoName := ctx . Params ( ":repo" )
repo , err := models . GetRepositoryByOwnerAndName ( ownerName , repoName )
if err != nil {
log . Error ( "Unable to get repository: %s/%s Error: %v" , ownerName , repoName , err )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : err . Error ( ) ,
2019-06-01 23:00:21 +08:00
} )
return
}
repo . OwnerName = ownerName
2020-01-15 16:32:57 +08:00
gitRepo , err := git . OpenRepository ( repo . RepoPath ( ) )
if err != nil {
log . Error ( "Unable to get git repository for: %s/%s Error: %v" , ownerName , repoName , err )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : err . Error ( ) ,
2020-01-15 16:32:57 +08:00
} )
return
}
defer gitRepo . Close ( )
// Generate git environment for checking commits
env := os . Environ ( )
if opts . GitAlternativeObjectDirectories != "" {
env = append ( env ,
private . GitAlternativeObjectDirectories + "=" + opts . GitAlternativeObjectDirectories )
}
if opts . GitObjectDirectory != "" {
env = append ( env ,
private . GitObjectDirectory + "=" + opts . GitObjectDirectory )
}
if opts . GitQuarantinePath != "" {
env = append ( env ,
private . GitQuarantinePath + "=" + opts . GitQuarantinePath )
}
2019-12-26 19:29:45 +08:00
2021-07-28 17:42:56 +08:00
if git . SupportProcReceive {
pusher , err := models . GetUserByID ( opts . UserID )
if err != nil {
log . Error ( "models.GetUserByID: %v" , err )
ctx . Error ( http . StatusInternalServerError , "" )
return
}
perm , err := models . GetUserRepoPermission ( repo , pusher )
if err != nil {
log . Error ( "models.GetUserRepoPermission:%v" , err )
ctx . Error ( http . StatusInternalServerError , "" )
return
}
canCreatePullRequest := perm . CanRead ( models . UnitTypePullRequests )
for _ , refFullName := range opts . RefFullNames {
// if user want update other refs (branch or tag),
// should check code write permission because
// this check was delayed.
if ! strings . HasPrefix ( refFullName , git . PullRequestPrefix ) {
if ! perm . CanWrite ( models . UnitTypeCode ) {
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "User permission denied." ,
} )
return
}
break
} else if repo . IsEmpty {
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "Can't create pull request for an empty repository." ,
} )
return
} else if ! canCreatePullRequest {
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "User permission denied." ,
} )
return
} else if opts . IsWiki {
// TODO: maybe can do it ...
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "not support send pull request to wiki." ,
} )
return
}
}
}
2021-06-25 22:28:55 +08:00
protectedTags , err := repo . GetProtectedTags ( )
if err != nil {
log . Error ( "Unable to get protected tags for %-v Error: %v" , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : err . Error ( ) ,
} )
return
}
2020-10-14 02:50:57 +08:00
// Iterate across the provided old commit IDs
2019-12-26 19:29:45 +08:00
for i := range opts . OldCommitIDs {
oldCommitID := opts . OldCommitIDs [ i ]
newCommitID := opts . NewCommitIDs [ i ]
refFullName := opts . RefFullNames [ i ]
2021-06-25 22:28:55 +08:00
if strings . HasPrefix ( refFullName , git . BranchPrefix ) {
branchName := strings . TrimPrefix ( refFullName , git . BranchPrefix )
if branchName == repo . DefaultBranch && newCommitID == git . EmptySHA {
log . Warn ( "Forbidden: Branch: %s is the default branch in %-v and cannot be deleted" , branchName , repo )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is the default branch and cannot be deleted" , branchName ) ,
} )
return
}
2020-10-14 02:50:57 +08:00
2021-06-25 22:28:55 +08:00
protectBranch , err := models . GetProtectedBranchBy ( repo . ID , branchName )
2020-10-14 02:50:57 +08:00
if err != nil {
2021-06-25 22:28:55 +08:00
log . Error ( "Unable to get protected branch: %s in %-v Error: %v" , branchName , repo , err )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
2021-06-25 22:28:55 +08:00
Err : err . Error ( ) ,
2020-10-14 02:50:57 +08:00
} )
return
2021-06-25 22:28:55 +08:00
}
// Allow pushes to non-protected branches
if protectBranch == nil || ! protectBranch . IsProtected ( ) {
continue
}
// This ref is a protected branch.
//
// First of all we need to enforce absolutely:
//
// 1. Detect and prevent deletion of the branch
if newCommitID == git . EmptySHA {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from deletion" , branchName , repo )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusForbidden , private . Response {
2021-06-25 22:28:55 +08:00
Err : fmt . Sprintf ( "branch %s is protected from deletion" , branchName ) ,
2019-06-01 23:00:21 +08:00
} )
return
}
2021-06-25 22:28:55 +08:00
// 2. Disallow force pushes to protected branches
if git . EmptySHA != oldCommitID {
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 )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
2021-06-25 22:28:55 +08:00
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 , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from force push" , branchName ) ,
2019-12-26 19:29:45 +08:00
} )
return
2021-06-25 22:28:55 +08:00
2019-12-26 19:29:45 +08:00
}
}
2020-01-15 16:32:57 +08:00
2021-06-25 22:28:55 +08:00
// 3. Enforce require signed commits
if protectBranch . RequireSignedCommits {
err := verifyCommits ( oldCommitID , newCommitID , gitRepo , env )
if err != nil {
if ! isErrUnverifiedCommit ( err ) {
log . Error ( "Unable to check commits from %s to %s in %-v: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to check commits from %s to %s: %v" , oldCommitID , newCommitID , err ) ,
} )
return
}
unverifiedCommit := err . ( * errUnverifiedCommit ) . sha
log . Warn ( "Forbidden: Branch: %s in %-v is protected from unverified commit %s" , branchName , repo , unverifiedCommit )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from unverified commit %s" , branchName , unverifiedCommit ) ,
} )
return
}
}
2020-10-14 02:50:57 +08:00
2021-06-25 22:28:55 +08:00
// Now there are several tests which can be overridden:
//
// 4. Check protected file patterns - this is overridable from the UI
changedProtectedfiles := false
protectedFilePath := ""
globs := protectBranch . GetProtectedFilePatterns ( )
if len ( globs ) > 0 {
_ , err := pull_service . CheckFileProtection ( oldCommitID , newCommitID , globs , 1 , env , gitRepo )
if err != nil {
if ! models . IsErrFilePathProtected ( err ) {
log . Error ( "Unable to check file protection for commits from %s to %s in %-v: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to check file protection for commits from %s to %s: %v" , oldCommitID , newCommitID , err ) ,
} )
return
}
changedProtectedfiles = true
protectedFilePath = err . ( models . ErrFilePathProtected ) . Path
}
}
// 5. Check if the doer is allowed to push
canPush := false
if opts . IsDeployKey {
canPush = ! changedProtectedfiles && protectBranch . CanPush && ( ! protectBranch . EnableWhitelist || protectBranch . WhitelistDeployKeys )
} else {
canPush = ! changedProtectedfiles && protectBranch . CanUserPush ( opts . UserID )
}
// 6. If we're not allowed to push directly
if ! canPush {
// Is this is a merge from the UI/API?
if opts . PullRequestID == 0 {
// 6a. If we're not merging from the UI/API then there are two ways we got here:
//
// We are changing a protected file and we're not allowed to do that
if changedProtectedfiles {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from changing file %s" , branchName , repo , protectedFilePath )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from changing file %s" , branchName , protectedFilePath ) ,
} )
return
}
// Or we're simply not able to push to this protected branch
log . Warn ( "Forbidden: User %d is not allowed to push to protected branch: %s in %-v" , opts . UserID , branchName , repo )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Not allowed to push to protected branch %s" , branchName ) ,
} )
return
}
// 6b. Merge (from UI or API)
// Get the PR, user and permissions for the user in the repository
pr , err := models . GetPullRequestByID ( opts . PullRequestID )
if err != nil {
log . Error ( "Unable to get PullRequest %d Error: %v" , opts . PullRequestID , err )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
2021-06-25 22:28:55 +08:00
Err : fmt . Sprintf ( "Unable to get PullRequest %d Error: %v" , opts . PullRequestID , err ) ,
} )
return
}
user , err := models . GetUserByID ( opts . UserID )
if err != nil {
log . Error ( "Unable to get User id %d Error: %v" , opts . UserID , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get User id %d Error: %v" , opts . UserID , err ) ,
} )
return
}
perm , err := models . GetUserRepoPermission ( repo , user )
if err != nil {
log . Error ( "Unable to get Repo permission of repo %s/%s of User %s" , repo . OwnerName , repo . Name , user . Name , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get Repo permission of repo %s/%s of User %s: %v" , repo . OwnerName , repo . Name , user . Name , err ) ,
2020-01-15 16:32:57 +08:00
} )
return
}
2020-10-14 02:50:57 +08:00
2021-06-25 22:28:55 +08:00
// Now check if the user is allowed to merge PRs for this repository
allowedMerge , err := pull_service . IsUserAllowedToMerge ( pr , perm , user )
if err != nil {
log . Error ( "Error calculating if allowed to merge: %v" , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Error calculating if allowed to merge: %v" , err ) ,
} )
return
}
2020-01-15 16:32:57 +08:00
2021-06-25 22:28:55 +08:00
if ! allowedMerge {
log . Warn ( "Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d" , opts . UserID , branchName , repo , pr . Index )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Not allowed to push to protected branch %s" , branchName ) ,
} )
return
}
// If we're an admin for the repository we can ignore status checks, reviews and override protected files
if perm . IsAdmin ( ) {
continue
}
2020-10-14 02:50:57 +08:00
2021-06-25 22:28:55 +08:00
// Now if we're not an admin - we can't overwrite protected files so fail now
2020-10-14 02:50:57 +08:00
if changedProtectedfiles {
2020-03-27 06:26:34 +08:00
log . Warn ( "Forbidden: Branch: %s in %-v is protected from changing file %s" , branchName , repo , protectedFilePath )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from changing file %s" , branchName , protectedFilePath ) ,
2020-03-27 06:26:34 +08:00
} )
return
}
2020-10-14 02:50:57 +08:00
2021-06-25 22:28:55 +08:00
// Check all status checks and reviews are ok
if err := pull_service . CheckPRReadyToMerge ( pr , true ) ; err != nil {
if models . IsErrNotAllowedToMerge ( err ) {
log . Warn ( "Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s" , opts . UserID , branchName , repo , pr . Index , err . Error ( ) )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s" , branchName , opts . PullRequestID , err . Error ( ) ) ,
} )
return
}
log . Error ( "Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v" , opts . UserID , branchName , repo , pr . Index , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get status of pull request %d. Error: %v" , opts . PullRequestID , err ) ,
} )
return
}
2020-10-14 02:50:57 +08:00
}
2021-06-25 22:28:55 +08:00
} else if strings . HasPrefix ( refFullName , git . TagPrefix ) {
tagName := strings . TrimPrefix ( refFullName , git . TagPrefix )
2020-10-14 02:50:57 +08:00
2021-06-25 22:28:55 +08:00
isAllowed , err := models . IsUserAllowedToControlTag ( protectedTags , tagName , opts . UserID )
2020-10-14 02:50:57 +08:00
if err != nil {
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
2021-06-25 22:28:55 +08:00
Err : err . Error ( ) ,
2020-10-14 02:50:57 +08:00
} )
return
}
2021-06-25 22:28:55 +08:00
if ! isAllowed {
log . Warn ( "Forbidden: Tag %s in %-v is protected" , tagName , repo )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusForbidden , private . Response {
2021-06-25 22:28:55 +08:00
Err : fmt . Sprintf ( "Tag %s is protected" , tagName ) ,
2019-07-01 09:18:13 +08:00
} )
return
}
2021-07-28 17:42:56 +08:00
} else if git . SupportProcReceive && strings . HasPrefix ( refFullName , git . PullRequestPrefix ) {
baseBranchName := opts . RefFullNames [ i ] [ len ( git . PullRequestPrefix ) : ]
baseBranchExist := false
if gitRepo . IsBranchExist ( baseBranchName ) {
baseBranchExist = true
}
if ! baseBranchExist {
for p , v := range baseBranchName {
if v == '/' && gitRepo . IsBranchExist ( baseBranchName [ : p ] ) && p != len ( baseBranchName ) - 1 {
baseBranchExist = true
break
}
}
}
if ! baseBranchExist {
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Unexpected ref: %s" , refFullName ) ,
} )
return
}
2021-06-25 22:28:55 +08:00
} else {
log . Error ( "Unexpected ref: %s" , refFullName )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unexpected ref: %s" , refFullName ) ,
} )
2021-07-28 17:42:56 +08:00
return
2019-06-01 23:00:21 +08:00
}
}
2019-12-26 19:29:45 +08:00
2019-06-01 23:00:21 +08:00
ctx . PlainText ( http . StatusOK , [ ] byte ( "ok" ) )
}
// HookPostReceive updates services and users
2021-01-26 23:36:53 +08:00
func HookPostReceive ( ctx * gitea_context . PrivateContext ) {
opts := web . GetForm ( ctx ) . ( * private . HookOptions )
2019-06-01 23:00:21 +08:00
ownerName := ctx . Params ( ":owner" )
repoName := ctx . Params ( ":repo" )
2019-12-26 19:29:45 +08:00
var repo * models . Repository
2020-10-31 05:59:02 +08:00
updates := make ( [ ] * repo_module . PushUpdateOptions , 0 , len ( opts . OldCommitIDs ) )
2019-12-26 19:29:45 +08:00
wasEmpty := false
for i := range opts . OldCommitIDs {
refFullName := opts . RefFullNames [ i ]
// 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 ) {
if repo == nil {
var err error
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 , private . HookPostReceiveResult {
Err : fmt . Sprintf ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
} )
return
}
if repo . OwnerName == "" {
repo . OwnerName = ownerName
}
wasEmpty = repo . IsEmpty
}
2020-10-31 05:59:02 +08:00
option := repo_module . PushUpdateOptions {
2019-12-26 19:29:45 +08:00
RefFullName : refFullName ,
OldCommitID : opts . OldCommitIDs [ i ] ,
NewCommitID : opts . NewCommitIDs [ i ] ,
PusherID : opts . UserID ,
PusherName : opts . UserName ,
RepoUserName : ownerName ,
RepoName : repoName ,
}
updates = append ( updates , & option )
2020-12-30 23:46:26 +08:00
if repo . IsEmpty && option . IsBranch ( ) && ( option . BranchName ( ) == "master" || option . BranchName ( ) == "main" ) {
// put the master/main branch first
2019-12-26 19:29:45 +08:00
copy ( updates [ 1 : ] , updates )
updates [ 0 ] = & option
}
2019-06-01 23:00:21 +08:00
}
}
2019-12-26 19:29:45 +08:00
if repo != nil && len ( updates ) > 0 {
2020-09-11 22:14:48 +08:00
if err := repo_service . PushUpdates ( updates ) ; err != nil {
2019-12-26 19:29:45 +08:00
log . Error ( "Failed to Update: %s/%s Total Updates: %d" , ownerName , repoName , len ( updates ) )
for i , update := range updates {
2020-02-03 04:27:34 +08:00
log . Error ( "Failed to Update: %s/%s Update: %d/%d: Branch: %s" , ownerName , repoName , i , len ( updates ) , update . BranchName ( ) )
2019-12-26 19:29:45 +08:00
}
log . Error ( "Failed to Update: %s/%s Error: %v" , ownerName , repoName , err )
2019-06-01 23:00:21 +08:00
2019-12-26 19:29:45 +08:00
ctx . JSON ( http . StatusInternalServerError , private . HookPostReceiveResult {
Err : fmt . Sprintf ( "Failed to Update: %s/%s Error: %v" , ownerName , repoName , err ) ,
2019-06-01 23:00:21 +08:00
} )
return
}
2019-12-26 19:29:45 +08:00
}
2020-08-24 00:02:35 +08:00
// Push Options
if repo != nil && len ( opts . GitPushOptions ) > 0 {
repo . IsPrivate = opts . GitPushOptions . Bool ( private . GitPushOptionRepoPrivate , repo . IsPrivate )
repo . IsTemplate = opts . GitPushOptions . Bool ( private . GitPushOptionRepoTemplate , repo . IsTemplate )
if err := models . UpdateRepositoryCols ( repo , "is_private" , "is_template" ) ; err != nil {
log . Error ( "Failed to Update: %s/%s Error: %v" , ownerName , repoName , err )
ctx . JSON ( http . StatusInternalServerError , private . HookPostReceiveResult {
Err : fmt . Sprintf ( "Failed to Update: %s/%s Error: %v" , ownerName , repoName , err ) ,
} )
}
}
2019-12-26 19:29:45 +08:00
results := make ( [ ] private . HookPostReceiveBranchResult , 0 , len ( opts . OldCommitIDs ) )
// We have to reload the repo in case its state is changed above
repo = nil
var baseRepo * models . Repository
for i := range opts . OldCommitIDs {
refFullName := opts . RefFullNames [ i ]
newCommitID := opts . NewCommitIDs [ i ]
branch := git . RefEndName ( opts . RefFullNames [ i ] )
if newCommitID != git . EmptySHA && strings . HasPrefix ( refFullName , git . BranchPrefix ) {
if repo == nil {
var err error
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 , private . HookPostReceiveResult {
Err : fmt . Sprintf ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
RepoWasEmpty : wasEmpty ,
} )
return
}
if repo . OwnerName == "" {
repo . OwnerName = ownerName
}
if ! repo . AllowsPulls ( ) {
// We can stop there's no need to go any further
ctx . JSON ( http . StatusOK , private . HookPostReceiveResult {
RepoWasEmpty : wasEmpty ,
} )
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 , private . HookPostReceiveResult {
Err : fmt . Sprintf ( "Failed to get Base Repository of Forked repository: %-v Error: %v" , repo , err ) ,
RepoWasEmpty : wasEmpty ,
} )
return
}
baseRepo = repo . BaseRepo
}
}
if ! repo . IsFork && branch == baseRepo . DefaultBranch {
results = append ( results , private . HookPostReceiveBranchResult { } )
continue
}
2019-06-01 23:00:21 +08:00
2021-07-28 17:42:56 +08:00
pr , err := models . GetUnmergedPullRequest ( repo . ID , baseRepo . ID , branch , baseRepo . DefaultBranch , models . PullRequestFlowGithub )
2019-12-26 19:29:45 +08:00
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 , private . HookPostReceiveResult {
Err : fmt . Sprintf (
"Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v" , repo , branch , baseRepo , baseRepo . DefaultBranch , err ) ,
RepoWasEmpty : wasEmpty ,
2019-06-01 23:00:21 +08:00
} )
return
}
2019-12-26 19:29:45 +08:00
if pr == nil {
if repo . IsFork {
branch = fmt . Sprintf ( "%s:%s" , repo . OwnerName , branch )
}
results = append ( results , private . HookPostReceiveBranchResult {
2020-03-08 21:34:38 +08:00
Message : setting . Git . PullRequestPushMessage && repo . AllowsPulls ( ) ,
2019-12-26 19:29:45 +08:00
Create : true ,
Branch : branch ,
URL : fmt . Sprintf ( "%s/compare/%s...%s" , baseRepo . HTMLURL ( ) , util . PathEscapeSegments ( baseRepo . DefaultBranch ) , util . PathEscapeSegments ( branch ) ) ,
} )
} else {
results = append ( results , private . HookPostReceiveBranchResult {
2020-03-08 21:34:38 +08:00
Message : setting . Git . PullRequestPushMessage && repo . AllowsPulls ( ) ,
2019-12-26 19:29:45 +08:00
Create : false ,
Branch : branch ,
URL : fmt . Sprintf ( "%s/pulls/%d" , baseRepo . HTMLURL ( ) , pr . Index ) ,
} )
}
2019-06-01 23:00:21 +08:00
}
2019-12-26 19:29:45 +08:00
}
ctx . JSON ( http . StatusOK , private . HookPostReceiveResult {
Results : results ,
RepoWasEmpty : wasEmpty ,
} )
}
2019-06-01 23:00:21 +08:00
2021-07-28 17:42:56 +08:00
// HookProcReceive proc-receive hook
func HookProcReceive ( ctx * gitea_context . PrivateContext ) {
opts := web . GetForm ( ctx ) . ( * private . HookOptions )
if ! git . SupportProcReceive {
ctx . Status ( http . StatusNotFound )
return
}
cancel := loadRepositoryAndGitRepoByParams ( ctx )
if ctx . Written ( ) {
return
}
defer cancel ( )
results := agit . ProcRecive ( ctx , opts )
if ctx . Written ( ) {
return
}
ctx . JSON ( http . StatusOK , private . HockProcReceiveResult {
Results : results ,
} )
}
2019-12-26 19:29:45 +08:00
// SetDefaultBranch updates the default branch
2021-01-26 23:36:53 +08:00
func SetDefaultBranch ( ctx * gitea_context . PrivateContext ) {
2019-12-26 19:29:45 +08:00
ownerName := ctx . Params ( ":owner" )
repoName := ctx . Params ( ":repo" )
branch := ctx . Params ( ":branch" )
repo , err := models . GetRepositoryByOwnerAndName ( ownerName , repoName )
if err != nil {
log . Error ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Failed to get repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
2019-12-26 19:29:45 +08:00
} )
return
}
if repo . OwnerName == "" {
repo . OwnerName = ownerName
}
repo . DefaultBranch = branch
gitRepo , err := git . OpenRepository ( repo . RepoPath ( ) )
if err != nil {
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Failed to get git repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
2019-12-26 19:29:45 +08:00
} )
return
}
if err := gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
if ! git . IsErrUnsupportedVersion ( err ) {
gitRepo . Close ( )
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to set default branch on repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
2019-06-01 23:00:21 +08:00
} )
return
}
2019-12-26 19:29:45 +08:00
}
gitRepo . Close ( )
2019-06-01 23:00:21 +08:00
2019-12-26 19:29:45 +08:00
if err := repo . UpdateDefaultBranch ( ) ; err != nil {
2021-06-24 03:38:19 +08:00
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to set default branch on repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
2019-12-26 19:29:45 +08:00
} )
2019-06-01 23:00:21 +08:00
return
}
2021-06-24 03:38:19 +08:00
ctx . PlainText ( http . StatusOK , [ ] byte ( "success" ) )
2019-06-01 23:00:21 +08:00
}
2021-07-28 17:42:56 +08:00
func loadRepositoryAndGitRepoByParams ( ctx * gitea_context . PrivateContext ) context . CancelFunc {
ownerName := ctx . Params ( ":owner" )
repoName := ctx . Params ( ":repo" )
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 nil
}
if repo . OwnerName == "" {
repo . OwnerName = ownerName
}
gitRepo , err := git . OpenRepository ( repo . RepoPath ( ) )
if err != nil {
log . Error ( "Failed to open repository: %s/%s Error: %v" , ownerName , repoName , err )
ctx . JSON ( http . StatusInternalServerError , map [ string ] interface { } {
"Err" : fmt . Sprintf ( "Failed to open repository: %s/%s Error: %v" , ownerName , repoName , err ) ,
} )
return nil
}
ctx . Repo = & gitea_context . Repository {
Repository : repo ,
GitRepo : gitRepo ,
}
// We opened it, we should close it
cancel := func ( ) {
// If it's been set to nil then assume someone else has closed it.
if ctx . Repo . GitRepo != nil {
ctx . Repo . GitRepo . Close ( )
}
}
return cancel
}