2017-06-15 14:20:39 +03:00
// Copyright 2017 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 integrations
import (
2019-11-10 11:42:51 +03:00
"bytes"
"fmt"
2017-06-15 14:20:39 +03:00
"net/http"
2017-12-04 01:46:01 +03:00
"net/http/httptest"
2019-05-11 18:29:17 +03:00
"net/url"
2019-11-10 11:42:51 +03:00
"os"
2017-06-15 14:20:39 +03:00
"path"
"strings"
"testing"
2019-11-10 11:42:51 +03:00
"time"
2017-06-15 14:20:39 +03:00
2018-01-05 21:56:50 +03:00
"code.gitea.io/gitea/models"
2021-11-16 11:53:21 +03:00
"code.gitea.io/gitea/models/unittest"
2021-11-10 08:13:16 +03:00
"code.gitea.io/gitea/models/webhook"
2019-11-10 11:42:51 +03:00
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
2017-12-16 00:11:02 +03:00
"code.gitea.io/gitea/modules/test"
2019-11-10 11:42:51 +03:00
"code.gitea.io/gitea/services/pull"
2017-12-16 00:11:02 +03:00
2017-06-15 14:20:39 +03:00
"github.com/stretchr/testify/assert"
2019-08-23 19:40:30 +03:00
"github.com/unknwon/i18n"
2017-06-15 14:20:39 +03:00
)
2018-01-05 21:56:50 +03:00
func testPullMerge ( t * testing . T , session * TestSession , user , repo , pullnum string , mergeStyle models . MergeStyle ) * httptest . ResponseRecorder {
2017-06-15 14:20:39 +03:00
req := NewRequest ( t , "GET" , path . Join ( user , repo , "pulls" , pullnum ) )
2017-07-07 22:36:47 +03:00
resp := session . MakeRequest ( t , req , http . StatusOK )
2017-06-15 14:20:39 +03:00
2017-07-07 22:36:47 +03:00
// Click the little green button to create a pull
2017-06-17 19:29:59 +03:00
htmlDoc := NewHTMLParser ( t , resp . Body )
2018-01-05 21:56:50 +03:00
link , exists := htmlDoc . doc . Find ( ".ui.form." + string ( mergeStyle ) + "-fields > form" ) . Attr ( "action" )
2017-06-15 14:20:39 +03:00
assert . True ( t , exists , "The template has changed" )
2017-06-17 07:49:45 +03:00
req = NewRequestWithValues ( t , "POST" , link , map [ string ] string {
"_csrf" : htmlDoc . GetCSRF ( ) ,
2018-01-05 21:56:50 +03:00
"do" : string ( mergeStyle ) ,
2017-06-17 07:49:45 +03:00
} )
2017-07-07 22:36:47 +03:00
resp = session . MakeRequest ( t , req , http . StatusFound )
2017-06-15 14:20:39 +03:00
return resp
}
2017-12-04 01:46:01 +03:00
func testPullCleanUp ( t * testing . T , session * TestSession , user , repo , pullnum string ) * httptest . ResponseRecorder {
2017-06-21 04:00:03 +03:00
req := NewRequest ( t , "GET" , path . Join ( user , repo , "pulls" , pullnum ) )
2017-07-07 22:36:47 +03:00
resp := session . MakeRequest ( t , req , http . StatusOK )
2017-06-21 04:00:03 +03:00
2017-07-07 22:36:47 +03:00
// Click the little green button to create a pull
2017-06-21 04:00:03 +03:00
htmlDoc := NewHTMLParser ( t , resp . Body )
2020-04-11 01:01:41 +03:00
link , exists := htmlDoc . doc . Find ( ".timeline-item .delete-button" ) . Attr ( "data-url" )
2017-06-21 04:00:03 +03:00
assert . True ( t , exists , "The template has changed" )
req = NewRequestWithValues ( t , "POST" , link , map [ string ] string {
"_csrf" : htmlDoc . GetCSRF ( ) ,
} )
2017-07-07 22:36:47 +03:00
resp = session . MakeRequest ( t , req , http . StatusOK )
2017-06-21 04:00:03 +03:00
return resp
}
2017-06-15 14:20:39 +03:00
func TestPullMerge ( t * testing . T ) {
2019-05-11 18:29:17 +03:00
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
2021-11-10 08:13:16 +03:00
hookTasks , err := webhook . HookTasks ( 1 , 1 ) //Retrieve previous hook number
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
hookTasksLenBefore := len ( hookTasks )
2019-05-11 18:29:17 +03:00
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
testEditFile ( t , session , "user1" , "repo1" , "master" , "README.md" , "Hello, World (Edited)\n" )
2017-06-15 14:20:39 +03:00
2019-05-11 18:29:17 +03:00
resp := testPullCreate ( t , session , "user1" , "repo1" , "master" , "This is a pull title" )
2017-06-15 14:20:39 +03:00
2019-05-11 18:29:17 +03:00
elem := strings . Split ( test . RedirectURL ( resp ) , "/" )
assert . EqualValues ( t , "pulls" , elem [ 3 ] )
testPullMerge ( t , session , elem [ 1 ] , elem [ 2 ] , elem [ 4 ] , models . MergeStyleMerge )
2019-08-11 23:31:18 +03:00
2021-11-10 08:13:16 +03:00
hookTasks , err = webhook . HookTasks ( 1 , 1 )
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
assert . Len ( t , hookTasks , hookTasksLenBefore + 1 )
2019-05-11 18:29:17 +03:00
} )
2018-01-05 21:56:50 +03:00
}
func TestPullRebase ( t * testing . T ) {
2019-05-11 18:29:17 +03:00
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
2021-11-10 08:13:16 +03:00
hookTasks , err := webhook . HookTasks ( 1 , 1 ) //Retrieve previous hook number
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
hookTasksLenBefore := len ( hookTasks )
2019-05-11 18:29:17 +03:00
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
testEditFile ( t , session , "user1" , "repo1" , "master" , "README.md" , "Hello, World (Edited)\n" )
2018-01-05 21:56:50 +03:00
2019-05-11 18:29:17 +03:00
resp := testPullCreate ( t , session , "user1" , "repo1" , "master" , "This is a pull title" )
2018-01-05 21:56:50 +03:00
2019-05-11 18:29:17 +03:00
elem := strings . Split ( test . RedirectURL ( resp ) , "/" )
assert . EqualValues ( t , "pulls" , elem [ 3 ] )
testPullMerge ( t , session , elem [ 1 ] , elem [ 2 ] , elem [ 4 ] , models . MergeStyleRebase )
2019-08-11 23:31:18 +03:00
2021-11-10 08:13:16 +03:00
hookTasks , err = webhook . HookTasks ( 1 , 1 )
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
assert . Len ( t , hookTasks , hookTasksLenBefore + 1 )
2019-05-11 18:29:17 +03:00
} )
2018-01-05 21:56:50 +03:00
}
2018-12-27 13:27:08 +03:00
func TestPullRebaseMerge ( t * testing . T ) {
2019-05-11 18:29:17 +03:00
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
2021-11-10 08:13:16 +03:00
hookTasks , err := webhook . HookTasks ( 1 , 1 ) //Retrieve previous hook number
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
hookTasksLenBefore := len ( hookTasks )
2019-05-11 18:29:17 +03:00
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
testEditFile ( t , session , "user1" , "repo1" , "master" , "README.md" , "Hello, World (Edited)\n" )
2018-12-27 13:27:08 +03:00
2019-05-11 18:29:17 +03:00
resp := testPullCreate ( t , session , "user1" , "repo1" , "master" , "This is a pull title" )
2018-12-27 13:27:08 +03:00
2019-05-11 18:29:17 +03:00
elem := strings . Split ( test . RedirectURL ( resp ) , "/" )
assert . EqualValues ( t , "pulls" , elem [ 3 ] )
testPullMerge ( t , session , elem [ 1 ] , elem [ 2 ] , elem [ 4 ] , models . MergeStyleRebaseMerge )
2019-08-11 23:31:18 +03:00
2021-11-10 08:13:16 +03:00
hookTasks , err = webhook . HookTasks ( 1 , 1 )
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
assert . Len ( t , hookTasks , hookTasksLenBefore + 1 )
2019-05-11 18:29:17 +03:00
} )
2018-12-27 13:27:08 +03:00
}
2018-01-05 21:56:50 +03:00
func TestPullSquash ( t * testing . T ) {
2019-05-11 18:29:17 +03:00
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
2021-11-10 08:13:16 +03:00
hookTasks , err := webhook . HookTasks ( 1 , 1 ) //Retrieve previous hook number
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
hookTasksLenBefore := len ( hookTasks )
2019-05-11 18:29:17 +03:00
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
testEditFile ( t , session , "user1" , "repo1" , "master" , "README.md" , "Hello, World (Edited)\n" )
testEditFile ( t , session , "user1" , "repo1" , "master" , "README.md" , "Hello, World (Edited!)\n" )
resp := testPullCreate ( t , session , "user1" , "repo1" , "master" , "This is a pull title" )
elem := strings . Split ( test . RedirectURL ( resp ) , "/" )
assert . EqualValues ( t , "pulls" , elem [ 3 ] )
testPullMerge ( t , session , elem [ 1 ] , elem [ 2 ] , elem [ 4 ] , models . MergeStyleSquash )
2019-08-11 23:31:18 +03:00
2021-11-10 08:13:16 +03:00
hookTasks , err = webhook . HookTasks ( 1 , 1 )
2019-08-11 23:31:18 +03:00
assert . NoError ( t , err )
assert . Len ( t , hookTasks , hookTasksLenBefore + 1 )
2019-05-11 18:29:17 +03:00
} )
2017-06-15 14:20:39 +03:00
}
2017-06-21 04:00:03 +03:00
func TestPullCleanUpAfterMerge ( t * testing . T ) {
2019-05-11 18:29:17 +03:00
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
2020-02-10 02:09:31 +03:00
testEditFileToNewBranch ( t , session , "user1" , "repo1" , "master" , "feature/test" , "README.md" , "Hello, World (Edited - TestPullCleanUpAfterMerge)\n" )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
resp := testPullCreate ( t , session , "user1" , "repo1" , "feature/test" , "This is a pull title" )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
elem := strings . Split ( test . RedirectURL ( resp ) , "/" )
assert . EqualValues ( t , "pulls" , elem [ 3 ] )
testPullMerge ( t , session , elem [ 1 ] , elem [ 2 ] , elem [ 4 ] , models . MergeStyleMerge )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
// Check PR branch deletion
resp = testPullCleanUp ( t , session , elem [ 1 ] , elem [ 2 ] , elem [ 4 ] )
respJSON := struct {
Redirect string
} { }
DecodeJSON ( t , resp , & respJSON )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
assert . NotEmpty ( t , respJSON . Redirect , "Redirected URL is not found" )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
elem = strings . Split ( respJSON . Redirect , "/" )
assert . EqualValues ( t , "pulls" , elem [ 3 ] )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
// Check branch deletion result
req := NewRequest ( t , "GET" , respJSON . Redirect )
resp = session . MakeRequest ( t , req , http . StatusOK )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
htmlDoc := NewHTMLParser ( t , resp . Body )
resultMsg := htmlDoc . doc . Find ( ".ui.message>p" ) . Text ( )
2017-06-21 04:00:03 +03:00
2019-05-11 18:29:17 +03:00
assert . EqualValues ( t , "Branch 'user1/feature/test' has been deleted." , resultMsg )
} )
2017-06-21 04:00:03 +03:00
}
2018-08-13 22:04:39 +03:00
func TestCantMergeWorkInProgress ( t * testing . T ) {
2019-05-11 18:29:17 +03:00
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
testEditFile ( t , session , "user1" , "repo1" , "master" , "README.md" , "Hello, World (Edited)\n" )
resp := testPullCreate ( t , session , "user1" , "repo1" , "master" , "[wip] This is a pull title" )
req := NewRequest ( t , "GET" , resp . Header ( ) . Get ( "Location" ) )
resp = session . MakeRequest ( t , req , http . StatusOK )
htmlDoc := NewHTMLParser ( t , resp . Body )
2020-11-15 23:58:16 +03:00
text := strings . TrimSpace ( htmlDoc . doc . Find ( ".merge-section > .item" ) . Last ( ) . Text ( ) )
2019-05-11 18:29:17 +03:00
assert . NotEmpty ( t , text , "Can't find WIP text" )
2021-05-27 23:02:04 +03:00
assert . Contains ( t , text , i18n . Tr ( "en" , "repo.pulls.cannot_merge_work_in_progress" ) , "Unable to find WIP text" )
assert . Contains ( t , text , "[wip]" , "Unable to find WIP text" )
2019-05-11 18:29:17 +03:00
} )
2018-08-13 22:04:39 +03:00
}
2019-11-10 11:42:51 +03:00
func TestCantMergeConflict ( t * testing . T ) {
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
testEditFileToNewBranch ( t , session , "user1" , "repo1" , "master" , "conflict" , "README.md" , "Hello, World (Edited Once)\n" )
testEditFileToNewBranch ( t , session , "user1" , "repo1" , "master" , "base" , "README.md" , "Hello, World (Edited Twice)\n" )
// Use API to create a conflicting pr
token := getTokenForLoggedInUser ( t , session )
req := NewRequestWithJSON ( t , http . MethodPost , fmt . Sprintf ( "/api/v1/repos/%s/%s/pulls?token=%s" , "user1" , "repo1" , token ) , & api . CreatePullRequestOption {
Head : "conflict" ,
Base : "base" ,
Title : "create a conflicting pr" ,
} )
session . MakeRequest ( t , req , 201 )
// Now this PR will be marked conflict - or at least a race will do - so drop down to pure code at this point...
2021-11-16 11:53:21 +03:00
user1 := unittest . AssertExistsAndLoadBean ( t , & models . User {
2019-11-10 11:42:51 +03:00
Name : "user1" ,
} ) . ( * models . User )
2021-11-16 11:53:21 +03:00
repo1 := unittest . AssertExistsAndLoadBean ( t , & models . Repository {
2019-11-10 11:42:51 +03:00
OwnerID : user1 . ID ,
Name : "repo1" ,
} ) . ( * models . Repository )
2021-11-16 11:53:21 +03:00
pr := unittest . AssertExistsAndLoadBean ( t , & models . PullRequest {
2019-11-10 11:42:51 +03:00
HeadRepoID : repo1 . ID ,
BaseRepoID : repo1 . ID ,
HeadBranch : "conflict" ,
BaseBranch : "base" ,
} ) . ( * models . PullRequest )
gitRepo , err := git . OpenRepository ( models . RepoPath ( user1 . Name , repo1 . Name ) )
assert . NoError ( t , err )
err = pull . Merge ( pr , user1 , gitRepo , models . MergeStyleMerge , "CONFLICT" )
assert . Error ( t , err , "Merge should return an error due to conflict" )
assert . True ( t , models . IsErrMergeConflicts ( err ) , "Merge error is not a conflict error" )
err = pull . Merge ( pr , user1 , gitRepo , models . MergeStyleRebase , "CONFLICT" )
assert . Error ( t , err , "Merge should return an error due to conflict" )
assert . True ( t , models . IsErrRebaseConflicts ( err ) , "Merge error is not a conflict error" )
2021-01-06 22:23:57 +03:00
gitRepo . Close ( )
2019-11-10 11:42:51 +03:00
} )
}
func TestCantMergeUnrelated ( t * testing . T ) {
onGiteaRun ( t , func ( t * testing . T , giteaURL * url . URL ) {
session := loginUser ( t , "user1" )
testRepoFork ( t , session , "user2" , "repo1" , "user1" , "repo1" )
testEditFileToNewBranch ( t , session , "user1" , "repo1" , "master" , "base" , "README.md" , "Hello, World (Edited Twice)\n" )
// Now we want to create a commit on a branch that is totally unrelated to our current head
// Drop down to pure code at this point
2021-11-16 11:53:21 +03:00
user1 := unittest . AssertExistsAndLoadBean ( t , & models . User {
2019-11-10 11:42:51 +03:00
Name : "user1" ,
} ) . ( * models . User )
2021-11-16 11:53:21 +03:00
repo1 := unittest . AssertExistsAndLoadBean ( t , & models . Repository {
2019-11-10 11:42:51 +03:00
OwnerID : user1 . ID ,
Name : "repo1" ,
} ) . ( * models . Repository )
path := models . RepoPath ( user1 . Name , repo1 . Name )
_ , err := git . NewCommand ( "read-tree" , "--empty" ) . RunInDir ( path )
assert . NoError ( t , err )
stdin := bytes . NewBufferString ( "Unrelated File" )
var stdout strings . Builder
err = git . NewCommand ( "hash-object" , "-w" , "--stdin" ) . RunInDirFullPipeline ( path , & stdout , nil , stdin )
assert . NoError ( t , err )
sha := strings . TrimSpace ( stdout . String ( ) )
_ , err = git . NewCommand ( "update-index" , "--add" , "--replace" , "--cacheinfo" , "100644" , sha , "somewher-over-the-rainbow" ) . RunInDir ( path )
assert . NoError ( t , err )
treeSha , err := git . NewCommand ( "write-tree" ) . RunInDir ( path )
assert . NoError ( t , err )
treeSha = strings . TrimSpace ( treeSha )
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
doerSig := user1 . NewGitSig ( )
env := append ( os . Environ ( ) ,
"GIT_AUTHOR_NAME=" + doerSig . Name ,
"GIT_AUTHOR_EMAIL=" + doerSig . Email ,
"GIT_AUTHOR_DATE=" + commitTimeStr ,
"GIT_COMMITTER_NAME=" + doerSig . Name ,
"GIT_COMMITTER_EMAIL=" + doerSig . Email ,
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
messageBytes := new ( bytes . Buffer )
_ , _ = messageBytes . WriteString ( "Unrelated" )
_ , _ = messageBytes . WriteString ( "\n" )
stdout . Reset ( )
err = git . NewCommand ( "commit-tree" , treeSha ) . RunInDirTimeoutEnvFullPipeline ( env , - 1 , path , & stdout , nil , messageBytes )
assert . NoError ( t , err )
commitSha := strings . TrimSpace ( stdout . String ( ) )
_ , err = git . NewCommand ( "branch" , "unrelated" , commitSha ) . RunInDir ( path )
assert . NoError ( t , err )
testEditFileToNewBranch ( t , session , "user1" , "repo1" , "master" , "conflict" , "README.md" , "Hello, World (Edited Once)\n" )
// Use API to create a conflicting pr
token := getTokenForLoggedInUser ( t , session )
req := NewRequestWithJSON ( t , http . MethodPost , fmt . Sprintf ( "/api/v1/repos/%s/%s/pulls?token=%s" , "user1" , "repo1" , token ) , & api . CreatePullRequestOption {
Head : "unrelated" ,
Base : "base" ,
Title : "create an unrelated pr" ,
} )
session . MakeRequest ( t , req , 201 )
// Now this PR could be marked conflict - or at least a race may occur - so drop down to pure code at this point...
gitRepo , err := git . OpenRepository ( path )
assert . NoError ( t , err )
2021-11-16 11:53:21 +03:00
pr := unittest . AssertExistsAndLoadBean ( t , & models . PullRequest {
2019-11-10 11:42:51 +03:00
HeadRepoID : repo1 . ID ,
BaseRepoID : repo1 . ID ,
HeadBranch : "unrelated" ,
BaseBranch : "base" ,
} ) . ( * models . PullRequest )
err = pull . Merge ( pr , user1 , gitRepo , models . MergeStyleMerge , "UNRELATED" )
assert . Error ( t , err , "Merge should return an error due to unrelated" )
assert . True ( t , models . IsErrMergeUnrelatedHistories ( err ) , "Merge error is not a unrelated histories error" )
2021-01-06 22:23:57 +03:00
gitRepo . Close ( )
2019-11-10 11:42:51 +03:00
} )
}