2021-07-28 15:12:56 +05:30
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 23:50:29 +05:30
// SPDX-License-Identifier: MIT
2021-07-28 15:12:56 +05:30
package agit
import (
2022-06-17 23:47:12 +05:30
"context"
2021-07-28 15:12:56 +05:30
"fmt"
"os"
"strings"
2022-06-13 15:07:59 +05:30
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 15:19:20 +05:30
user_model "code.gitea.io/gitea/models/user"
2021-07-28 15:12:56 +05:30
"code.gitea.io/gitea/modules/git"
2024-06-28 04:07:39 +05:30
"code.gitea.io/gitea/modules/git/pushoptions"
2021-07-28 15:12:56 +05:30
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
2023-09-06 00:07:47 +05:30
notify_service "code.gitea.io/gitea/services/notify"
2021-07-28 15:12:56 +05:30
pull_service "code.gitea.io/gitea/services/pull"
)
2022-06-01 08:36:31 +05:30
// ProcReceive handle proc receive work
2022-06-17 23:47:12 +05:30
func ProcReceive ( ctx context . Context , repo * repo_model . Repository , gitRepo * git . Repository , opts * private . HookOptions ) ( [ ] private . HookProcReceiveRefResult , error ) {
2021-08-14 16:47:10 +05:30
results := make ( [ ] private . HookProcReceiveRefResult , 0 , len ( opts . OldCommitIDs ) )
2022-06-17 23:47:12 +05:30
2024-06-28 04:07:39 +05:30
topicBranch , _ := opts . GetGitPushOptions ( ) . GetString ( pushoptions . AgitTopic )
_ , forcePush := opts . GetGitPushOptions ( ) . GetString ( pushoptions . AgitForcePush )
title , hasTitle := opts . GetGitPushOptions ( ) . GetString ( pushoptions . AgitTitle )
description , hasDesc := opts . GetGitPushOptions ( ) . GetString ( pushoptions . AgitDescription )
2024-02-14 05:05:38 +05:30
2024-02-24 12:25:19 +05:30
objectFormat := git . ObjectFormatFromName ( repo . ObjectFormatName )
2024-02-19 04:37:24 +05:30
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 15:12:56 +05:30
for i := range opts . OldCommitIDs {
2024-02-19 04:37:24 +05:30
// Avoid processing this change if the new commit is empty.
2023-12-17 17:26:08 +05:30
if opts . NewCommitIDs [ i ] == objectFormat . EmptyObjectID ( ) . String ( ) {
2021-08-14 16:47:10 +05:30
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 15:12:56 +05:30
OriginalRef : opts . RefFullNames [ i ] ,
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 04:37:24 +05:30
Err : "Cannot delete a non-existent branch." ,
2021-07-28 15:12:56 +05:30
} )
continue
}
2024-02-19 04:37:24 +05:30
// Only process references that are in the form of refs/for/
2023-05-26 06:34:48 +05:30
if ! opts . RefFullNames [ i ] . IsFor ( ) {
2021-08-14 16:47:10 +05:30
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 15:12:56 +05:30
IsNotMatched : true ,
OriginalRef : opts . RefFullNames [ i ] ,
} )
continue
}
2024-02-19 04:37:24 +05:30
// Get the anything after the refs/for/ prefix.
2023-05-26 06:34:48 +05:30
baseBranchName := opts . RefFullNames [ i ] . ForBranchName ( )
2024-02-19 04:37:24 +05:30
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 15:12:56 +05:30
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 04:37:24 +05:30
if len ( curentTopicBranch ) == 0 {
2021-08-14 16:47:10 +05:30
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 15:12:56 +05:30
OriginalRef : opts . RefFullNames [ i ] ,
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 04:37:24 +05:30
Err : "The topic-branch option is not set" ,
2021-07-28 15:12:56 +05:30
} )
continue
}
2024-02-19 04:37:24 +05:30
// Include the user's name in the head branch, to avoid conflicts
// with other users.
headBranch := curentTopicBranch
2021-07-28 15:12:56 +05:30
userName := strings . ToLower ( opts . UserName )
if ! strings . HasPrefix ( curentTopicBranch , userName + "/" ) {
headBranch = userName + "/" + curentTopicBranch
}
2024-02-19 04:37:24 +05:30
// Check if a AGit pull request already exist for this branch.
2022-11-19 13:42:33 +05:30
pr , err := issues_model . GetUnmergedPullRequest ( ctx , repo . ID , repo . ID , headBranch , baseBranchName , issues_model . PullRequestFlowAGit )
2021-07-28 15:12:56 +05:30
if err != nil {
2022-06-13 15:07:59 +05:30
if ! issues_model . IsErrPullRequestNotExist ( err ) {
2024-02-19 04:37:24 +05:30
return nil , fmt . Errorf ( "failed to get unmerged AGit flow pull request in repository %q: %w" , repo . FullName ( ) , err )
2021-07-28 15:12:56 +05:30
}
2024-02-24 02:12:15 +05:30
// 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 04:37:24 +05:30
// Automatically fill out the title and the description from the first commit.
2024-02-14 05:05:38 +05:30
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 04:37:24 +05:30
return nil , fmt . Errorf ( "failed to get commit %s in repository %q: %w" , opts . NewCommitIDs [ i ] , repo . FullName ( ) , err )
2021-07-28 15:12:56 +05:30
}
2024-02-14 05:05:38 +05:30
}
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 15:12:56 +05:30
}
2022-06-13 15:07:59 +05:30
prIssue := & issues_model . Issue {
2021-07-28 15:12:56 +05:30
RepoID : repo . ID ,
Title : title ,
PosterID : pusher . ID ,
Poster : pusher ,
IsPull : true ,
Content : description ,
}
2022-06-13 15:07:59 +05:30
pr := & issues_model . PullRequest {
2021-07-28 15:12:56 +05:30
HeadRepoID : repo . ID ,
BaseRepoID : repo . ID ,
HeadBranch : headBranch ,
HeadCommitID : opts . NewCommitIDs [ i ] ,
BaseBranch : baseBranchName ,
HeadRepo : repo ,
BaseRepo : repo ,
MergeBase : "" ,
2022-06-13 15:07:59 +05:30
Type : issues_model . PullRequestGitea ,
Flow : issues_model . PullRequestFlowAGit ,
2021-07-28 15:12:56 +05:30
}
2022-01-20 04:56:57 +05:30
if err := pull_service . NewPullRequest ( ctx , repo , prIssue , [ ] int64 { } , [ ] string { } , pr , [ ] int64 { } ) ; err != nil {
2024-02-19 04:37:24 +05:30
return nil , fmt . Errorf ( "unable to create new pull request: %w" , err )
2021-07-28 15:12:56 +05:30
}
log . Trace ( "Pull request created: %d/%d" , repo . ID , prIssue . ID )
2021-08-14 16:47:10 +05:30
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 15:12:56 +05:30
Ref : pr . GetGitRefName ( ) ,
OriginalRef : opts . RefFullNames [ i ] ,
2023-12-17 17:26:08 +05:30
OldOID : objectFormat . EmptyObjectID ( ) . String ( ) ,
2021-07-28 15:12:56 +05:30
NewOID : opts . NewCommitIDs [ i ] ,
} )
continue
}
2024-02-19 04:37:24 +05:30
// Update an existing pull request.
2022-11-19 13:42:33 +05:30
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2024-02-19 04:37:24 +05:30
return nil , fmt . Errorf ( "unable to load base repository for PR[%d]: %w" , pr . ID , err )
2021-07-28 15:12:56 +05:30
}
oldCommitID , err := gitRepo . GetRefCommitID ( pr . GetGitRefName ( ) )
if err != nil {
2024-02-19 04:37:24 +05:30
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 15:12:56 +05:30
}
2024-02-19 04:37:24 +05:30
// Do not process this change if nothing was changed.
2021-07-28 15:12:56 +05:30
if oldCommitID == opts . NewCommitIDs [ i ] {
2021-08-14 16:47:10 +05:30
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 15:12:56 +05:30
OriginalRef : opts . RefFullNames [ i ] ,
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 04:37:24 +05:30
Err : "The new commit is the same as the old commit" ,
2021-07-28 15:12:56 +05:30
} )
continue
}
2024-02-19 04:37:24 +05:30
// If the force push option was not set, ensure that this change isn't a force push.
2021-07-28 15:12:56 +05:30
if ! forcePush {
2022-10-23 20:14:45 +05:30
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 15:12:56 +05:30
if err != nil {
2024-02-19 04:37:24 +05:30
return nil , fmt . Errorf ( "failed to detect a force push: %w" , err )
2021-07-28 15:12:56 +05:30
} else if len ( output ) > 0 {
2021-08-14 16:47:10 +05:30
results = append ( results , private . HookProcReceiveRefResult {
2021-09-09 01:50:55 +05:30
OriginalRef : opts . RefFullNames [ i ] ,
2021-07-28 15:12:56 +05:30
OldOID : opts . OldCommitIDs [ i ] ,
NewOID : opts . NewCommitIDs [ i ] ,
2024-02-19 04:37:24 +05:30
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 15:12:56 +05:30
} )
continue
}
}
2024-02-19 04:37:24 +05:30
// Set the new commit as reference of the pull request.
2021-07-28 15:12:56 +05:30
pr . HeadCommitID = opts . NewCommitIDs [ i ]
2022-01-20 04:56:57 +05:30
if err = pull_service . UpdateRef ( ctx , pr ) ; err != nil {
2024-02-19 04:37:24 +05:30
return nil , fmt . Errorf ( "failed to update the reference of the pull request: %w" , err )
2021-07-28 15:12:56 +05:30
}
2024-02-19 04:37:24 +05:30
// Add the pull request to the merge conflicting checker queue.
2023-07-22 19:44:27 +05:30
pull_service . AddToTaskQueue ( ctx , pr )
2024-02-19 04:37:24 +05:30
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 15:12:56 +05:30
}
2024-02-19 04:37:24 +05:30
// Create and notify about the new commits.
2022-12-10 08:16:31 +05:30
comment , err := pull_service . CreatePushPullComment ( ctx , pusher , pr , oldCommitID , opts . NewCommitIDs [ i ] )
2021-07-28 15:12:56 +05:30
if err == nil && comment != nil {
2023-09-06 00:07:47 +05:30
notify_service . PullRequestPushCommits ( ctx , pusher , pr , comment )
2021-07-28 15:12:56 +05:30
}
2023-09-06 00:07:47 +05:30
notify_service . PullRequestSynchronized ( ctx , pusher , pr )
2021-07-28 15:12:56 +05:30
isForcePush := comment != nil && comment . IsForcePush
2021-08-14 16:47:10 +05:30
results = append ( results , private . HookProcReceiveRefResult {
2021-07-28 15:12:56 +05:30
OldOID : oldCommitID ,
NewOID : opts . NewCommitIDs [ i ] ,
Ref : pr . GetGitRefName ( ) ,
OriginalRef : opts . RefFullNames [ i ] ,
IsForcePush : isForcePush ,
} )
}
2022-06-17 23:47:12 +05:30
return results , nil
2021-07-28 15:12:56 +05:30
}
2022-01-10 15:02:37 +05:30
// UserNameChanged handle user name change for agit flow pull
2023-03-14 13:15:21 +05:30
func UserNameChanged ( ctx context . Context , user * user_model . User , newName string ) error {
pulls , err := issues_model . GetAllUnmergedAgitPullRequestByPoster ( ctx , user . ID )
2021-07-28 15:12:56 +05:30
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 09:54:07 +05:30
if err = pull . UpdateCols ( ctx , "head_branch" ) ; err != nil {
2021-07-28 15:12:56 +05:30
return err
}
}
return nil
}