From d68b9237bf91aecd99132c8a98f815d40a1b8642 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Wed, 27 Dec 2023 01:57:25 +0800
Subject: [PATCH] Use known issue IID to generate new PR index number when
 migrating from GitLab (#28616)

Fix #13884
---
 services/migrations/gitlab.go      | 39 +++++++++++++++++++++---------
 services/migrations/gitlab_test.go | 17 +++++++++++++
 2 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go
index 22bc4cf8f3..3db10465fc 100644
--- a/services/migrations/gitlab.go
+++ b/services/migrations/gitlab.go
@@ -55,19 +55,36 @@ func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType {
 	return structs.GitlabService
 }
 
+type gitlabIIDResolver struct {
+	maxIssueIID int64
+	frozen      bool
+}
+
+func (r *gitlabIIDResolver) recordIssueIID(issueIID int) {
+	if r.frozen {
+		panic("cannot record issue IID after pull request IID generation has started")
+	}
+	r.maxIssueIID = max(r.maxIssueIID, int64(issueIID))
+}
+
+func (r *gitlabIIDResolver) generatePullRequestNumber(mrIID int) int64 {
+	r.frozen = true
+	return r.maxIssueIID + int64(mrIID)
+}
+
 // GitlabDownloader implements a Downloader interface to get repository information
 // from gitlab via go-gitlab
 // - issueCount is incremented in GetIssues() to ensure PR and Issue numbers do not overlap,
 // because Gitlab has individual Issue and Pull Request numbers.
 type GitlabDownloader struct {
 	base.NullDownloader
-	ctx        context.Context
-	client     *gitlab.Client
-	baseURL    string
-	repoID     int
-	repoName   string
-	issueCount int64
-	maxPerPage int
+	ctx         context.Context
+	client      *gitlab.Client
+	baseURL     string
+	repoID      int
+	repoName    string
+	iidResolver gitlabIIDResolver
+	maxPerPage  int
 }
 
 // NewGitlabDownloader creates a gitlab Downloader via gitlab API
@@ -450,8 +467,8 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 			Context:      gitlabIssueContext{IsMergeRequest: false},
 		})
 
-		// increment issueCount, to be used in GetPullRequests()
-		g.issueCount++
+		// record the issue IID, to be used in GetPullRequests()
+		g.iidResolver.recordIssueIID(issue.IID)
 	}
 
 	return allIssues, len(issues) < perPage, nil
@@ -607,8 +624,8 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 			awardPage++
 		}
 
-		// Add the PR ID to the Issue Count because PR and Issues share ID space in Gitea
-		newPRNumber := g.issueCount + int64(pr.IID)
+		// Generate new PR Numbers by the known Issue Numbers, because they share the same number space in Gitea, but they are independent in Gitlab
+		newPRNumber := g.iidResolver.generatePullRequestNumber(pr.IID)
 
 		allPRs = append(allPRs, &base.PullRequest{
 			Title:          pr.Title,
diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go
index 731486eff2..1e0aa2b025 100644
--- a/services/migrations/gitlab_test.go
+++ b/services/migrations/gitlab_test.go
@@ -516,3 +516,20 @@ func TestAwardsToReactions(t *testing.T) {
 		},
 	}, reactions)
 }
+
+func TestGitlabIIDResolver(t *testing.T) {
+	r := gitlabIIDResolver{}
+	r.recordIssueIID(1)
+	r.recordIssueIID(2)
+	r.recordIssueIID(3)
+	r.recordIssueIID(2)
+	assert.EqualValues(t, 4, r.generatePullRequestNumber(1))
+	assert.EqualValues(t, 13, r.generatePullRequestNumber(10))
+
+	assert.Panics(t, func() {
+		r := gitlabIIDResolver{}
+		r.recordIssueIID(1)
+		assert.EqualValues(t, 2, r.generatePullRequestNumber(1))
+		r.recordIssueIID(3) // the generation procedure has been started, it shouldn't accept any new issue IID, so it panics
+	})
+}