From 75fd8f6445c1ddf1fb6e7befe4cf177719b15f41 Mon Sep 17 00:00:00 2001
From: Giteabot <teabot@gitea.io>
Date: Wed, 21 Aug 2024 00:20:58 +0800
Subject: [PATCH] [PORT] Fix agit automerge (gitea#31881)

Backport https://github.com/go-gitea/gitea/pull/31207 by @lunny

Fix https://github.com/go-gitea/gitea/issues/31134

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

---
Conflict resolution: none
Modification: `s/assert.NoError/require.NoError`

(cherry picked from commit a0d1630700073785baff4ebc3031b0480d44cc63)
---
 models/fixtures/repository.yml                |   2 +-
 services/automerge/automerge.go               |  18 ++-
 .../user2/repo1.git/hooks/proc-receive        |   7 +
 .../repo1.git/hooks/proc-receive.d/gitea      |   2 +
 tests/integration/pull_merge_test.go          | 129 ++++++++++++++++++
 5 files changed, 154 insertions(+), 4 deletions(-)
 create mode 100755 tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive
 create mode 100755 tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive.d/gitea

diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 845dae7fc1..f783d58187 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -26,7 +26,7 @@
   fork_id: 0
   is_template: false
   template_id: 0
-  size: 7320
+  size: 7597
   is_fsck_enabled: true
   close_issues_via_commit_in_any_branch: false
 
diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go
index 10f3c28d56..ed7a0141b9 100644
--- a/services/automerge/automerge.go
+++ b/services/automerge/automerge.go
@@ -245,9 +245,21 @@ func handlePullRequestAutoMerge(pullID int64, sha string) {
 		defer headGitRepo.Close()
 	}
 
-	headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch)
-	if pr.HeadRepo == nil || !headBranchExist {
-		log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch)
+	switch pr.Flow {
+	case issues_model.PullRequestFlowGithub:
+		headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch)
+		if pr.HeadRepo == nil || !headBranchExist {
+			log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch)
+			return
+		}
+	case issues_model.PullRequestFlowAGit:
+		headBranchExist := git.IsReferenceExist(ctx, baseGitRepo.Path, pr.GetGitRefName())
+		if !headBranchExist {
+			log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch(Agit): %s]", pr, pr.HeadRepoID, pr.HeadBranch)
+			return
+		}
+	default:
+		log.Error("wrong flow type %d", pr.Flow)
 		return
 	}
 
diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive b/tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive
new file mode 100755
index 0000000000..af2808b037
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+ORI_DIR=`pwd`
+SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
+cd "$ORI_DIR"
+for i in `ls "$SHELL_FOLDER/proc-receive.d"`; do
+    sh "$SHELL_FOLDER/proc-receive.d/$i"
+done
diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive.d/gitea b/tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive.d/gitea
new file mode 100755
index 0000000000..97521c6211
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive.d/gitea
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" proc-receive
diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go
index b13ae60c45..e874030503 100644
--- a/tests/integration/pull_merge_test.go
+++ b/tests/integration/pull_merge_test.go
@@ -1020,3 +1020,132 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) {
 		unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
 	})
 }
+
+func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.T) {
+	onGiteaRun(t, func(t *testing.T, u *url.URL) {
+		// create a pull request
+		baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
+
+		dstPath := t.TempDir()
+
+		u.Path = baseAPITestContext.GitPath()
+		u.User = url.UserPassword("user2", userPassword)
+
+		t.Run("Clone", doGitClone(dstPath, u))
+
+		err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
+		require.NoError(t, err)
+
+		err = git.AddChanges(dstPath, true)
+		require.NoError(t, err)
+
+		err = git.CommitChanges(dstPath, git.CommitChangesOptions{
+			Committer: &git.Signature{
+				Email: "user2@example.com",
+				Name:  "user2",
+				When:  time.Now(),
+			},
+			Author: &git.Signature{
+				Email: "user2@example.com",
+				Name:  "user2",
+				When:  time.Now(),
+			},
+			Message: "Testing commit 1",
+		})
+		require.NoError(t, err)
+
+		stderrBuf := &bytes.Buffer{}
+
+		err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").
+			AddDynamicArguments(`topic=test/head2`).
+			AddArguments("-o").
+			AddDynamicArguments(`title="create a test pull request with agit"`).
+			AddArguments("-o").
+			AddDynamicArguments(`description="This PR is a test pull request which created with agit"`).
+			Run(&git.RunOpts{Dir: dstPath, Stderr: stderrBuf})
+		require.NoError(t, err)
+
+		assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/6")
+
+		baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
+		pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
+			Flow:       issues_model.PullRequestFlowAGit,
+			BaseRepoID: baseRepo.ID,
+			BaseBranch: "master",
+			HeadRepoID: baseRepo.ID,
+			HeadBranch: "user2/test/head2",
+		})
+
+		session := loginUser(t, "user1")
+		// add protected branch for commit status
+		csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
+		// Change master branch to protected
+		req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
+			"_csrf":                 csrf,
+			"rule_name":             "master",
+			"enable_push":           "true",
+			"enable_status_check":   "true",
+			"status_check_contexts": "gitea/actions",
+			"required_approvals":    "1",
+		})
+		session.MakeRequest(t, req, http.StatusSeeOther)
+
+		user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+		// first time insert automerge record, return true
+		scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
+		require.NoError(t, err)
+		assert.True(t, scheduled)
+
+		// second time insert automerge record, return false because it does exist
+		scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
+		require.Error(t, err)
+		assert.False(t, scheduled)
+
+		// reload pr again
+		pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+		assert.False(t, pr.HasMerged)
+		assert.Empty(t, pr.MergedCommitID)
+
+		// update commit status to success, then it should be merged automatically
+		baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
+		require.NoError(t, err)
+		sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
+		require.NoError(t, err)
+		masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
+		require.NoError(t, err)
+		baseGitRepo.Close()
+		defer func() {
+			testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
+		}()
+
+		err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
+			State:     api.CommitStatusSuccess,
+			TargetURL: "https://gitea.com",
+			Context:   "gitea/actions",
+		})
+		require.NoError(t, err)
+
+		time.Sleep(2 * time.Second)
+
+		// reload pr again
+		pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+		assert.False(t, pr.HasMerged)
+		assert.Empty(t, pr.MergedCommitID)
+
+		// approve the PR from non-author
+		approveSession := loginUser(t, "user1")
+		req = NewRequest(t, "GET", fmt.Sprintf("/user2/repo1/pulls/%d", pr.Index))
+		resp := approveSession.MakeRequest(t, req, http.StatusOK)
+		htmlDoc := NewHTMLParser(t, resp.Body)
+		testSubmitReview(t, approveSession, htmlDoc.GetCSRF(), "user2", "repo1", strconv.Itoa(int(pr.Index)), sha, "approve", http.StatusOK)
+
+		time.Sleep(2 * time.Second)
+
+		// realod pr again
+		pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+		assert.True(t, pr.HasMerged)
+		assert.NotEmpty(t, pr.MergedCommitID)
+
+		unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
+	})
+}