// Copyright Earl Warren // Copyright Loïc Dachary // SPDX-License-Identifier: MIT package driver import ( "context" "fmt" "time" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/timeutil" issue_service "code.gitea.io/gitea/services/issue" "code.forgejo.org/f3/gof3/v3/f3" 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_tree.KindForge).GetDriver().(*forge) owners := forge.getOwnersPath(ctx, fmt.Sprintf("%d", repository.OwnerID)) return f3_tree.NewRepositoryReference(owners.String(), repository.OwnerID, repository.ID) } func (o *pullRequest) referenceToRepository(reference *f3.Reference) int64 { var project int64 if reference.Get() == "../../repository/vcs" { project = f3_tree.GetProjectID(o.GetNode()) } else { p := f3_tree.ToPath(generic.PathAbsolute(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(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(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 == "closed", 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 := f3_util.ParseInt(string(node.GetID())) 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 := f3_util.ParseInt(string(node.GetID())) 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) generic.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 generic.NodeID(fmt.Sprintf("%d", 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{} }