2021-07-28 11:42:56 +02:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 19:20:29 +01:00
// SPDX-License-Identifier: MIT
2021-07-28 11:42:56 +02:00
package agit
import (
2022-06-17 20:17:12 +02:00
"context"
2021-07-28 11:42:56 +02:00
"fmt"
"os"
"strings"
2022-06-13 11:37:59 +02:00
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 10:49:20 +01:00
user_model "code.gitea.io/gitea/models/user"
2021-07-28 11:42:56 +02:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
2023-09-05 20:37:47 +02:00
notify_service "code.gitea.io/gitea/services/notify"
2021-07-28 11:42:56 +02:00
pull_service "code.gitea.io/gitea/services/pull"
)
2022-06-01 05:06:31 +02:00
// ProcReceive handle proc receive work
2022-06-17 20:17:12 +02:00
func ProcReceive ( ctx context . Context , repo * repo_model . Repository , gitRepo * git . Repository , opts * private . HookOptions ) ( [ ] private . HookProcReceiveRefResult , error ) {
2021-08-14 13:17:10 +02:00
results := make ( [ ] private . HookProcReceiveRefResult , 0 , len ( opts . OldCommitIDs ) )
2022-06-17 20:17:12 +02:00
2024-02-19 00:07:24 +01:00
topicBranch := opts . GitPushOptions [ "topic" ]
_ , forcePush := opts . GitPushOptions [ "force-push" ]
2024-02-14 00:35:38 +01:00
title , hasTitle := opts . GitPushOptions [ "title" ]
description , hasDesc := opts . GitPushOptions [ "description" ]
2024-02-24 07:55:19 +01:00
objectFormat := git . ObjectFormatFromName ( repo . ObjectFormatName )
2024-02-19 00:07:24 +01:00
pusher , err := user_model . GetUserByID ( ctx , opts . UserID )
if err != nil {
return nil , fmt . Errorf ( "failed to get user[%d]: %w" , opts . UserID , err )
}
2021-07-28 11:42:56 +02:00
for i := range opts . OldCommitIDs {
2024-02-19 00:07:24 +01:00
// Avoid processing this change if the new commit is empty.
2023-12-17 12:56:08 +01:00
if opts . NewCommitIDs [ i ] == objectFormat . EmptyObjectID ( ) . String ( ) {
2021-08-14 13:17:10 +02:00
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 11:42:56 +02:00
OriginalRef : opts . RefFullNames [ i ] ,
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 00:07:24 +01:00
Err : "Cannot delete a non-existent branch." ,
2021-07-28 11:42:56 +02:00
} )
continue
}
2024-02-19 00:07:24 +01:00
// Only process references that are in the form of refs/for/
2023-05-26 03:04:48 +02:00
if ! opts . RefFullNames [ i ] . IsFor ( ) {
2021-08-14 13:17:10 +02:00
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 11:42:56 +02:00
IsNotMatched : true ,
OriginalRef : opts . RefFullNames [ i ] ,
} )
continue
}
2024-02-19 00:07:24 +01:00
// Get the anything after the refs/for/ prefix.
2023-05-26 03:04:48 +02:00
baseBranchName := opts . RefFullNames [ i ] . ForBranchName ( )
2024-02-19 00:07:24 +01:00
curentTopicBranch := topicBranch
// If the reference was given in the format of refs/for/<target-branch>/<topic-branch>,
// where <target-branch> and <topic-branch> can contain slashes, we need to iteratively
// search for what the target and topic branch is.
2021-07-28 11:42:56 +02:00
if ! gitRepo . IsBranchExist ( baseBranchName ) {
for p , v := range baseBranchName {
if v == '/' && gitRepo . IsBranchExist ( baseBranchName [ : p ] ) && p != len ( baseBranchName ) - 1 {
curentTopicBranch = baseBranchName [ p + 1 : ]
baseBranchName = baseBranchName [ : p ]
break
}
}
}
2024-02-19 00:07:24 +01:00
if len ( curentTopicBranch ) == 0 {
2021-08-14 13:17:10 +02:00
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 11:42:56 +02:00
OriginalRef : opts . RefFullNames [ i ] ,
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 00:07:24 +01:00
Err : "The topic-branch option is not set" ,
2021-07-28 11:42:56 +02:00
} )
continue
}
2024-02-19 00:07:24 +01:00
// Include the user's name in the head branch, to avoid conflicts
// with other users.
headBranch := curentTopicBranch
2021-07-28 11:42:56 +02:00
userName := strings . ToLower ( opts . UserName )
if ! strings . HasPrefix ( curentTopicBranch , userName + "/" ) {
headBranch = userName + "/" + curentTopicBranch
}
2024-02-19 00:07:24 +01:00
// Check if a AGit pull request already exist for this branch.
2022-11-19 09:12:33 +01:00
pr , err := issues_model . GetUnmergedPullRequest ( ctx , repo . ID , repo . ID , headBranch , baseBranchName , issues_model . PullRequestFlowAGit )
2021-07-28 11:42:56 +02:00
if err != nil {
2022-06-13 11:37:59 +02:00
if ! issues_model . IsErrPullRequestNotExist ( err ) {
2024-02-19 00:07:24 +01:00
return nil , fmt . Errorf ( "failed to get unmerged AGit flow pull request in repository %q: %w" , repo . FullName ( ) , err )
2021-07-28 11:42:56 +02:00
}
2024-02-23 21:42:15 +01:00
// Check if the changes are already in the target branch.
stdout , _ , gitErr := git . NewCommand ( ctx , "branch" , "--contains" ) . AddDynamicArguments ( opts . NewCommitIDs [ i ] , baseBranchName ) . RunStdString ( & git . RunOpts { Dir : repo . RepoPath ( ) } )
if gitErr != nil {
return nil , fmt . Errorf ( "failed to check if the target branch already contains the new commit in repository %q: %w" , repo . FullName ( ) , err )
}
if len ( stdout ) > 0 {
results = append ( results , private . HookProcReceiveRefResult {
OriginalRef : opts . RefFullNames [ i ] ,
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
Err : "The target branch already contains this commit" ,
} )
continue
}
2024-02-19 00:07:24 +01:00
// Automatically fill out the title and the description from the first commit.
2024-02-14 00:35:38 +01:00
shouldGetCommit := len ( title ) == 0 || len ( description ) == 0
var commit * git . Commit
if shouldGetCommit {
commit , err = gitRepo . GetCommit ( opts . NewCommitIDs [ i ] )
if err != nil {
2024-02-19 00:07:24 +01:00
return nil , fmt . Errorf ( "failed to get commit %s in repository %q: %w" , opts . NewCommitIDs [ i ] , repo . FullName ( ) , err )
2021-07-28 11:42:56 +02:00
}
2024-02-14 00:35:38 +01:00
}
if ! hasTitle || len ( title ) == 0 {
title = strings . Split ( commit . CommitMessage , "\n" ) [ 0 ]
}
if ! hasDesc || len ( description ) == 0 {
_ , description , _ = strings . Cut ( commit . CommitMessage , "\n\n" )
2021-07-28 11:42:56 +02:00
}
2022-06-13 11:37:59 +02:00
prIssue := & issues_model . Issue {
2021-07-28 11:42:56 +02:00
RepoID : repo . ID ,
Title : title ,
PosterID : pusher . ID ,
Poster : pusher ,
IsPull : true ,
Content : description ,
}
2022-06-13 11:37:59 +02:00
pr := & issues_model . PullRequest {
2021-07-28 11:42:56 +02:00
HeadRepoID : repo . ID ,
BaseRepoID : repo . ID ,
HeadBranch : headBranch ,
HeadCommitID : opts . NewCommitIDs [ i ] ,
BaseBranch : baseBranchName ,
HeadRepo : repo ,
BaseRepo : repo ,
MergeBase : "" ,
2022-06-13 11:37:59 +02:00
Type : issues_model . PullRequestGitea ,
Flow : issues_model . PullRequestFlowAGit ,
2021-07-28 11:42:56 +02:00
}
2022-01-20 00:26:57 +01:00
if err := pull_service . NewPullRequest ( ctx , repo , prIssue , [ ] int64 { } , [ ] string { } , pr , [ ] int64 { } ) ; err != nil {
2024-02-19 00:07:24 +01:00
return nil , fmt . Errorf ( "unable to create new pull request: %w" , err )
2021-07-28 11:42:56 +02:00
}
log . Trace ( "Pull request created: %d/%d" , repo . ID , prIssue . ID )
2021-08-14 13:17:10 +02:00
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 11:42:56 +02:00
Ref : pr . GetGitRefName ( ) ,
OriginalRef : opts . RefFullNames [ i ] ,
2023-12-17 12:56:08 +01:00
OldOID : objectFormat . EmptyObjectID ( ) . String ( ) ,
2021-07-28 11:42:56 +02:00
NewOID : opts . NewCommitIDs [ i ] ,
} )
continue
}
2024-02-19 00:07:24 +01:00
// Update an existing pull request.
2022-11-19 09:12:33 +01:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2024-02-19 00:07:24 +01:00
return nil , fmt . Errorf ( "unable to load base repository for PR[%d]: %w" , pr . ID , err )
2021-07-28 11:42:56 +02:00
}
oldCommitID , err := gitRepo . GetRefCommitID ( pr . GetGitRefName ( ) )
if err != nil {
2024-02-19 00:07:24 +01:00
return nil , fmt . Errorf ( "unable to get commit id of reference[%s] in base repository for PR[%d]: %w" , pr . GetGitRefName ( ) , pr . ID , err )
2021-07-28 11:42:56 +02:00
}
2024-02-19 00:07:24 +01:00
// Do not process this change if nothing was changed.
2021-07-28 11:42:56 +02:00
if oldCommitID == opts . NewCommitIDs [ i ] {
2021-08-14 13:17:10 +02:00
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 11:42:56 +02:00
OriginalRef : opts . RefFullNames [ i ] ,
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 00:07:24 +01:00
Err : "The new commit is the same as the old commit" ,
2021-07-28 11:42:56 +02:00
} )
continue
}
2024-02-19 00:07:24 +01:00
// If the force push option was not set, ensure that this change isn't a force push.
2021-07-28 11:42:56 +02:00
if ! forcePush {
2022-10-23 16:44:45 +02:00
output , _ , err := git . NewCommand ( ctx , "rev-list" , "--max-count=1" ) . AddDynamicArguments ( oldCommitID , "^" + opts . NewCommitIDs [ i ] ) . RunStdString ( & git . RunOpts { Dir : repo . RepoPath ( ) , Env : os . Environ ( ) } )
2021-07-28 11:42:56 +02:00
if err != nil {
2024-02-19 00:07:24 +01:00
return nil , fmt . Errorf ( "failed to detect a force push: %w" , err )
2021-07-28 11:42:56 +02:00
} else if len ( output ) > 0 {
2021-08-14 13:17:10 +02:00
results = append ( results , private . HookProcReceiveRefResult {
2021-09-08 22:20:55 +02:00
OriginalRef : opts . RefFullNames [ i ] ,
2021-07-28 11:42:56 +02:00
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 00:07:24 +01:00
Err : "Updates were rejected because the tip of your current branch is behind its remote counterpart. If this is intentional, set the `force-push` option by adding `-o force-push=true` to your `git push` command." ,
2021-07-28 11:42:56 +02:00
} )
continue
}
}
2024-02-19 00:07:24 +01:00
// Set the new commit as reference of the pull request.
2021-07-28 11:42:56 +02:00
pr . HeadCommitID = opts . NewCommitIDs [ i ]
2022-01-20 00:26:57 +01:00
if err = pull_service . UpdateRef ( ctx , pr ) ; err != nil {
2024-02-19 00:07:24 +01:00
return nil , fmt . Errorf ( "failed to update the reference of the pull request: %w" , err )
2021-07-28 11:42:56 +02:00
}
2024-08-08 12:34:51 +02:00
// TODO: refactor to unify with `pull_service.AddTestPullRequestTask`
2024-02-19 00:07:24 +01:00
// Add the pull request to the merge conflicting checker queue.
2023-07-22 16:14:27 +02:00
pull_service . AddToTaskQueue ( ctx , pr )
2024-02-19 00:07:24 +01:00
if err := pr . LoadIssue ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "failed to load the issue of the pull request: %w" , err )
2021-07-28 11:42:56 +02:00
}
2024-02-19 00:07:24 +01:00
2024-08-08 12:34:51 +02:00
// Validate pull request.
pull_service . ValidatePullRequest ( ctx , pr , oldCommitID , opts . NewCommitIDs [ i ] , pusher )
// TODO: call `InvalidateCodeComments`
2024-02-19 00:07:24 +01:00
// Create and notify about the new commits.
2022-12-10 03:46:31 +01:00
comment , err := pull_service . CreatePushPullComment ( ctx , pusher , pr , oldCommitID , opts . NewCommitIDs [ i ] )
2021-07-28 11:42:56 +02:00
if err == nil && comment != nil {
2023-09-05 20:37:47 +02:00
notify_service . PullRequestPushCommits ( ctx , pusher , pr , comment )
2021-07-28 11:42:56 +02:00
}
2023-09-05 20:37:47 +02:00
notify_service . PullRequestSynchronized ( ctx , pusher , pr )
2024-08-08 12:34:51 +02:00
// this always seems to be false
2021-07-28 11:42:56 +02:00
isForcePush := comment != nil && comment . IsForcePush
2021-08-14 13:17:10 +02:00
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 11:42:56 +02:00
OldOID : oldCommitID ,
NewOID : opts . NewCommitIDs [ i ] ,
Ref : pr . GetGitRefName ( ) ,
OriginalRef : opts . RefFullNames [ i ] ,
IsForcePush : isForcePush ,
} )
}
2022-06-17 20:17:12 +02:00
return results , nil
2021-07-28 11:42:56 +02:00
}
2022-01-10 10:32:37 +01:00
// UserNameChanged handle user name change for agit flow pull
2023-03-14 08:45:21 +01:00
func UserNameChanged ( ctx context . Context , user * user_model . User , newName string ) error {
pulls , err := issues_model . GetAllUnmergedAgitPullRequestByPoster ( ctx , user . ID )
2021-07-28 11:42:56 +02:00
if err != nil {
return err
}
newName = strings . ToLower ( newName )
for _ , pull := range pulls {
pull . HeadBranch = strings . TrimPrefix ( pull . HeadBranch , user . LowerName + "/" )
pull . HeadBranch = newName + "/" + pull . HeadBranch
2023-10-11 06:24:07 +02:00
if err = pull . UpdateCols ( ctx , "head_branch" ) ; err != nil {
2021-07-28 11:42:56 +02:00
return err
}
}
return nil
}