@ -5,10 +5,14 @@
package pull
import (
"bufio"
"bytes"
"context"
"fmt"
"os"
"path"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
@ -16,6 +20,8 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
issue_service "code.gitea.io/gitea/services/issue"
"github.com/unknwon/com"
)
// NewPullRequest creates new pull request with labels for repository.
@ -168,7 +174,7 @@ func addHeadRepoTasks(prs []*models.PullRequest) {
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
// and generate new patch for testing as needed.
func AddTestPullRequestTask ( doer * models . User , repoID int64 , branch string , isSync bool ) {
func AddTestPullRequestTask ( doer * models . User , repoID int64 , branch string , isSync bool , oldCommitID , newCommitID string ) {
log . Trace ( "AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests" , repoID , branch )
graceful . GetManager ( ) . RunWithShutdownContext ( func ( ctx context . Context ) {
// There is no sensible way to shut this down ":-("
@ -191,6 +197,22 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy
}
if err == nil {
for _ , pr := range prs {
if newCommitID != "" && newCommitID != git . EmptySHA {
changed , err := checkIfPRContentChanged ( pr , oldCommitID , newCommitID )
if err != nil {
log . Error ( "checkIfPRContentChanged: %v" , err )
}
if changed {
// Mark old reviews as stale if diff to mergebase has changed
if err := models . MarkReviewsAsStale ( pr . IssueID ) ; err != nil {
log . Error ( "MarkReviewsAsStale: %v" , err )
}
}
if err := models . MarkReviewsAsNotStale ( pr . IssueID , newCommitID ) ; err != nil {
log . Error ( "MarkReviewsAsNotStale: %v" , err )
}
}
pr . Issue . PullRequest = pr
notification . NotifyPullRequestSynchronized ( doer , pr )
}
@ -211,6 +233,78 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy
} )
}
// checkIfPRContentChanged checks if diff to target branch has changed by push
// A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
func checkIfPRContentChanged ( pr * models . PullRequest , oldCommitID , newCommitID string ) ( hasChanged bool , err error ) {
if err = pr . GetHeadRepo ( ) ; err != nil {
return false , fmt . Errorf ( "GetHeadRepo: %v" , err )
} else if pr . HeadRepo == nil {
// corrupt data assumed changed
return true , nil
}
if err = pr . GetBaseRepo ( ) ; err != nil {
return false , fmt . Errorf ( "GetBaseRepo: %v" , err )
}
headGitRepo , err := git . OpenRepository ( pr . HeadRepo . RepoPath ( ) )
if err != nil {
return false , fmt . Errorf ( "OpenRepository: %v" , err )
}
defer headGitRepo . Close ( )
// Add a temporary remote.
tmpRemote := "checkIfPRContentChanged-" + com . ToStr ( time . Now ( ) . UnixNano ( ) )
if err = headGitRepo . AddRemote ( tmpRemote , models . RepoPath ( pr . BaseRepo . MustOwner ( ) . Name , pr . BaseRepo . Name ) , true ) ; err != nil {
return false , fmt . Errorf ( "AddRemote: %s/%s-%s: %v" , pr . HeadRepo . OwnerName , pr . HeadRepo . Name , tmpRemote , err )
}
defer func ( ) {
if err := headGitRepo . RemoveRemote ( tmpRemote ) ; err != nil {
log . Error ( "checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v" , pr . HeadRepo . OwnerName , pr . HeadRepo . Name , tmpRemote , err )
}
} ( )
// To synchronize repo and get a base ref
_ , base , err := headGitRepo . GetMergeBase ( tmpRemote , pr . BaseBranch , pr . HeadBranch )
if err != nil {
return false , fmt . Errorf ( "GetMergeBase: %v" , err )
}
diffBefore := & bytes . Buffer { }
diffAfter := & bytes . Buffer { }
if err := headGitRepo . GetDiffFromMergeBase ( base , oldCommitID , diffBefore ) ; err != nil {
// If old commit not found, assume changed.
log . Debug ( "GetDiffFromMergeBase: %v" , err )
return true , nil
}
if err := headGitRepo . GetDiffFromMergeBase ( base , newCommitID , diffAfter ) ; err != nil {
// New commit should be found
return false , fmt . Errorf ( "GetDiffFromMergeBase: %v" , err )
}
diffBeforeLines := bufio . NewScanner ( diffBefore )
diffAfterLines := bufio . NewScanner ( diffAfter )
for diffBeforeLines . Scan ( ) && diffAfterLines . Scan ( ) {
if strings . HasPrefix ( diffBeforeLines . Text ( ) , "index" ) && strings . HasPrefix ( diffAfterLines . Text ( ) , "index" ) {
// file hashes can change without the diff changing
continue
} else if strings . HasPrefix ( diffBeforeLines . Text ( ) , "@@" ) && strings . HasPrefix ( diffAfterLines . Text ( ) , "@@" ) {
// the location of the difference may change
continue
} else if ! bytes . Equal ( diffBeforeLines . Bytes ( ) , diffAfterLines . Bytes ( ) ) {
return true , nil
}
}
if diffBeforeLines . Scan ( ) || diffAfterLines . Scan ( ) {
// Diffs not of equal length
return true , nil
}
return false , nil
}
// PushToBaseRepo pushes commits from branches of head repository to
// corresponding branches of base repository.
// FIXME: Only push branches that are actually updates?