Skip to content

Commit 3628e2b

Browse files
committed
Improve checkIfPRContentChanged
The code for checking if a commit has caused a change in a PR is extremely inefficient and affects the head repository instead of using a temporary repository. This PR therefore makes several significant improvements: * A temporary repo like that used in merging. * The diff code is then significant improved to use a three-way diff instead of comparing diffs (possibly binary) line-by-line - in memory... Ref go-gitea#22578 Signed-off-by: Andrew Thornton <[email protected]>
1 parent 4f8c0eb commit 3628e2b

File tree

1 file changed

+28
-56
lines changed

1 file changed

+28
-56
lines changed

services/pull/pull.go

+28-56
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44
package pull
55

66
import (
7-
"bufio"
8-
"bytes"
97
"context"
108
"fmt"
119
"io"
1210
"regexp"
1311
"strings"
14-
"time"
1512

1613
"code.gitea.io/gitea/models"
1714
"code.gitea.io/gitea/models/db"
@@ -351,72 +348,47 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
351348
// checkIfPRContentChanged checks if diff to target branch has changed by push
352349
// A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
353350
func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
354-
if err = pr.LoadHeadRepo(ctx); err != nil {
355-
return false, fmt.Errorf("LoadHeadRepo: %w", err)
356-
} else if pr.HeadRepo == nil {
357-
// corrupt data assumed changed
358-
return true, nil
359-
}
360-
361-
if err = pr.LoadBaseRepo(ctx); err != nil {
362-
return false, fmt.Errorf("LoadBaseRepo: %w", err)
363-
}
364-
365-
headGitRepo, err := git.OpenRepository(ctx, pr.HeadRepo.RepoPath())
351+
tmpBasePath, err := createTemporaryRepo(ctx, pr)
366352
if err != nil {
367-
return false, fmt.Errorf("OpenRepository: %w", err)
368-
}
369-
defer headGitRepo.Close()
370-
371-
// Add a temporary remote.
372-
tmpRemote := "checkIfPRContentChanged-" + fmt.Sprint(time.Now().UnixNano())
373-
if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil {
374-
return false, fmt.Errorf("AddRemote: %s/%s-%s: %w", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
353+
log.Error("CreateTemporaryPath: %v", err)
354+
return false, err
375355
}
376356
defer func() {
377-
if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
378-
log.Error("checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
357+
if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil {
358+
log.Error("checkIfPRContentChanged: RemoveTemporaryPath: %s", err)
379359
}
380360
}()
381-
// To synchronize repo and get a base ref
382-
_, base, err := headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
361+
362+
tmpRepo, err := git.OpenRepository(ctx, tmpBasePath)
383363
if err != nil {
384-
return false, fmt.Errorf("GetMergeBase: %w", err)
364+
return false, fmt.Errorf("OpenRepository: %w", err)
385365
}
366+
defer tmpRepo.Close()
386367

387-
diffBefore := &bytes.Buffer{}
388-
diffAfter := &bytes.Buffer{}
389-
if err := headGitRepo.GetDiffFromMergeBase(base, oldCommitID, diffBefore); err != nil {
390-
// If old commit not found, assume changed.
391-
log.Debug("GetDiffFromMergeBase: %v", err)
392-
return true, nil
393-
}
394-
if err := headGitRepo.GetDiffFromMergeBase(base, newCommitID, diffAfter); err != nil {
395-
// New commit should be found
396-
return false, fmt.Errorf("GetDiffFromMergeBase: %w", err)
368+
// Find the merge-base
369+
_, base, err := tmpRepo.GetMergeBase("", "base", "tracking")
370+
if err != nil {
371+
return false, fmt.Errorf("GetMergeBase: %w", err)
397372
}
398373

399-
diffBeforeLines := bufio.NewScanner(diffBefore)
400-
diffAfterLines := bufio.NewScanner(diffAfter)
401-
402-
for diffBeforeLines.Scan() && diffAfterLines.Scan() {
403-
if strings.HasPrefix(diffBeforeLines.Text(), "index") && strings.HasPrefix(diffAfterLines.Text(), "index") {
404-
// file hashes can change without the diff changing
405-
continue
406-
} else if strings.HasPrefix(diffBeforeLines.Text(), "@@") && strings.HasPrefix(diffAfterLines.Text(), "@@") {
407-
// the location of the difference may change
408-
continue
409-
} else if !bytes.Equal(diffBeforeLines.Bytes(), diffAfterLines.Bytes()) {
410-
return true, nil
411-
}
412-
}
374+
cmd := git.NewCommand(ctx, "diff", "--name-only", "-1").AddDynamicArguments(newCommitID, oldCommitID, base)
375+
stdout, stderr, err := cmd.RunStdString(&git.RunOpts{
376+
Dir: tmpBasePath,
377+
})
378+
if err != nil {
379+
log.Error("Unable to run diff on %s %s %s in tempRepo for PR[%d]%s/%s...%s/%s: stdout %s stderr %s Error: %v",
380+
newCommitID, oldCommitID, base,
381+
pr.ID, pr.BaseRepo.FullName(), pr.BaseBranch, pr.HeadRepo.FullName(), pr.HeadBranch,
382+
stdout, stderr, err)
413383

414-
if diffBeforeLines.Scan() || diffAfterLines.Scan() {
415-
// Diffs not of equal length
416-
return true, nil
384+
// New commit should be found
385+
return false, fmt.Errorf("Unable to run diff on %s %s %s in tempRepo for PR[%d]%s/%s...%s/%s: %w",
386+
newCommitID, oldCommitID, base,
387+
pr.ID, pr.BaseRepo.FullName(), pr.BaseBranch, pr.HeadRepo.FullName(), pr.HeadBranch,
388+
err)
417389
}
418390

419-
return false, nil
391+
return strings.TrimSpace(stdout) == "", nil
420392
}
421393

422394
// PushToBaseRepo pushes commits from branches of head repository to

0 commit comments

Comments
 (0)