mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-04-08 02:31:36 +02:00
[v11.0/forgejo] fix(migrations): transfer PR flow information (#7437)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/7421 - When migrating a pull requests from a Gitea or Forgejo instance, check if the pull request was created via the AGit flow and transfer that bit of information to the migrated pull request. - Expose this bit of information as the `flow` field for the pull request. - We have to do a horrible Go hack with Gitea's [go-sdk](gitea.com/gitea/go-sdk) to list all pull requests while being able to decode it to a struct that contains the new `Flow` field. The library does not allow you to do this out of the box, so we have to use `go:linkname` to access the private method that allows us to do this. This in turn means we have to do some boilerplate code that the library otherwise would do for us. The better option would be forking, but that would be a hassle of keeping the library in sync. - Resolves forgejo/forgejo#5848 - Unit test added. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7437 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org> Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
This commit is contained in:
parent
49625aac60
commit
52da8aab7e
12 changed files with 131 additions and 3 deletions
|
@ -34,6 +34,7 @@ type PullRequest struct {
|
|||
Assignees []string
|
||||
IsLocked bool `yaml:"is_locked"`
|
||||
Reactions []*Reaction
|
||||
Flow int64
|
||||
ForeignIndex int64
|
||||
Context DownloaderContext `yaml:"-"`
|
||||
EnsuredSafe bool `yaml:"ensured_safe"`
|
||||
|
|
|
@ -57,7 +57,8 @@ type PullRequest struct {
|
|||
// swagger:strfmt date-time
|
||||
Closed *time.Time `json:"closed_at"`
|
||||
|
||||
PinOrder int `json:"pin_order"`
|
||||
PinOrder int `json:"pin_order"`
|
||||
Flow int64 `json:"flow"`
|
||||
}
|
||||
|
||||
// PRBranchInfo information about a branch
|
||||
|
|
|
@ -95,6 +95,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
|||
RequestedReviewersTeams: []*api.Team{},
|
||||
|
||||
AllowMaintainerEdit: pr.AllowMaintainerEdit,
|
||||
Flow: int64(pr.Flow),
|
||||
|
||||
Base: &api.PRBranchInfo{
|
||||
Name: pr.BaseBranch,
|
||||
|
|
|
@ -504,6 +504,28 @@ func (g *GiteaDownloader) GetComments(commentable base.Commentable) ([]*base.Com
|
|||
return allComments, true, nil
|
||||
}
|
||||
|
||||
type ForgejoPullRequest struct {
|
||||
gitea_sdk.PullRequest
|
||||
Flow int64 `json:"flow"`
|
||||
}
|
||||
|
||||
// Extracted from https://gitea.com/gitea/go-sdk/src/commit/164e3358bc02213954fb4380b821bed80a14824d/gitea/pull.go#L347-L364
|
||||
func (g *GiteaDownloader) fixPullHeadSha(pr *ForgejoPullRequest) error {
|
||||
if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil && pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" {
|
||||
owner := pr.Base.Repository.Owner.UserName
|
||||
repo := pr.Base.Repository.Name
|
||||
refs, _, err := g.client.GetRepoRefs(owner, repo, pr.Head.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
return fmt.Errorf("unable to resolve PR ref %q", pr.Head.Ref)
|
||||
}
|
||||
pr.Head.Sha = refs[0].Object.SHA
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPullRequests returns pull requests according page and perPage
|
||||
func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) {
|
||||
if perPage > g.maxPerPage {
|
||||
|
@ -511,16 +533,30 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
|
|||
}
|
||||
allPRs := make([]*base.PullRequest, 0, perPage)
|
||||
|
||||
prs, _, err := g.client.ListRepoPullRequests(g.repoOwner, g.repoName, gitea_sdk.ListPullRequestsOptions{
|
||||
prs := make([]*ForgejoPullRequest, 0, perPage)
|
||||
opt := gitea_sdk.ListPullRequestsOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: perPage,
|
||||
},
|
||||
State: gitea_sdk.StateAll,
|
||||
})
|
||||
}
|
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", url.PathEscape(g.repoOwner), url.PathEscape(g.repoName)))
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
_, err := getParsedResponse(g.client, "GET", link.String(), http.Header{"content-type": []string{"application/json"}}, nil, &prs)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing pull requests (page: %d, pagesize: %d). Error: %w", page, perPage, err)
|
||||
}
|
||||
|
||||
if g.client.CheckServerVersionConstraint(">= 1.14.0") != nil {
|
||||
for i := range prs {
|
||||
if err := g.fixPullHeadSha(prs[i]); err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing pull requests (page: %d, pagesize: %d). Error: %w", page, perPage, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
var milestone string
|
||||
if pr.Milestone != nil {
|
||||
|
@ -598,6 +634,7 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
|
|||
MergeCommitSHA: mergeCommitSHA,
|
||||
IsLocked: pr.IsLocked,
|
||||
PatchURL: pr.PatchURL,
|
||||
Flow: pr.Flow,
|
||||
Head: base.PullRequestBranch{
|
||||
Ref: headRef,
|
||||
SHA: headSHA,
|
||||
|
|
|
@ -307,3 +307,46 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
},
|
||||
}, reviews)
|
||||
}
|
||||
|
||||
func TestForgejoDownloadRepo(t *testing.T) {
|
||||
token := os.Getenv("CODE_FORGEJO_TOKEN")
|
||||
|
||||
fixturePath := "./testdata/code-forgejo-org/full_download"
|
||||
server := unittest.NewMockWebServer(t, "https://code.forgejo.org", fixturePath, token != "")
|
||||
defer server.Close()
|
||||
|
||||
downloader, err := NewGiteaDownloader(t.Context(), server.URL, "Gusted/agit-test", "", "", token)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, downloader)
|
||||
|
||||
prs, _, err := downloader.GetPullRequests(1, 50)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, prs, 1)
|
||||
|
||||
assertPullRequestEqual(t, &base.PullRequest{
|
||||
Number: 1,
|
||||
PosterID: 63,
|
||||
PosterName: "Gusted",
|
||||
PosterEmail: "postmaster@gusted.xyz",
|
||||
Title: "Add extra information",
|
||||
State: "open",
|
||||
Created: time.Date(2025, time.April, 1, 20, 28, 45, 0, time.UTC),
|
||||
Updated: time.Date(2025, time.April, 1, 20, 28, 45, 0, time.UTC),
|
||||
Base: base.PullRequestBranch{
|
||||
CloneURL: "",
|
||||
Ref: "main",
|
||||
SHA: "79ebb873a6497c8847141ba9706b3f757196a1e6",
|
||||
RepoName: "agit-test",
|
||||
OwnerName: "Gusted",
|
||||
},
|
||||
Head: base.PullRequestBranch{
|
||||
CloneURL: server.URL + "/Gusted/agit-test.git",
|
||||
Ref: "refs/pull/1/head",
|
||||
SHA: "667e9317ec37b977e6d3d7d43e3440636970563c",
|
||||
RepoName: "agit-test",
|
||||
OwnerName: "Gusted",
|
||||
},
|
||||
PatchURL: server.URL + "/Gusted/agit-test/pulls/1.patch",
|
||||
Flow: 1,
|
||||
}, prs[0])
|
||||
}
|
||||
|
|
16
services/migrations/gitea_sdk_hack.go
Normal file
16
services/migrations/gitea_sdk_hack.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
_ "unsafe" // Needed for go:linkname support
|
||||
|
||||
gitea_sdk "code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
//go:linkname getParsedResponse code.gitea.io/sdk/gitea.(*Client).getParsedResponse
|
||||
func getParsedResponse(client *gitea_sdk.Client, method, path string, header http.Header, body io.Reader, obj any) (*gitea_sdk.Response, error)
|
|
@ -802,6 +802,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model
|
|||
MergeBase: pr.Base.SHA,
|
||||
Index: pr.Number,
|
||||
HasMerged: pr.Merged,
|
||||
Flow: issues_model.PullRequestFlow(pr.Flow),
|
||||
|
||||
Issue: &issue,
|
||||
}
|
||||
|
|
|
@ -136,6 +136,7 @@ func assertPullRequestEqual(t *testing.T, expected, actual *base.PullRequest) {
|
|||
assert.ElementsMatch(t, expected.Assignees, actual.Assignees)
|
||||
assert.Equal(t, expected.IsLocked, actual.IsLocked)
|
||||
assertReactionsEqual(t, expected.Reactions, actual.Reactions)
|
||||
assert.Equal(t, expected.Flow, actual.Flow)
|
||||
}
|
||||
|
||||
func assertPullRequestsEqual(t *testing.T, expected, actual []*base.PullRequest) {
|
||||
|
|
File diff suppressed because one or more lines are too long
7
services/migrations/testdata/code-forgejo-org/full_download/GET_%2Fapi%2Fv1%2Fsettings%2Fapi
vendored
Normal file
7
services/migrations/testdata/code-forgejo-org/full_download/GET_%2Fapi%2Fv1%2Fsettings%2Fapi
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
Content-Length: 117
|
||||
Cache-Control: max-age=0, private, must-revalidate, no-transform
|
||||
Content-Type: application/json;charset=utf-8
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
|
||||
{"max_response_items":50,"default_paging_num":30,"default_git_trees_per_page":1000,"default_max_blob_size":10485760}
|
7
services/migrations/testdata/code-forgejo-org/full_download/GET_%2Fapi%2Fv1%2Fversion
vendored
Normal file
7
services/migrations/testdata/code-forgejo-org/full_download/GET_%2Fapi%2Fv1%2Fversion
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
Cache-Control: max-age=0, private, must-revalidate, no-transform
|
||||
Content-Type: application/json;charset=utf-8
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
Content-Length: 53
|
||||
|
||||
{"version":"11.0.0-dev-617-1d1e0ced3e+gitea-1.22.0"}
|
5
templates/swagger/v1_json.tmpl
generated
5
templates/swagger/v1_json.tmpl
generated
|
@ -26133,6 +26133,11 @@
|
|||
"format": "date-time",
|
||||
"x-go-name": "Deadline"
|
||||
},
|
||||
"flow": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "Flow"
|
||||
},
|
||||
"head": {
|
||||
"$ref": "#/definitions/PRBranchInfo"
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue