mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-22 22:35:18 +02:00
before: gitrepo vs git packages after: git package fully handle all git operations by the way, use `WithRepo(repo)` instead of `WithDir(repo.Path)` to hide path details. benefits: 1. remove all unnecessary wrappers, developers no need to struggle with "which package should be used" 2. simplify code, RepositoryFacade can (will) be used everywhere, all "path" details are (will be) hidden
488 lines
14 KiB
Go
488 lines
14 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package convert
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
git_model "gitea.dev/models/git"
|
|
issues_model "gitea.dev/models/issues"
|
|
"gitea.dev/models/perm"
|
|
access_model "gitea.dev/models/perm/access"
|
|
repo_model "gitea.dev/models/repo"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/cache"
|
|
"gitea.dev/modules/cachegroup"
|
|
"gitea.dev/modules/git"
|
|
"gitea.dev/modules/log"
|
|
"gitea.dev/modules/setting"
|
|
api "gitea.dev/modules/structs"
|
|
"gitea.dev/modules/util"
|
|
"gitea.dev/services/gitdiff"
|
|
)
|
|
|
|
// ToAPIPullRequest assumes following fields have been assigned with valid values:
|
|
// Required - Issue
|
|
// Optional - Merger
|
|
func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) *api.PullRequest {
|
|
var (
|
|
baseBranch string
|
|
headBranch string
|
|
baseCommit *git.Commit
|
|
err error
|
|
)
|
|
|
|
if err = pr.LoadIssue(ctx); err != nil {
|
|
log.Error("pr.LoadIssue[%d]: %v", pr.ID, err)
|
|
return nil
|
|
}
|
|
|
|
if err = pr.Issue.LoadRepo(ctx); err != nil {
|
|
log.Error("pr.Issue.LoadRepo[%d]: %v", pr.ID, err)
|
|
return nil
|
|
}
|
|
|
|
apiIssue := ToAPIIssue(ctx, doer, pr.Issue)
|
|
if err := pr.LoadBaseRepo(ctx); err != nil {
|
|
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
|
|
return nil
|
|
}
|
|
|
|
if err := pr.LoadHeadRepo(ctx); err != nil {
|
|
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
|
|
return nil
|
|
}
|
|
|
|
var doerID int64
|
|
if doer != nil {
|
|
doerID = doer.ID
|
|
}
|
|
|
|
repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, fmt.Sprintf("%d-%d", pr.BaseRepoID, doerID),
|
|
func(ctx context.Context, _ string) (access_model.Permission, error) {
|
|
return access_model.GetDoerRepoPermission(ctx, pr.BaseRepo, doer)
|
|
},
|
|
)
|
|
if err != nil {
|
|
log.Error("GetDoerRepoPermission[%d]: %v", pr.BaseRepoID, err)
|
|
repoUserPerm.AccessMode = perm.AccessModeNone
|
|
}
|
|
|
|
apiPullRequest := &api.PullRequest{
|
|
ID: pr.ID,
|
|
URL: pr.Issue.HTMLURL(ctx),
|
|
Index: pr.Index,
|
|
Poster: apiIssue.Poster,
|
|
Title: apiIssue.Title,
|
|
Body: apiIssue.Body,
|
|
Labels: apiIssue.Labels,
|
|
Milestone: apiIssue.Milestone,
|
|
Assignee: apiIssue.Assignee,
|
|
Assignees: util.SliceNilAsEmpty(apiIssue.Assignees),
|
|
State: apiIssue.State,
|
|
Draft: pr.IsWorkInProgress(ctx),
|
|
IsLocked: apiIssue.IsLocked,
|
|
Comments: apiIssue.Comments,
|
|
ReviewComments: pr.GetReviewCommentsCount(ctx),
|
|
HTMLURL: pr.Issue.HTMLURL(ctx),
|
|
DiffURL: pr.Issue.DiffURL(),
|
|
PatchURL: pr.Issue.PatchURL(),
|
|
HasMerged: pr.HasMerged,
|
|
MergeBase: pr.MergeBase,
|
|
Mergeable: pr.Mergeable(ctx),
|
|
Deadline: apiIssue.Deadline,
|
|
Created: pr.Issue.CreatedUnix.AsTimePtr(),
|
|
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
|
|
PinOrder: util.Iif(apiIssue.PinOrder == -1, 0, apiIssue.PinOrder),
|
|
ContentVersion: apiIssue.ContentVersion,
|
|
|
|
// output "[]" rather than null to align to github outputs
|
|
RequestedReviewers: []*api.User{},
|
|
RequestedReviewersTeams: []*api.Team{},
|
|
|
|
AllowMaintainerEdit: pr.AllowMaintainerEdit,
|
|
|
|
Base: &api.PRBranchInfo{
|
|
Name: pr.BaseBranch,
|
|
Ref: pr.BaseBranch,
|
|
RepoID: pr.BaseRepoID,
|
|
Repository: ToRepo(ctx, pr.BaseRepo, repoUserPerm),
|
|
},
|
|
Head: &api.PRBranchInfo{
|
|
Name: pr.HeadBranch,
|
|
Ref: pr.GetGitHeadRefName(),
|
|
RepoID: -1,
|
|
},
|
|
}
|
|
|
|
if err = pr.LoadRequestedReviewers(ctx); err != nil {
|
|
log.Error("LoadRequestedReviewers[%d]: %v", pr.ID, err)
|
|
return nil
|
|
}
|
|
if err = pr.LoadRequestedReviewersTeams(ctx); err != nil {
|
|
log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err)
|
|
return nil
|
|
}
|
|
|
|
for _, reviewer := range pr.RequestedReviewers {
|
|
apiPullRequest.RequestedReviewers = append(apiPullRequest.RequestedReviewers, ToUser(ctx, reviewer, nil))
|
|
}
|
|
|
|
for _, reviewerTeam := range pr.RequestedReviewersTeams {
|
|
convertedTeam, err := ToTeam(ctx, reviewerTeam, true)
|
|
if err != nil {
|
|
log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err)
|
|
return nil
|
|
}
|
|
|
|
apiPullRequest.RequestedReviewersTeams = append(apiPullRequest.RequestedReviewersTeams, convertedTeam)
|
|
}
|
|
|
|
if pr.Issue.ClosedUnix != 0 {
|
|
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
|
|
}
|
|
|
|
gitRepo, err := git.OpenRepository(pr.BaseRepo)
|
|
if err != nil {
|
|
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.FullName(), err)
|
|
return nil
|
|
}
|
|
defer gitRepo.Close()
|
|
|
|
exist, err := git_model.IsBranchExist(ctx, pr.BaseRepoID, pr.BaseBranch)
|
|
if err != nil {
|
|
log.Error("GetBranch[%s]: %v", pr.BaseBranch, err)
|
|
return nil
|
|
}
|
|
|
|
if exist {
|
|
baseCommit, err = gitRepo.GetBranchCommit(ctx, pr.BaseBranch)
|
|
if err != nil && !git.IsErrNotExist(err) {
|
|
log.Error("GetCommit[%s]: %v", baseBranch, err)
|
|
return nil
|
|
}
|
|
|
|
if err == nil {
|
|
apiPullRequest.Base.Sha = baseCommit.ID.String()
|
|
}
|
|
}
|
|
|
|
if pr.Flow == issues_model.PullRequestFlowAGit {
|
|
apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(ctx, pr.GetGitHeadRefName())
|
|
if err != nil {
|
|
log.Error("GetRefCommitID[%s]: %v", pr.GetGitHeadRefName(), err)
|
|
return nil
|
|
}
|
|
apiPullRequest.Head.RepoID = pr.BaseRepoID
|
|
apiPullRequest.Head.Repository = apiPullRequest.Base.Repository
|
|
apiPullRequest.Head.Name = ""
|
|
}
|
|
|
|
if pr.HeadRepo != nil && pr.Flow == issues_model.PullRequestFlowGithub {
|
|
p, err := access_model.GetDoerRepoPermission(ctx, pr.HeadRepo, doer)
|
|
if err != nil {
|
|
log.Error("GetDoerRepoPermission[%d]: %v", pr.HeadRepoID, err)
|
|
p.AccessMode = perm.AccessModeNone
|
|
}
|
|
|
|
apiPullRequest.Head.RepoID = pr.HeadRepo.ID
|
|
apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p)
|
|
|
|
headGitRepo, err := git.OpenRepository(pr.HeadRepo)
|
|
if err != nil {
|
|
log.Error("OpenRepository[%s]: %v", pr.HeadRepo.FullName(), err)
|
|
return nil
|
|
}
|
|
defer headGitRepo.Close()
|
|
|
|
exist, err = git_model.IsBranchExist(ctx, pr.HeadRepoID, pr.HeadBranch)
|
|
if err != nil {
|
|
log.Error("GetBranch[%s]: %v", pr.HeadBranch, err)
|
|
return nil
|
|
}
|
|
|
|
// Outer scope variables to be used in diff calculation
|
|
var (
|
|
startCommitID string
|
|
endCommitID string
|
|
)
|
|
|
|
if !exist {
|
|
headCommitID, err := headGitRepo.GetRefCommitID(ctx, apiPullRequest.Head.Ref)
|
|
if err != nil && !git.IsErrNotExist(err) {
|
|
log.Error("GetCommit[%s]: %v", pr.HeadBranch, err)
|
|
return nil
|
|
}
|
|
if err == nil {
|
|
apiPullRequest.Head.Sha = headCommitID
|
|
endCommitID = headCommitID
|
|
}
|
|
} else {
|
|
commit, err := headGitRepo.GetBranchCommit(ctx, pr.HeadBranch)
|
|
if err != nil && !git.IsErrNotExist(err) {
|
|
log.Error("GetCommit[%s]: %v", headBranch, err)
|
|
return nil
|
|
}
|
|
if err == nil {
|
|
apiPullRequest.Head.Ref = pr.HeadBranch
|
|
apiPullRequest.Head.Sha = commit.ID.String()
|
|
endCommitID = commit.ID.String()
|
|
}
|
|
}
|
|
|
|
// Calculate diff
|
|
startCommitID = pr.MergeBase
|
|
|
|
diffShortStats, err := gitdiff.GetDiffShortStat(ctx, gitRepo, startCommitID, endCommitID)
|
|
if err != nil {
|
|
log.Error("GetDiffShortStat: %v", err)
|
|
} else {
|
|
apiPullRequest.ChangedFiles = &diffShortStats.NumFiles
|
|
apiPullRequest.Additions = &diffShortStats.TotalAddition
|
|
apiPullRequest.Deletions = &diffShortStats.TotalDeletion
|
|
}
|
|
}
|
|
|
|
if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 {
|
|
baseGitRepo, err := git.OpenRepository(pr.BaseRepo)
|
|
if err != nil {
|
|
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.FullName(), err)
|
|
return nil
|
|
}
|
|
defer baseGitRepo.Close()
|
|
refs, err := baseGitRepo.GetRefsFiltered(ctx, apiPullRequest.Head.Ref)
|
|
if err != nil {
|
|
log.Error("GetRefsFiltered[%s]: %v", apiPullRequest.Head.Ref, err)
|
|
return nil
|
|
} else if len(refs) == 0 {
|
|
log.Error("unable to resolve PR head ref")
|
|
} else {
|
|
apiPullRequest.Head.Sha = refs[0].Object.String()
|
|
}
|
|
}
|
|
|
|
if pr.HasMerged {
|
|
apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
|
|
apiPullRequest.MergedCommitID = &pr.MergedCommitID
|
|
apiPullRequest.MergedBy = ToUser(ctx, pr.Merger, nil)
|
|
}
|
|
|
|
return apiPullRequest
|
|
}
|
|
|
|
func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs issues_model.PullRequestList, doer *user_model.User) ([]*api.PullRequest, error) {
|
|
for _, pr := range prs {
|
|
pr.BaseRepo = baseRepo
|
|
if pr.BaseRepoID == pr.HeadRepoID {
|
|
pr.HeadRepo = baseRepo
|
|
}
|
|
}
|
|
|
|
// NOTE: load head repositories
|
|
if err := prs.LoadRepositories(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
issueList, err := prs.LoadIssues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := issueList.LoadLabels(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := issueList.LoadPosters(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := issueList.LoadAttachments(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := issueList.LoadMilestones(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := issueList.LoadAssignees(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = issueList.LoadPinOrder(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reviews, err := prs.LoadReviews(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = reviews.LoadReviewers(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reviewersMap := make(map[int64][]*user_model.User)
|
|
for _, review := range reviews {
|
|
if review.Reviewer != nil {
|
|
reviewersMap[review.IssueID] = append(reviewersMap[review.IssueID], review.Reviewer)
|
|
}
|
|
}
|
|
|
|
reviewCounts, err := prs.LoadReviewCommentsCounts(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gitRepo, err := git.OpenRepository(baseRepo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer gitRepo.Close()
|
|
|
|
baseRepoPerm, err := access_model.GetDoerRepoPermission(ctx, baseRepo, doer)
|
|
if err != nil {
|
|
log.Error("GetDoerRepoPermission[%d]: %v", baseRepo.ID, err)
|
|
baseRepoPerm.AccessMode = perm.AccessModeNone
|
|
}
|
|
|
|
apiRepo := ToRepo(ctx, baseRepo, baseRepoPerm)
|
|
baseBranchCache := make(map[string]*git_model.Branch)
|
|
apiPullRequests := make([]*api.PullRequest, 0, len(prs))
|
|
for _, pr := range prs {
|
|
apiIssue := ToAPIIssue(ctx, doer, pr.Issue)
|
|
|
|
apiPullRequest := &api.PullRequest{
|
|
ID: pr.ID,
|
|
URL: pr.Issue.HTMLURL(ctx),
|
|
Index: pr.Index,
|
|
Poster: apiIssue.Poster,
|
|
Title: apiIssue.Title,
|
|
Body: apiIssue.Body,
|
|
Labels: apiIssue.Labels,
|
|
Milestone: apiIssue.Milestone,
|
|
Assignee: apiIssue.Assignee,
|
|
Assignees: apiIssue.Assignees,
|
|
State: apiIssue.State,
|
|
Draft: pr.IsWorkInProgress(ctx),
|
|
IsLocked: apiIssue.IsLocked,
|
|
Comments: apiIssue.Comments,
|
|
ReviewComments: reviewCounts[pr.IssueID],
|
|
HTMLURL: pr.Issue.HTMLURL(ctx),
|
|
DiffURL: pr.Issue.DiffURL(),
|
|
PatchURL: pr.Issue.PatchURL(),
|
|
HasMerged: pr.HasMerged,
|
|
MergeBase: pr.MergeBase,
|
|
Mergeable: pr.Mergeable(ctx),
|
|
Deadline: apiIssue.Deadline,
|
|
Created: pr.Issue.CreatedUnix.AsTimePtr(),
|
|
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
|
|
PinOrder: util.Iif(apiIssue.PinOrder == -1, 0, apiIssue.PinOrder),
|
|
ContentVersion: apiIssue.ContentVersion,
|
|
|
|
AllowMaintainerEdit: pr.AllowMaintainerEdit,
|
|
|
|
Base: &api.PRBranchInfo{
|
|
Name: pr.BaseBranch,
|
|
Ref: pr.BaseBranch,
|
|
RepoID: pr.BaseRepoID,
|
|
Repository: apiRepo,
|
|
},
|
|
Head: &api.PRBranchInfo{
|
|
Name: pr.HeadBranch,
|
|
Ref: pr.GetGitHeadRefName(),
|
|
RepoID: -1,
|
|
},
|
|
}
|
|
|
|
pr.RequestedReviewers = reviewersMap[pr.IssueID]
|
|
for _, reviewer := range pr.RequestedReviewers {
|
|
apiPullRequest.RequestedReviewers = append(apiPullRequest.RequestedReviewers, ToUser(ctx, reviewer, nil))
|
|
}
|
|
|
|
for _, reviewerTeam := range pr.RequestedReviewersTeams {
|
|
convertedTeam, err := ToTeam(ctx, reviewerTeam, true)
|
|
if err != nil {
|
|
log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err)
|
|
return nil, err
|
|
}
|
|
|
|
apiPullRequest.RequestedReviewersTeams = append(apiPullRequest.RequestedReviewersTeams, convertedTeam)
|
|
}
|
|
|
|
if pr.Issue.ClosedUnix != 0 {
|
|
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
|
|
}
|
|
|
|
baseBranch, ok := baseBranchCache[pr.BaseBranch]
|
|
if !ok {
|
|
baseBranch, err = git_model.GetBranch(ctx, baseRepo.ID, pr.BaseBranch)
|
|
if err == nil {
|
|
baseBranchCache[pr.BaseBranch] = baseBranch
|
|
} else if !git_model.IsErrBranchNotExist(err) {
|
|
return nil, err
|
|
}
|
|
}
|
|
if baseBranch != nil {
|
|
apiPullRequest.Base.Sha = baseBranch.CommitID
|
|
}
|
|
if pr.HeadRepoID == pr.BaseRepoID {
|
|
apiPullRequest.Head.Repository = apiPullRequest.Base.Repository
|
|
}
|
|
|
|
// pull request head branch, both repository and branch could not exist
|
|
if pr.HeadRepo != nil {
|
|
apiPullRequest.Head.RepoID = pr.HeadRepo.ID
|
|
exist, err := git_model.IsBranchExist(ctx, pr.HeadRepo.ID, pr.HeadBranch)
|
|
if err != nil {
|
|
log.Error("IsBranchExist[%d]: %v", pr.HeadRepo.ID, err)
|
|
return nil, err
|
|
}
|
|
if exist {
|
|
apiPullRequest.Head.Ref = pr.HeadBranch
|
|
}
|
|
if pr.HeadRepoID != pr.BaseRepoID {
|
|
p, err := access_model.GetDoerRepoPermission(ctx, pr.HeadRepo, doer)
|
|
if err != nil {
|
|
log.Error("GetDoerRepoPermission[%d]: %v", pr.HeadRepoID, err)
|
|
p.AccessMode = perm.AccessModeNone
|
|
}
|
|
apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p)
|
|
}
|
|
}
|
|
if apiPullRequest.Head.Ref == "" {
|
|
apiPullRequest.Head.Ref = pr.GetGitHeadRefName()
|
|
}
|
|
|
|
if pr.Flow == issues_model.PullRequestFlowAGit {
|
|
apiPullRequest.Head.Name = ""
|
|
}
|
|
apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(ctx, pr.GetGitHeadRefName())
|
|
if err != nil {
|
|
log.Error("GetRefCommitID[%s]: %v", pr.GetGitHeadRefName(), err)
|
|
}
|
|
|
|
if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 {
|
|
refs, err := gitRepo.GetRefsFiltered(ctx, apiPullRequest.Head.Ref)
|
|
if err != nil {
|
|
log.Error("GetRefsFiltered[%s]: %v", apiPullRequest.Head.Ref, err)
|
|
return nil, err
|
|
} else if len(refs) == 0 {
|
|
log.Error("unable to resolve PR head ref")
|
|
} else {
|
|
apiPullRequest.Head.Sha = refs[0].Object.String()
|
|
}
|
|
}
|
|
|
|
if pr.HasMerged {
|
|
apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
|
|
apiPullRequest.MergedCommitID = &pr.MergedCommitID
|
|
apiPullRequest.MergedBy = ToUser(ctx, pr.Merger, nil)
|
|
}
|
|
|
|
// Do not provide "ChangeFiles/Additions/Deletions" for the PR list, because the "diff" is quite slow
|
|
// If callers are interested in these values, they should do a separate request to get the PR details
|
|
if apiPullRequest.ChangedFiles != nil || apiPullRequest.Additions != nil || apiPullRequest.Deletions != nil {
|
|
setting.PanicInDevOrTesting("ChangedFiles/Additions/Deletions should not be set in PR list")
|
|
}
|
|
|
|
apiPullRequests = append(apiPullRequests, apiPullRequest)
|
|
}
|
|
|
|
return apiPullRequests, nil
|
|
}
|