2021-09-16 21:34:54 +08:00
// 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"
2022-01-17 13:56:43 +08:00
"strconv"
2021-09-16 21:34:54 +08:00
"strings"
"code.gitea.io/gitea/models"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-09-16 21:34:54 +08:00
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
2021-12-10 09:27:50 +08:00
var repo * repo_model . Repository
2021-09-16 21:34:54 +08:00
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
}
2021-11-24 17:08:13 +08:00
option := & repo_module . PushUpdateOptions {
2021-09-16 21:34:54 +08:00
RefFullName : refFullName ,
OldCommitID : opts . OldCommitIDs [ i ] ,
NewCommitID : opts . NewCommitIDs [ i ] ,
PusherID : opts . UserID ,
PusherName : opts . UserName ,
RepoUserName : ownerName ,
RepoName : repoName ,
}
2021-11-24 17:08:13 +08:00
updates = append ( updates , option )
2021-09-16 21:34:54 +08:00
if repo . IsEmpty && option . IsBranch ( ) && ( option . BranchName ( ) == "master" || option . BranchName ( ) == "main" ) {
// put the master/main branch first
copy ( updates [ 1 : ] , updates )
2021-11-24 17:08:13 +08:00
updates [ 0 ] = option
2021-09-16 21:34:54 +08:00
}
}
}
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 )
2022-05-20 22:08:52 +08:00
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "is_private" , "is_template" ) ; err != nil {
2021-09-16 21:34:54 +08:00
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
2021-12-10 09:27:50 +08:00
var baseRepo * repo_model . Repository
2021-09-16 21:34:54 +08:00
// Now handle the pull request notification trailers
for i := range opts . OldCommitIDs {
refFullName := opts . RefFullNames [ i ]
newCommitID := opts . NewCommitIDs [ i ]
2022-01-17 13:56:43 +08:00
// post update for agit pull request
if git . SupportProcReceive && strings . HasPrefix ( refFullName , git . PullPrefix ) {
if repo == nil {
repo = loadRepository ( ctx , ownerName , repoName )
if ctx . Written ( ) {
return
}
}
pullIndexStr := strings . TrimPrefix ( refFullName , git . PullPrefix )
pullIndexStr = strings . Split ( pullIndexStr , "/" ) [ 0 ]
pullIndex , _ := strconv . ParseInt ( pullIndexStr , 10 , 64 )
if pullIndex <= 0 {
continue
}
2022-05-20 22:08:52 +08:00
pr , err := models . GetPullRequestByIndex ( ctx , repo . ID , pullIndex )
2022-01-17 13:56:43 +08:00
if err != nil && ! models . IsErrPullRequestNotExist ( err ) {
log . Error ( "Failed to get PR by index %v Error: %v" , pullIndex , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Failed to get PR by index %v Error: %v" , pullIndex , err ) ,
} )
return
}
if pr == nil {
continue
}
results = append ( results , private . HookPostReceiveBranchResult {
Message : setting . Git . PullRequestPushMessage && repo . AllowsPulls ( ) ,
Create : false ,
Branch : "" ,
URL : fmt . Sprintf ( "%s/pulls/%d" , repo . HTMLURL ( ) , pr . Index ) ,
} )
continue
}
2021-09-16 21:34:54 +08:00
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 ,
} )
}