// Copyright 2021 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"
"strings"
"code.gitea.io/gitea/models"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
repo_service "code.gitea.io/gitea/services/repository"
)
// HookPostReceive updates services and users
func HookPostReceive ( ctx * gitea_context . PrivateContext ) {
opts := web . GetForm ( ctx ) . ( * private . HookOptions )
// We don't rely on RepoAssignment here because:
// a) we don't need the git repo in this function
// b) our update function will likely change the repository in the db so we will need to refresh it
// c) we don't always need the repo
ownerName := ctx . Params ( ":owner" )
repoName := ctx . Params ( ":repo" )
// defer getting the repository at this point - as we should only retrieve it if we're going to call update
var repo * models . Repository
updates := make ( [ ] * repo_module . PushUpdateOptions , 0 , len ( opts . OldCommitIDs ) )
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 {
repo = loadRepository ( ctx , ownerName , repoName )
if ctx . Written ( ) {
// Error handled in loadRepository
return
}
wasEmpty = repo . IsEmpty
}
option := & repo_module . PushUpdateOptions {
RefFullName : refFullName ,
OldCommitID : opts . OldCommitIDs [ i ] ,
NewCommitID : opts . NewCommitIDs [ i ] ,
PusherID : opts . UserID ,
PusherName : opts . UserName ,
RepoUserName : ownerName ,
RepoName : repoName ,
}
updates = append ( updates , option )
if repo . IsEmpty && option . IsBranch ( ) && ( option . BranchName ( ) == "master" || option . BranchName ( ) == "main" ) {
// put the master/main branch first
copy ( updates [ 1 : ] , updates )
updates [ 0 ] = option
}
}
}
if repo != nil && len ( updates ) > 0 {
if err := repo_service . PushUpdates ( updates ) ; err != nil {
log . Error ( "Failed to Update: %s/%s Total Updates: %d" , ownerName , repoName , len ( updates ) )
for i , update := range updates {
log . Error ( "Failed to Update: %s/%s Update: %d/%d: Branch: %s" , ownerName , repoName , i , len ( updates ) , update . BranchName ( ) )
}
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 ) ,
} )
return
}
}
// Handle Push Options
if len ( opts . GitPushOptions ) > 0 {
// load the repository
if repo == nil {
repo = loadRepository ( ctx , ownerName , repoName )
if ctx . Written ( ) {
// Error handled in loadRepository
return
}
wasEmpty = repo . IsEmpty
}
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 ) ,
} )
}
}
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
// Now handle the pull request notification trailers
for i := range opts . OldCommitIDs {
refFullName := opts . RefFullNames [ i ]
newCommitID := opts . NewCommitIDs [ i ]
branch := git . RefEndName ( opts . RefFullNames [ i ] )
// If we've pushed a branch (and not deleted it)
if newCommitID != git . EmptySHA && strings . HasPrefix ( refFullName , git . BranchPrefix ) {
// First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo
if repo == nil {
repo = loadRepository ( ctx , ownerName , repoName )
if ctx . Written ( ) {
return
}
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 our branch is the default branch of an unforked repo - there's no PR to create or refer to
if ! repo . IsFork && branch == baseRepo . DefaultBranch {
results = append ( results , private . HookPostReceiveBranchResult { } )
continue
}
pr , err := models . GetUnmergedPullRequest ( repo . ID , baseRepo . ID , branch , baseRepo . DefaultBranch , models . PullRequestFlowGithub )
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 ,
} )
return
}
if pr == nil {
if repo . IsFork {
branch = fmt . Sprintf ( "%s:%s" , repo . OwnerName , branch )
}
results = append ( results , private . HookPostReceiveBranchResult {
Message : setting . Git . PullRequestPushMessage && repo . AllowsPulls ( ) ,
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 {
Message : setting . Git . PullRequestPushMessage && repo . AllowsPulls ( ) ,
Create : false ,
Branch : branch ,
URL : fmt . Sprintf ( "%s/pulls/%d" , baseRepo . HTMLURL ( ) , pr . Index ) ,
} )
}
}
}
ctx . JSON ( http . StatusOK , private . HookPostReceiveResult {
Results : results ,
RepoWasEmpty : wasEmpty ,
} )
}