diff --git a/modules/convert/pull.go b/modules/convert/pull.go new file mode 100644 index 0000000..4fcfa8c --- /dev/null +++ b/modules/convert/pull.go @@ -0,0 +1,104 @@ +package convert + +import ( + "context" + "errors" + "strings" + + "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/user" + gitea_convert "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs" +) + +func ToAPIPullRequest(ctx context.Context, gitRepo *git.Repository, pr *issues.PullRequest, doer *user.User) (*hat_api.PullRequest, error) { + giteaAPIPullrequest := gitea_convert.ToAPIPullRequest(ctx, pr, doer) + hatAPIPullrequest := &hat_api.PullRequest{ + ID: giteaAPIPullrequest.ID, + URL: giteaAPIPullrequest.URL, + Index: giteaAPIPullrequest.Index, + Poster: giteaAPIPullrequest.Poster, + Title: giteaAPIPullrequest.Title, + Body: giteaAPIPullrequest.Body, + Labels: giteaAPIPullrequest.Labels, + Milestone: giteaAPIPullrequest.Milestone, + Assignee: giteaAPIPullrequest.Assignee, + Assignees: giteaAPIPullrequest.Assignees, + State: giteaAPIPullrequest.State, + IsLocked: giteaAPIPullrequest.IsLocked, + Comments: giteaAPIPullrequest.Comments, + HTMLURL: giteaAPIPullrequest.HTMLURL, + DiffURL: giteaAPIPullrequest.DiffURL, + PatchURL: giteaAPIPullrequest.PatchURL, + Mergeable: giteaAPIPullrequest.Mergeable, + HasMerged: giteaAPIPullrequest.HasMerged, + Merged: giteaAPIPullrequest.Merged, + MergedCommitID: giteaAPIPullrequest.MergedCommitID, + MergedBy: giteaAPIPullrequest.MergedBy, + AllowMaintainerEdit: giteaAPIPullrequest.AllowMaintainerEdit, + Base: giteaAPIPullrequest.Base, + Head: giteaAPIPullrequest.Head, + MergeBase: giteaAPIPullrequest.MergeBase, + Deadline: giteaAPIPullrequest.Deadline, + Created: giteaAPIPullrequest.Created, + Updated: giteaAPIPullrequest.Updated, + Closed: giteaAPIPullrequest.Closed, + } + issue := pr.Issue + if issue == nil { + return hatAPIPullrequest, errors.New("pullrequest's issue is blank.") + } + + var compareInfo *git.CompareInfo + var err error + if pr.HasMerged { + var baseCommit string + if pr.MergeBase == "" { + var commitSHA, parentCommit string + // If there is a head or a patch file, and it is readable, grab info + commitSHA, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil { + // Head File does not exist, try the patch + commitSHA, err = gitRepo.ReadPatchCommit(pr.Index) + if err == nil { + // Recreate pull head in files for next time + if err := gitRepo.SetReference(pr.GetGitRefName(), commitSHA); err != nil { + log.Error("Could not write head file", err) + } + } else { + // There is no history available + log.Trace("No history file available for PR %d", pr.Index) + } + } + if commitSHA != "" { + // Get immediate parent of the first commit in the patch, grab history back + parentCommit, _, err = git.NewCommand(ctx, "rev-list", "-1", "--skip=1", commitSHA).RunStdString(&git.RunOpts{Dir: gitRepo.Path}) + if err == nil { + parentCommit = strings.TrimSpace(parentCommit) + } + // Special case on Git < 2.25 that doesn't fail on immediate empty history + if err != nil || parentCommit == "" { + log.Info("No known parent commit for PR %d, error: %v", pr.Index, err) + // bring at least partial history if it can work + parentCommit = commitSHA + } + } + baseCommit = parentCommit + } else { + baseCommit = pr.MergeBase + } + compareInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, baseCommit, pr.GetGitRefName(), false, false) + } else { + compareInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, pr.MergeBase, pr.GetGitRefName(), false, false) + + } + if err != nil { + return hatAPIPullrequest, err + } + hatAPIPullrequest.CommitNum = len(compareInfo.Commits) + hatAPIPullrequest.ChangedFiles = compareInfo.NumFiles + + return hatAPIPullrequest, nil +} diff --git a/modules/structs/pull.go b/modules/structs/pull.go new file mode 100644 index 0000000..92eb5f4 --- /dev/null +++ b/modules/structs/pull.go @@ -0,0 +1,41 @@ +package structs + +import ( + "time" + + gitea_api "code.gitea.io/gitea/modules/structs" +) + +type PullRequest struct { + ID int64 `json:"id"` + URL string `json:"url"` + Index int64 `json:"number"` + Poster *gitea_api.User `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*gitea_api.Label `json:"labels"` + Milestone *gitea_api.Milestone `json:"milestone"` + Assignee *gitea_api.User `json:"assignee"` + Assignees []*gitea_api.User `json:"assignees"` + State gitea_api.StateType `json:"state"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + Mergeable bool `json:"mergeable"` + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` + MergedCommitID *string `json:"merge_commit_sha"` + MergedBy *gitea_api.User `json:"merged_by"` + AllowMaintainerEdit bool `json:"allow_maintainer_edit"` + Base *gitea_api.PRBranchInfo `json:"base"` + Head *gitea_api.PRBranchInfo `json:"head"` + MergeBase string `json:"merge_base"` + Deadline *time.Time `json:"due_date"` + Created *time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + CommitNum int `json:"commit_num"` + ChangedFiles int `json:"changed_files"` +} diff --git a/routers/hat/hat.go b/routers/hat/hat.go index b13e257..e3fd9bd 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -11,6 +11,7 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" @@ -78,6 +79,11 @@ func Routers() *web.Route { m.Get("/*", repo.GetReadmeContentsByPath) }) m.Get("/commits_slice", repo.GetAllCommitsSliceByTime) + m.Group("/pulls", func() { + m.Group("/{index}", func() { + m.Combo("").Get(repo.GetPullRequest) + }) + }, mustAllowPulls, reqRepoReader(unit_model.TypeCode), context.ReferencesGitRepo()) }, repoAssignment()) }) }) @@ -202,3 +208,26 @@ func reqRepoReader(unitType unit_model.Type) func(ctx *context.APIContext) { } } } + +func mustAllowPulls(ctx *context.APIContext) { + if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) { + if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() { + if ctx.IsSigned { + log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+ + "User in Repo has Permissions: %-+v", + ctx.Doer, + unit.TypePullRequests, + ctx.Repo.Repository, + ctx.Repo.Permission) + } else { + log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+ + "Anonymous user in Repo has Permissions: %-+v", + unit.TypePullRequests, + ctx.Repo.Repository, + ctx.Repo.Permission) + } + } + ctx.NotFound() + return + } +} diff --git a/routers/hat/repo/pull.go b/routers/hat/repo/pull.go new file mode 100644 index 0000000..00fd9ae --- /dev/null +++ b/routers/hat/repo/pull.go @@ -0,0 +1,36 @@ +package repo + +import ( + "net/http" + + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/context" + "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert" +) + +func GetPullRequest(ctx *context.APIContext) { + pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if issues_model.IsErrPullRequestNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err) + } + return + } + + if err = pr.LoadBaseRepoCtx(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err) + return + } + if err = pr.LoadHeadRepoCtx(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) + return + } + apiResult, err := convert.ToAPIPullRequest(ctx, ctx.Repo.GitRepo, pr, ctx.Doer) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ToAPIPullRequest", err) + return + } + ctx.JSON(http.StatusOK, apiResult) +}