mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
486 lines
14 KiB
486 lines
14 KiB
// Copyright 2020 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package convert |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
|
|
git_model "code.gitea.io/gitea/models/git" |
|
issues_model "code.gitea.io/gitea/models/issues" |
|
"code.gitea.io/gitea/models/perm" |
|
access_model "code.gitea.io/gitea/models/perm/access" |
|
repo_model "code.gitea.io/gitea/models/repo" |
|
user_model "code.gitea.io/gitea/models/user" |
|
"code.gitea.io/gitea/modules/cache" |
|
"code.gitea.io/gitea/modules/cachegroup" |
|
"code.gitea.io/gitea/modules/git" |
|
"code.gitea.io/gitea/modules/gitrepo" |
|
"code.gitea.io/gitea/modules/log" |
|
"code.gitea.io/gitea/modules/setting" |
|
api "code.gitea.io/gitea/modules/structs" |
|
"code.gitea.io/gitea/modules/util" |
|
"code.gitea.io/gitea/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.GetUserRepoPermission(ctx, pr.BaseRepo, doer) |
|
}, |
|
) |
|
if err != nil { |
|
log.Error("GetUserRepoPermission[%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), |
|
|
|
// 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 := gitrepo.OpenRepository(ctx, pr.BaseRepo) |
|
if err != nil { |
|
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), 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(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(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.GetUserRepoPermission(ctx, pr.HeadRepo, doer) |
|
if err != nil { |
|
log.Error("GetUserRepoPermission[%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 := gitrepo.OpenRepository(ctx, pr.HeadRepo) |
|
if err != nil { |
|
log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RepoPath(), 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(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(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(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 := gitrepo.OpenRepository(ctx, pr.BaseRepo) |
|
if err != nil { |
|
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err) |
|
return nil |
|
} |
|
defer baseGitRepo.Close() |
|
refs, err := baseGitRepo.GetRefsFiltered(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 := gitrepo.OpenRepository(ctx, baseRepo) |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer gitRepo.Close() |
|
|
|
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, baseRepo, doer) |
|
if err != nil { |
|
log.Error("GetUserRepoPermission[%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), |
|
|
|
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.GetUserRepoPermission(ctx, pr.HeadRepo, doer) |
|
if err != nil { |
|
log.Error("GetUserRepoPermission[%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(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(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 |
|
}
|
|
|