From 89651b74caefd72c9545a3af90d2da6a7e734d7b Mon Sep 17 00:00:00 2001 From: yystopf Date: Tue, 7 Mar 2023 15:20:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=8C=87=E6=B4=BE=E4=BA=BA=E5=91=98=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=AD=A3=E5=B8=B8=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/hat.go | 3 +- routers/hat/repo/pull.go | 193 +++++++++++++++++++++++++++++++++++++++ services/issue/issue.go | 56 ++++++++++++ 3 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 services/issue/issue.go diff --git a/routers/hat/hat.go b/routers/hat/hat.go index b5f1096..e047e27 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -98,7 +98,8 @@ func Routers(ctx gocontext.Context) *web.Route { repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff) m.Group("/pulls", func() { m.Group("/{index}", func() { - m.Combo("").Get(repo.GetPullRequest) + m.Combo("").Get(repo.GetPullRequest). + Patch(bind(gitea_api.EditPullRequestOption{}), repo.EditPullRequest) m.Get("/commits", context.RepoRef(), repo.GetPullCommits) m.Get("/files", context.RepoRef(), repo.GetPullFiles) m.Group("/versions", func() { diff --git a/routers/hat/repo/pull.go b/routers/hat/repo/pull.go index 761be71..68e1977 100644 --- a/routers/hat/repo/pull.go +++ b/routers/hat/repo/pull.go @@ -1,22 +1,32 @@ package repo import ( + "errors" + "fmt" "net/http" "strings" + "time" + "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/gitdiff" "code.gitea.io/gitea/modules/context" + gitea_convert "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/setting" gitea_api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" + issue_service "code.gitea.io/gitea/services/issue" + pull_service "code.gitea.io/gitea/services/pull" "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert" hat_git "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git" hat_pull_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/pull" @@ -28,6 +38,189 @@ func CreatePrVersion(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } +func EditPullRequest(ctx *context.APIContext) { + form := web.GetForm(ctx).(*gitea_api.EditPullRequestOption) + 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 + } + + err = pr.LoadIssue() + if err != nil { + ctx.Error(http.StatusInternalServerError, "LoadIssue", err) + return + } + issue := pr.Issue + issue.Repo = ctx.Repo.Repository + + if err := issue.LoadAttributes(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) + return + } + + if !issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWrite(unit.TypePullRequests) { + ctx.Status(http.StatusForbidden) + return + } + + oldTitle := issue.Title + if len(form.Title) > 0 { + issue.Title = form.Title + } + if len(form.Body) > 0 { + issue.Content = form.Body + } + + // Update or remove deadline if set + if form.Deadline != nil || form.RemoveDeadline != nil { + var deadlineUnix timeutil.TimeStamp + if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() { + deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(), + 23, 59, 59, 0, form.Deadline.Location()) + deadlineUnix = timeutil.TimeStamp(deadline.Unix()) + } + + if err := issues_model.UpdateIssueDeadline(issue, deadlineUnix, ctx.Doer); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err) + return + } + issue.DeadlineUnix = deadlineUnix + } + + // Add/delete assignees + + // Deleting is done the GitHub way (quote from their api documentation): + // https://developer.github.com/v3/issues/#edit-an-issue + // "assignees" (array): Logins for Users to assign to this issue. + // Pass one or more user logins to replace the set of assignees on this Issue. + // Send an empty array ([]) to clear all assignees from the Issue. + + if ctx.Repo.CanWrite(unit.TypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) { + err = issue_service.UpdateAssignees(issue, form.Assignee, form.Assignees, ctx.Doer) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err)) + } else { + ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err) + } + return + } + } + + if ctx.Repo.CanWrite(unit.TypePullRequests) && form.Milestone != 0 && + issue.MilestoneID != form.Milestone { + oldMilestoneID := issue.MilestoneID + issue.MilestoneID = form.Milestone + if err = issue_service.ChangeMilestoneAssign(issue, ctx.Doer, oldMilestoneID); err != nil { + ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err) + return + } + } + + if ctx.Repo.CanWrite(unit.TypePullRequests) && form.Labels != nil { + labels, err := issues_model.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDsError", err) + return + } + + if ctx.Repo.Owner.IsOrganization() { + orgLabels, err := issues_model.GetLabelsInOrgByIDs(ctx.Repo.Owner.ID, form.Labels) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetLabelsInOrgByIDs", err) + return + } + + labels = append(labels, orgLabels...) + } + + if err = issues_model.ReplaceIssueLabels(issue, labels, ctx.Doer); err != nil { + ctx.Error(http.StatusInternalServerError, "ReplaceLabelsError", err) + return + } + } + + if form.State != nil { + if pr.HasMerged { + ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged") + return + } + issue.IsClosed = gitea_api.StateClosed == gitea_api.StateType(*form.State) + } + statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(issue, ctx.Doer) + if err != nil { + if issues_model.IsErrDependenciesLeft(err) { + ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") + return + } + ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) + return + } + + if titleChanged { + notification.NotifyIssueChangeTitle(ctx.Doer, issue, oldTitle) + } + + if statusChangeComment != nil { + notification.NotifyIssueChangeStatus(ctx.Doer, issue, statusChangeComment, issue.IsClosed) + } + + // change pull target branch + if !pr.HasMerged && len(form.Base) != 0 && form.Base != pr.BaseBranch { + if !ctx.Repo.GitRepo.IsBranchExist(form.Base) { + ctx.Error(http.StatusNotFound, "NewBaseBranchNotExist", fmt.Errorf("new base '%s' not exist", form.Base)) + return + } + if err := pull_service.ChangeTargetBranch(ctx, pr, ctx.Doer, form.Base); err != nil { + if issues_model.IsErrPullRequestAlreadyExists(err) { + ctx.Error(http.StatusConflict, "IsErrPullRequestAlreadyExists", err) + return + } else if issues_model.IsErrIssueIsClosed(err) { + ctx.Error(http.StatusUnprocessableEntity, "IsErrIssueIsClosed", err) + return + } else if models.IsErrPullRequestHasMerged(err) { + ctx.Error(http.StatusConflict, "IsErrPullRequestHasMerged", err) + return + } else { + ctx.InternalServerError(err) + } + return + } + notification.NotifyPullRequestChangeTargetBranch(ctx.Doer, pr, form.Base) + } + + // update allow edits + if form.AllowMaintainerEdit != nil { + if err := pull_service.SetAllowEdits(ctx, ctx.Doer, pr, *form.AllowMaintainerEdit); err != nil { + if errors.Is(pull_service.ErrUserHasNoPermissionForAction, err) { + ctx.Error(http.StatusForbidden, "SetAllowEdits", fmt.Sprintf("SetAllowEdits: %s", err)) + return + } + ctx.ServerError("SetAllowEdits", err) + return + } + } + + // Refetch from database + pr, err = issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, pr.Index) + if err != nil { + if issues_model.IsErrPullRequestNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err) + } + return + } + + // TODO this should be 200, not 201 + ctx.JSON(http.StatusCreated, gitea_convert.ToAPIPullRequest(ctx, pr, ctx.Doer)) +} + func GetPullRequest(ctx *context.APIContext) { pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { diff --git a/services/issue/issue.go b/services/issue/issue.go new file mode 100644 index 0000000..9343f87 --- /dev/null +++ b/services/issue/issue.go @@ -0,0 +1,56 @@ +package issue + +import ( + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + user_model "code.gitea.io/gitea/models/user" + gitea_issue_service "code.gitea.io/gitea/services/issue" +) + +func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssignees []string, doer *user_model.User) (err error) { + var allNewAssignees []*user_model.User + // Keep the old assignee thingy for compatibility reasons + if oneAssignee != "" { + // Prevent double adding assignees + var isDouble bool + for _, assignee := range multipleAssignees { + if assignee == oneAssignee { + isDouble = true + break + } + } + + if !isDouble { + multipleAssignees = append(multipleAssignees, oneAssignee) + } + } + + // Loop through all assignees to add them + for _, assigneeName := range multipleAssignees { + assignee, err := user_model.GetUserByName(db.DefaultContext, assigneeName) + if err != nil { + return err + } + + allNewAssignees = append(allNewAssignees, assignee) + } + + // Delete all old assignees not passed + if err = gitea_issue_service.DeleteNotPassedAssignee(issue, doer, allNewAssignees); err != nil { + return err + } + + // Add all new assignees + // Update the assignee. The function will check if the user exists, is already + // assigned (which he shouldn't as we deleted all assignees before) and + // has access to the repo. + for _, assignee := range allNewAssignees { + // Extra method to prevent double adding (which would result in removing) + err = gitea_issue_service.AddAssigneeIfNotAssigned(issue, doer, assignee.ID) + if err != nil { + return err + } + } + + return err +}