forgejo/services/f3/driver/pullrequest.go
limiting-factor a71392c6aa chore: update gof3/v3 v3.11.15 (#10673)
Update the Forgejo driver for gof3 with modifications for non-backward compatible changes. The changes are isolated behind the f3.Enable flag and not yet functional. The purpose of this upgrade is to not drift from the gof3 implementation while the work continues.

The `fix: include remote users when counting users` commit is a functional change to Forgejo itself but does not change the behavior because the remote users are only created in fixtures or by F3.

59c721d26b/models/user/user.go (L65-L66)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10673
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: limiting-factor <limiting-factor@posteo.com>
Co-committed-by: limiting-factor <limiting-factor@posteo.com>
2026-01-13 16:59:56 +01:00

322 lines
9.3 KiB
Go

// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"time"
"forgejo.org/models/db"
issues_model "forgejo.org/models/issues"
repo_model "forgejo.org/models/repo"
user_model "forgejo.org/models/user"
"forgejo.org/modules/git"
"forgejo.org/modules/timeutil"
issue_service "forgejo.org/services/issue"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_kind "code.forgejo.org/f3/gof3/v3/kind"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
type pullRequest struct {
common
forgejoPullRequest *issues_model.Issue
headRepository *f3.Reference
baseRepository *f3.Reference
fetchFunc f3.PullRequestFetchFunc
}
func (o *pullRequest) SetNative(pullRequest any) {
o.forgejoPullRequest = pullRequest.(*issues_model.Issue)
}
func (o *pullRequest) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoPullRequest.Index)
}
func (o *pullRequest) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *pullRequest) repositoryToReference(ctx context.Context, repository *repo_model.Repository) *f3.Reference {
if repository == nil {
panic("unexpected nil repository")
}
forge := o.getTree().GetRoot().GetChild(f3_id.NewNodeID(f3_kind.KindForge)).GetDriver().(*forge)
owners := forge.getOwnersPath(ctx, fmt.Sprintf("%d", repository.OwnerID))
return f3_tree.NewRepositoryReference(owners.String(), f3_util.ToString(repository.OwnerID), f3_util.ToString(repository.ID), f3.RepositoryNameDefault)
}
func (o *pullRequest) referenceToRepository(reference *f3.Reference) int64 {
var project int64
if reference.Get() == "../../repositories/vcs" {
project = f3_tree.GetProjectID(o.GetNode())
} else {
p := generic.PathAbsolute(generic.NewNode, o.GetNode().GetCurrentPath().String(), reference.Get())
o.Trace("%v %v", o.GetNode().GetCurrentPath().String(), p)
_, project = p.OwnerAndProjectID()
}
return project
}
func (o *pullRequest) ToFormat() f3.Interface {
if o.forgejoPullRequest == nil {
return o.NewFormat()
}
var milestone *f3.Reference
if o.forgejoPullRequest.Milestone != nil {
milestone = f3_tree.NewIssueMilestoneReference(f3_util.ToString(o.forgejoPullRequest.Milestone.ID))
}
var mergedTime *time.Time
if o.forgejoPullRequest.PullRequest.HasMerged {
mergedTime = o.forgejoPullRequest.PullRequest.MergedUnix.AsTimePtr()
}
var closedTime *time.Time
if o.forgejoPullRequest.IsClosed {
closedTime = o.forgejoPullRequest.ClosedUnix.AsTimePtr()
}
makePullRequestBranch := func(repo *repo_model.Repository, branch string) f3.PullRequestBranch {
r, err := git.OpenRepository(context.Background(), repo.RepoPath())
if err != nil {
panic(err)
}
defer r.Close()
b, err := r.GetBranch(branch)
if err != nil {
panic(err)
}
c, err := b.GetCommit()
if err != nil {
panic(err)
}
return f3.PullRequestBranch{
Ref: branch,
SHA: c.ID.String(),
}
}
if err := o.forgejoPullRequest.PullRequest.LoadHeadRepo(db.DefaultContext); err != nil {
panic(err)
}
head := makePullRequestBranch(o.forgejoPullRequest.PullRequest.HeadRepo, o.forgejoPullRequest.PullRequest.HeadBranch)
head.Repository = o.headRepository
if err := o.forgejoPullRequest.PullRequest.LoadBaseRepo(db.DefaultContext); err != nil {
panic(err)
}
base := makePullRequestBranch(o.forgejoPullRequest.PullRequest.BaseRepo, o.forgejoPullRequest.PullRequest.BaseBranch)
base.Repository = o.baseRepository
return &f3.PullRequest{
Common: f3.NewCommon(o.GetNativeID()),
PosterID: f3_tree.NewUserReference(f3_util.ToString(o.forgejoPullRequest.Poster.ID)),
Title: o.forgejoPullRequest.Title,
Content: o.forgejoPullRequest.Content,
Milestone: milestone,
State: string(o.forgejoPullRequest.State()),
IsLocked: o.forgejoPullRequest.IsLocked,
Created: o.forgejoPullRequest.CreatedUnix.AsTime(),
Updated: o.forgejoPullRequest.UpdatedUnix.AsTime(),
Closed: closedTime,
Merged: o.forgejoPullRequest.PullRequest.HasMerged,
MergedTime: mergedTime,
MergeCommitSHA: o.forgejoPullRequest.PullRequest.MergedCommitID,
Head: head,
Base: base,
FetchFunc: o.fetchFunc,
}
}
func (o *pullRequest) FromFormat(content f3.Interface) {
pullRequest := content.(*f3.PullRequest)
var milestone *issues_model.Milestone
if pullRequest.Milestone != nil {
milestone = &issues_model.Milestone{
ID: pullRequest.Milestone.GetIDAsInt(),
}
}
o.headRepository = pullRequest.Head.Repository
o.baseRepository = pullRequest.Base.Repository
pr := issues_model.PullRequest{
HeadBranch: pullRequest.Head.Ref,
HeadRepoID: o.referenceToRepository(o.headRepository),
BaseBranch: pullRequest.Base.Ref,
BaseRepoID: o.referenceToRepository(o.baseRepository),
MergeBase: pullRequest.Base.SHA,
Index: f3_util.ParseInt(pullRequest.GetID()),
HasMerged: pullRequest.Merged,
}
o.forgejoPullRequest = &issues_model.Issue{
Index: f3_util.ParseInt(pullRequest.GetID()),
PosterID: pullRequest.PosterID.GetIDAsInt(),
Poster: &user_model.User{
ID: pullRequest.PosterID.GetIDAsInt(),
},
Title: pullRequest.Title,
Content: pullRequest.Content,
Milestone: milestone,
IsClosed: pullRequest.State == f3.PullRequestStateClosed,
CreatedUnix: timeutil.TimeStamp(pullRequest.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(pullRequest.Updated.Unix()),
IsLocked: pullRequest.IsLocked,
PullRequest: &pr,
IsPull: true,
}
if pullRequest.Closed != nil {
o.forgejoPullRequest.ClosedUnix = timeutil.TimeStamp(pullRequest.Closed.Unix())
}
}
func (o *pullRequest) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
id := node.GetID().Int64()
issue, err := issues_model.GetIssueByIndex(ctx, project, id)
if issues_model.IsErrIssueNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("issue %v %w", id, err))
}
if err := issue.LoadAttributes(ctx); err != nil {
panic(err)
}
if err := issue.PullRequest.LoadHeadRepo(ctx); err != nil {
panic(err)
}
o.headRepository = o.repositoryToReference(ctx, issue.PullRequest.HeadRepo)
if err := issue.PullRequest.LoadBaseRepo(ctx); err != nil {
panic(err)
}
o.baseRepository = o.repositoryToReference(ctx, issue.PullRequest.BaseRepo)
o.forgejoPullRequest = issue
o.Trace("ID = %s", o.forgejoPullRequest.ID)
return true
}
func (o *pullRequest) Patch(ctx context.Context) {
node := o.GetNode()
project := f3_tree.GetProjectID(o.GetNode())
id := node.GetID().Int64()
o.Trace("repo_id = %d, index = %d", project, id)
if _, err := db.GetEngine(ctx).Where("`repo_id` = ? AND `index` = ?", project, id).Cols("name", "content").Update(o.forgejoPullRequest); err != nil {
panic(fmt.Errorf("%v %v", o.forgejoPullRequest, err))
}
}
func (o *pullRequest) GetPullRequestPushRefs() []string {
return []string{
fmt.Sprintf("refs/f3/%s/head", o.GetNativeID()),
fmt.Sprintf("refs/pull/%s/head", o.GetNativeID()),
}
}
func (o *pullRequest) GetPullRequestRef() string {
return fmt.Sprintf("refs/pull/%s/head", o.GetNativeID())
}
func (o *pullRequest) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
o.forgejoPullRequest.RepoID = f3_tree.GetProjectID(o.GetNode())
ctx, committer, err := db.TxContext(ctx)
if err != nil {
panic(err)
}
defer committer.Close()
idx, err := db.GetNextResourceIndex(ctx, "issue_index", o.forgejoPullRequest.RepoID)
if err != nil {
panic(fmt.Errorf("generate issue index failed: %w", err))
}
o.forgejoPullRequest.Index = idx
sess := db.GetEngine(ctx)
if _, err = sess.NoAutoTime().Insert(o.forgejoPullRequest); err != nil {
panic(err)
}
pr := o.forgejoPullRequest.PullRequest
pr.Index = o.forgejoPullRequest.Index
pr.IssueID = o.forgejoPullRequest.ID
pr.HeadRepoID = o.referenceToRepository(o.headRepository)
if pr.HeadRepoID == 0 {
panic(fmt.Errorf("HeadRepoID == 0 in %v", pr))
}
pr.BaseRepoID = o.referenceToRepository(o.baseRepository)
if pr.BaseRepoID == 0 {
panic(fmt.Errorf("BaseRepoID == 0 in %v", pr))
}
if _, err = sess.NoAutoTime().Insert(pr); err != nil {
panic(err)
}
if err = committer.Commit(); err != nil {
panic(fmt.Errorf("Commit: %w", err))
}
if err := pr.LoadBaseRepo(ctx); err != nil {
panic(err)
}
if err := pr.LoadHeadRepo(ctx); err != nil {
panic(err)
}
o.Trace("pullRequest created %d/%d", o.forgejoPullRequest.ID, o.forgejoPullRequest.Index)
return f3_id.NewNodeID(o.forgejoPullRequest.Index)
}
func (o *pullRequest) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
repoPath := repo_model.RepoPath(owner, project)
gitRepo, err := git.OpenRepository(ctx, repoPath)
if err != nil {
panic(err)
}
defer gitRepo.Close()
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
if err := issue_service.DeleteIssue(ctx, doer, gitRepo, o.forgejoPullRequest); err != nil {
panic(err)
}
}
func newPullRequest() generic.NodeDriverInterface {
return &pullRequest{}
}