mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	[API] Pull Requests (#248)
This commit is contained in:
		 Kim "BKC" Carlbäcker
					Kim "BKC" Carlbäcker
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							d7ed78a919
						
					
				
				
					commit
					0f05470cb8
				
			| @@ -584,6 +584,28 @@ func (err ErrPullRequestNotExist) Error() string { | ||||
| 		err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch) | ||||
| } | ||||
|  | ||||
| // ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error | ||||
| type ErrPullRequestAlreadyExists struct { | ||||
| 	ID         int64 | ||||
| 	IssueID    int64 | ||||
| 	HeadRepoID int64 | ||||
| 	BaseRepoID int64 | ||||
| 	HeadBranch string | ||||
| 	BaseBranch string | ||||
| } | ||||
|  | ||||
| // IsErrPullRequestAlreadyExists checks if an error is a ErrPullRequestAlreadyExists. | ||||
| func IsErrPullRequestAlreadyExists(err error) bool { | ||||
| 	_, ok := err.(ErrPullRequestAlreadyExists) | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| // Error does pretty-printing :D | ||||
| func (err ErrPullRequestAlreadyExists) Error() string { | ||||
| 	return fmt.Sprintf("pull request already exists for these targets [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]", | ||||
| 		err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch) | ||||
| } | ||||
|  | ||||
| // _________                                       __ | ||||
| // \_   ___ \  ____   _____   _____   ____   _____/  |_ | ||||
| // /    \  \/ /  _ \ /     \ /     \_/ __ \ /    \   __\ | ||||
|   | ||||
| @@ -170,6 +170,22 @@ func (issue *Issue) HTMLURL() string { | ||||
| 	return fmt.Sprintf("%s/%s/%d", issue.Repo.HTMLURL(), path, issue.Index) | ||||
| } | ||||
|  | ||||
| // DiffURL returns the absolute URL to this diff | ||||
| func (issue *Issue) DiffURL() string { | ||||
| 	if issue.IsPull { | ||||
| 		return fmt.Sprintf("%s/pulls/%d.diff", issue.Repo.HTMLURL(), issue.Index) | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // PatchURL returns the absolute URL to this patch | ||||
| func (issue *Issue) PatchURL() string { | ||||
| 	if issue.IsPull { | ||||
| 		return fmt.Sprintf("%s/pulls/%d.patch", issue.Repo.HTMLURL(), issue.Index) | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // State returns string representation of issue status. | ||||
| func (issue *Issue) State() api.StateType { | ||||
| 	if issue.IsClosed { | ||||
|   | ||||
| @@ -121,7 +121,26 @@ func (pr *PullRequest) LoadIssue() (err error) { | ||||
| // Required - Issue | ||||
| // Optional - Merger | ||||
| func (pr *PullRequest) APIFormat() *api.PullRequest { | ||||
|  | ||||
| 	apiIssue := pr.Issue.APIFormat() | ||||
| 	baseBranch, _ := pr.BaseRepo.GetBranch(pr.BaseBranch) | ||||
| 	baseCommit, _ := baseBranch.GetCommit() | ||||
| 	headBranch, _ := pr.HeadRepo.GetBranch(pr.HeadBranch) | ||||
| 	headCommit, _ := headBranch.GetCommit() | ||||
| 	apiBaseBranchInfo := &api.PRBranchInfo{ | ||||
| 		Name:       pr.BaseBranch, | ||||
| 		Ref:        pr.BaseBranch, | ||||
| 		Sha:        baseCommit.ID.String(), | ||||
| 		RepoID:     pr.BaseRepoID, | ||||
| 		Repository: pr.BaseRepo.APIFormat(nil), | ||||
| 	} | ||||
| 	apiHeadBranchInfo := &api.PRBranchInfo{ | ||||
| 		Name:       pr.HeadBranch, | ||||
| 		Ref:        pr.HeadBranch, | ||||
| 		Sha:        headCommit.ID.String(), | ||||
| 		RepoID:     pr.HeadRepoID, | ||||
| 		Repository: pr.HeadRepo.APIFormat(nil), | ||||
| 	} | ||||
| 	apiPullRequest := &api.PullRequest{ | ||||
| 		ID:        pr.ID, | ||||
| 		Index:     pr.Index, | ||||
| @@ -134,7 +153,12 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { | ||||
| 		State:     apiIssue.State, | ||||
| 		Comments:  apiIssue.Comments, | ||||
| 		HTMLURL:   pr.Issue.HTMLURL(), | ||||
| 		DiffURL:   pr.Issue.DiffURL(), | ||||
| 		PatchURL:  pr.Issue.PatchURL(), | ||||
| 		HasMerged: pr.HasMerged, | ||||
| 		Base:      apiBaseBranchInfo, | ||||
| 		Head:      apiHeadBranchInfo, | ||||
| 		MergeBase: pr.MergeBase, | ||||
| 	} | ||||
|  | ||||
| 	if pr.Status != PullRequestStatusChecking { | ||||
| @@ -472,6 +496,46 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // PullRequestsOptions holds the options for PRs | ||||
| type PullRequestsOptions struct { | ||||
| 	Page        int | ||||
| 	State       string | ||||
| 	SortType    string | ||||
| 	Labels      []string | ||||
| 	MilestoneID int64 | ||||
| } | ||||
|  | ||||
| func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) *xorm.Session { | ||||
| 	sess := x.Where("pull_request.base_repo_id=?", baseRepoID) | ||||
|  | ||||
| 	sess.Join("INNER", "issue", "pull_request.issue_id = issue.id") | ||||
| 	switch opts.State { | ||||
| 	case "closed", "open": | ||||
| 		sess.And("issue.is_closed=?", opts.State == "closed") | ||||
| 	} | ||||
|  | ||||
| 	return sess | ||||
| } | ||||
|  | ||||
| // PullRequests returns all pull requests for a base Repo by the given conditions | ||||
| func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, int64, error) { | ||||
| 	if opts.Page <= 0 { | ||||
| 		opts.Page = 1 | ||||
| 	} | ||||
|  | ||||
| 	countSession := listPullRequestStatement(baseRepoID, opts) | ||||
| 	maxResults, err := countSession.Count(new(PullRequest)) | ||||
| 	if err != nil { | ||||
| 		log.Error(4, "Count PRs", err) | ||||
| 		return nil, maxResults, err | ||||
| 	} | ||||
|  | ||||
| 	prs := make([]*PullRequest, 0, ItemsPerPage) | ||||
| 	findSession := listPullRequestStatement(baseRepoID, opts) | ||||
| 	findSession.Limit(ItemsPerPage, (opts.Page-1)*ItemsPerPage) | ||||
| 	return prs, maxResults, findSession.Find(&prs) | ||||
| } | ||||
|  | ||||
| // GetUnmergedPullRequest returnss a pull request that is open and has not been merged | ||||
| // by given head/base and repo/branch. | ||||
| func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { | ||||
| @@ -512,6 +576,26 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ | ||||
| 		Find(&prs) | ||||
| } | ||||
|  | ||||
| // GetPullRequestByIndex returns a pull request by the given index | ||||
| func GetPullRequestByIndex(repoID int64, index int64) (*PullRequest, error) { | ||||
| 	pr := &PullRequest{ | ||||
| 		BaseRepoID: repoID, | ||||
| 		Index:      index, | ||||
| 	} | ||||
|  | ||||
| 	has, err := x.Get(pr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} else if !has { | ||||
| 		return nil, ErrPullRequestNotExist{0, repoID, index, 0, "", ""} | ||||
| 	} | ||||
|  | ||||
| 	pr.LoadAttributes() | ||||
| 	pr.LoadIssue() | ||||
|  | ||||
| 	return pr, nil | ||||
| } | ||||
|  | ||||
| func getPullRequestByID(e Engine, id int64) (*PullRequest, error) { | ||||
| 	pr := new(PullRequest) | ||||
| 	has, err := e.Id(id).Get(pr) | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/git" | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| @@ -103,3 +104,24 @@ func ExtractOwnerAndRepo() macaron.Handler { | ||||
| 		ctx.Data["Repository"] = repo | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ReferencesGitRepo injects the GitRepo into the Context | ||||
| func ReferencesGitRepo() macaron.Handler { | ||||
| 	return func(ctx *APIContext) { | ||||
| 		// Empty repository does not have reference information. | ||||
| 		if ctx.Repo.Repository.IsBare { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// For API calls. | ||||
| 		if ctx.Repo.GitRepo == nil { | ||||
| 			repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) | ||||
| 			gitRepo, err := git.OpenRepository(repoPath) | ||||
| 			if err != nil { | ||||
| 				ctx.Error(500, "RepoRef Invalid repo "+repoPath, err) | ||||
| 				return | ||||
| 			} | ||||
| 			ctx.Repo.GitRepo = gitRepo | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -167,6 +167,13 @@ func mustEnableIssues(ctx *context.APIContext) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func mustAllowPulls(ctx *context.Context) { | ||||
| 	if !ctx.Repo.Repository.AllowsPulls() { | ||||
| 		ctx.Status(404) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers all v1 APIs routes to web application. | ||||
| // FIXME: custom form error response | ||||
| func RegisterRoutes(m *macaron.Macaron) { | ||||
| @@ -305,6 +312,14 @@ func RegisterRoutes(m *macaron.Macaron) { | ||||
| 						Delete(reqRepoWriter(), repo.DeleteMilestone) | ||||
| 				}) | ||||
| 				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) | ||||
| 				m.Group("/pulls", func() { | ||||
| 					m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).Post(reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) | ||||
| 					m.Group("/:index", func() { | ||||
| 						m.Combo("").Get(repo.GetPullRequest).Patch(reqRepoWriter(), bind(api.EditPullRequestOption{}), repo.EditPullRequest) | ||||
| 						m.Combo("/merge").Get(repo.IsPullRequestMerged).Post(reqRepoWriter(), repo.MergePullRequest) | ||||
| 					}) | ||||
|  | ||||
| 				}, mustAllowPulls, context.ReferencesGitRepo()) | ||||
| 			}, repoAssignment()) | ||||
| 		}, reqToken()) | ||||
|  | ||||
|   | ||||
							
								
								
									
										433
									
								
								routers/api/v1/repo/pull.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										433
									
								
								routers/api/v1/repo/pull.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,433 @@ | ||||
| // Copyright 2016 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package repo | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/git" | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
|  | ||||
| 	api "code.gitea.io/sdk/gitea" | ||||
| ) | ||||
|  | ||||
| // ListPullRequests returns a list of all PRs | ||||
| func ListPullRequests(ctx *context.APIContext, form api.ListPullRequestsOptions) { | ||||
| 	prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{ | ||||
| 		Page:        ctx.QueryInt("page"), | ||||
| 		State:       ctx.QueryTrim("state"), | ||||
| 		SortType:    ctx.QueryTrim("sort"), | ||||
| 		Labels:      ctx.QueryStrings("labels"), | ||||
| 		MilestoneID: ctx.QueryInt64("milestone"), | ||||
| 	}) | ||||
|  | ||||
| 	/*prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{ | ||||
| 		Page:  form.Page, | ||||
| 		State: form.State, | ||||
| 	})*/ | ||||
| 	if err != nil { | ||||
| 		ctx.Error(500, "PullRequests", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	apiPrs := make([]*api.PullRequest, len(prs)) | ||||
| 	for i := range prs { | ||||
| 		prs[i].LoadIssue() | ||||
| 		prs[i].LoadAttributes() | ||||
| 		prs[i].GetBaseRepo() | ||||
| 		prs[i].GetHeadRepo() | ||||
| 		apiPrs[i] = prs[i].APIFormat() | ||||
| 	} | ||||
|  | ||||
| 	ctx.SetLinkHeader(int(maxResults), models.ItemsPerPage) | ||||
| 	ctx.JSON(200, &apiPrs) | ||||
| } | ||||
|  | ||||
| // GetPullRequest returns a single PR based on index | ||||
| func GetPullRequest(ctx *context.APIContext) { | ||||
| 	pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Status(404) | ||||
| 		} else { | ||||
| 			ctx.Error(500, "GetPullRequestByIndex", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pr.GetBaseRepo() | ||||
| 	pr.GetHeadRepo() | ||||
| 	ctx.JSON(200, pr.APIFormat()) | ||||
| } | ||||
|  | ||||
| // CreatePullRequest does what it says | ||||
| func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption) { | ||||
| 	var ( | ||||
| 		repo        = ctx.Repo.Repository | ||||
| 		labelIDs    []int64 | ||||
| 		assigneeID  int64 | ||||
| 		milestoneID int64 | ||||
| 	) | ||||
|  | ||||
| 	// Get repo/branch information | ||||
| 	headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := parseCompareInfo(ctx, form) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Check if another PR exists with the same targets | ||||
| 	existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) | ||||
| 	if err != nil { | ||||
| 		if !models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Error(500, "GetUnmergedPullRequest", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		err = models.ErrPullRequestAlreadyExists{ | ||||
| 			ID:         existingPr.ID, | ||||
| 			IssueID:    existingPr.Index, | ||||
| 			HeadRepoID: existingPr.HeadRepoID, | ||||
| 			BaseRepoID: existingPr.BaseRepoID, | ||||
| 			HeadBranch: existingPr.HeadBranch, | ||||
| 			BaseBranch: existingPr.BaseBranch, | ||||
| 		} | ||||
| 		ctx.Error(409, "GetUnmergedPullRequest", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(form.Labels) > 0 { | ||||
| 		labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(500, "GetLabelsInRepoByIDs", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		labelIDs = make([]int64, len(labels)) | ||||
| 		for i := range labels { | ||||
| 			labelIDs[i] = labels[i].ID | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if form.Milestone > 0 { | ||||
| 		milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrMilestoneNotExist(err) { | ||||
| 				ctx.Status(404) | ||||
| 			} else { | ||||
| 				ctx.Error(500, "GetMilestoneByRepoID", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		milestoneID = milestone.ID | ||||
| 	} | ||||
|  | ||||
| 	if len(form.Assignee) > 0 { | ||||
| 		assigneeUser, err := models.GetUserByName(form.Assignee) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrUserNotExist(err) { | ||||
| 				ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee)) | ||||
| 			} else { | ||||
| 				ctx.Error(500, "GetUserByName", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		assignee, err := repo.GetAssigneeByID(assigneeUser.ID) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(500, "GetAssigneeByID", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		assigneeID = assignee.ID | ||||
| 	} | ||||
|  | ||||
| 	patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(500, "GetPatch", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	prIssue := &models.Issue{ | ||||
| 		RepoID:      repo.ID, | ||||
| 		Index:       repo.NextIssueIndex(), | ||||
| 		Title:       form.Title, | ||||
| 		PosterID:    ctx.User.ID, | ||||
| 		Poster:      ctx.User, | ||||
| 		MilestoneID: milestoneID, | ||||
| 		AssigneeID:  assigneeID, | ||||
| 		IsPull:      true, | ||||
| 		Content:     form.Body, | ||||
| 	} | ||||
| 	pr := &models.PullRequest{ | ||||
| 		HeadRepoID:   headRepo.ID, | ||||
| 		BaseRepoID:   repo.ID, | ||||
| 		HeadUserName: headUser.Name, | ||||
| 		HeadBranch:   headBranch, | ||||
| 		BaseBranch:   baseBranch, | ||||
| 		HeadRepo:     headRepo, | ||||
| 		BaseRepo:     repo, | ||||
| 		MergeBase:    prInfo.MergeBase, | ||||
| 		Type:         models.PullRequestGitea, | ||||
| 	} | ||||
|  | ||||
| 	if err := models.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, patch); err != nil { | ||||
| 		ctx.Error(500, "NewPullRequest", err) | ||||
| 		return | ||||
| 	} else if err := pr.PushToBaseRepo(); err != nil { | ||||
| 		ctx.Error(500, "PushToBaseRepo", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID) | ||||
| 	ctx.JSON(201, pr.APIFormat()) | ||||
| } | ||||
|  | ||||
| // EditPullRequest does what it says | ||||
| func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | ||||
| 	pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Status(404) | ||||
| 		} else { | ||||
| 			ctx.Error(500, "GetPullRequestByIndex", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pr.LoadIssue() | ||||
| 	issue := pr.Issue | ||||
|  | ||||
| 	if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter() { | ||||
| 		ctx.Status(403) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(form.Title) > 0 { | ||||
| 		issue.Title = form.Title | ||||
| 	} | ||||
| 	if len(form.Body) > 0 { | ||||
| 		issue.Content = form.Body | ||||
| 	} | ||||
|  | ||||
| 	if ctx.Repo.IsWriter() && len(form.Assignee) > 0 && | ||||
| 		(issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(form.Assignee)) { | ||||
| 		if len(form.Assignee) == 0 { | ||||
| 			issue.AssigneeID = 0 | ||||
| 		} else { | ||||
| 			assignee, err := models.GetUserByName(form.Assignee) | ||||
| 			if err != nil { | ||||
| 				if models.IsErrUserNotExist(err) { | ||||
| 					ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee)) | ||||
| 				} else { | ||||
| 					ctx.Error(500, "GetUserByName", err) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
| 			issue.AssigneeID = assignee.ID | ||||
| 		} | ||||
|  | ||||
| 		if err = models.UpdateIssueUserByAssignee(issue); err != nil { | ||||
| 			ctx.Error(500, "UpdateIssueUserByAssignee", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if ctx.Repo.IsWriter() && form.Milestone != 0 && | ||||
| 		issue.MilestoneID != form.Milestone { | ||||
| 		oldMilestoneID := issue.MilestoneID | ||||
| 		issue.MilestoneID = form.Milestone | ||||
| 		if err = models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil { | ||||
| 			ctx.Error(500, "ChangeMilestoneAssign", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err = models.UpdateIssue(issue); err != nil { | ||||
| 		ctx.Error(500, "UpdateIssue", err) | ||||
| 		return | ||||
| 	} | ||||
| 	if form.State != nil { | ||||
| 		if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil { | ||||
| 			ctx.Error(500, "ChangeStatus", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Refetch from database | ||||
| 	pr, err = models.GetPullRequestByIndex(ctx.Repo.Repository.ID, pr.Index) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Status(404) | ||||
| 		} else { | ||||
| 			ctx.Error(500, "GetPullRequestByIndex", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ctx.JSON(201, pr.APIFormat()) | ||||
| } | ||||
|  | ||||
| // IsPullRequestMerged checks if a PR exists given an index | ||||
| //  - Returns 204 if it exists | ||||
| //    Otherwise 404 | ||||
| func IsPullRequestMerged(ctx *context.APIContext) { | ||||
| 	pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Status(404) | ||||
| 		} else { | ||||
| 			ctx.Error(500, "GetPullRequestByIndex", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if pr.HasMerged { | ||||
| 		ctx.Status(204) | ||||
| 	} | ||||
| 	ctx.Status(404) | ||||
| } | ||||
|  | ||||
| // MergePullRequest merges a PR given an index | ||||
| func MergePullRequest(ctx *context.APIContext) { | ||||
| 	pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Handle(404, "GetPullRequestByIndex", err) | ||||
| 		} else { | ||||
| 			ctx.Error(500, "GetPullRequestByIndex", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err = pr.GetHeadRepo(); err != nil { | ||||
| 		ctx.Handle(500, "GetHeadRepo", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pr.LoadIssue() | ||||
| 	pr.Issue.Repo = ctx.Repo.Repository | ||||
|  | ||||
| 	if ctx.IsSigned { | ||||
| 		// Update issue-user. | ||||
| 		if err = pr.Issue.ReadBy(ctx.User.ID); err != nil { | ||||
| 			ctx.Error(500, "ReadBy", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if pr.Issue.IsClosed { | ||||
| 		ctx.Status(404) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !pr.CanAutoMerge() || pr.HasMerged { | ||||
| 		ctx.Status(405) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := pr.Merge(ctx.User, ctx.Repo.GitRepo); err != nil { | ||||
| 		ctx.Error(500, "Merge", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Trace("Pull request merged: %d", pr.ID) | ||||
| 	ctx.Status(200) | ||||
| } | ||||
|  | ||||
| func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { | ||||
| 	baseRepo := ctx.Repo.Repository | ||||
|  | ||||
| 	// Get compared branches information | ||||
| 	// format: <base branch>...[<head repo>:]<head branch> | ||||
| 	// base<-head: master...head:feature | ||||
| 	// same repo: master...feature | ||||
|  | ||||
| 	// TODO: Validate form first? | ||||
|  | ||||
| 	baseBranch := form.Base | ||||
|  | ||||
| 	var ( | ||||
| 		headUser   *models.User | ||||
| 		headBranch string | ||||
| 		isSameRepo bool | ||||
| 		err        error | ||||
| 	) | ||||
|  | ||||
| 	// If there is no head repository, it means pull request between same repository. | ||||
| 	headInfos := strings.Split(form.Head, ":") | ||||
| 	if len(headInfos) == 1 { | ||||
| 		isSameRepo = true | ||||
| 		headUser = ctx.Repo.Owner | ||||
| 		headBranch = headInfos[0] | ||||
|  | ||||
| 	} else if len(headInfos) == 2 { | ||||
| 		headUser, err = models.GetUserByName(headInfos[0]) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrUserNotExist(err) { | ||||
| 				ctx.Handle(404, "GetUserByName", nil) | ||||
| 			} else { | ||||
| 				ctx.Handle(500, "GetUserByName", err) | ||||
| 			} | ||||
| 			return nil, nil, nil, nil, "", "" | ||||
| 		} | ||||
| 		headBranch = headInfos[1] | ||||
|  | ||||
| 	} else { | ||||
| 		ctx.Status(404) | ||||
| 		return nil, nil, nil, nil, "", "" | ||||
| 	} | ||||
|  | ||||
| 	ctx.Repo.PullRequest.SameRepo = isSameRepo | ||||
| 	log.Info("Base branch: %s", baseBranch) | ||||
| 	log.Info("Repo path: %s", ctx.Repo.GitRepo.Path) | ||||
| 	// Check if base branch is valid. | ||||
| 	if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) { | ||||
| 		ctx.Status(404) | ||||
| 		return nil, nil, nil, nil, "", "" | ||||
| 	} | ||||
|  | ||||
| 	// Check if current user has fork of repository or in the same repository. | ||||
| 	headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID) | ||||
| 	if !has && !isSameRepo { | ||||
| 		log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) | ||||
| 		ctx.Status(404) | ||||
| 		return nil, nil, nil, nil, "", "" | ||||
| 	} | ||||
|  | ||||
| 	var headGitRepo *git.Repository | ||||
| 	if isSameRepo { | ||||
| 		headRepo = ctx.Repo.Repository | ||||
| 		headGitRepo = ctx.Repo.GitRepo | ||||
| 	} else { | ||||
| 		headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name)) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(500, "OpenRepository", err) | ||||
| 			return nil, nil, nil, nil, "", "" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !ctx.User.IsWriterOfRepo(headRepo) && !ctx.User.IsAdmin { | ||||
| 		log.Trace("ParseCompareInfo[%d]: does not have write access or site admin", baseRepo.ID) | ||||
| 		ctx.Status(404) | ||||
| 		return nil, nil, nil, nil, "", "" | ||||
| 	} | ||||
|  | ||||
| 	// Check if head branch is valid. | ||||
| 	if !headGitRepo.IsBranchExist(headBranch) { | ||||
| 		ctx.Status(404) | ||||
| 		return nil, nil, nil, nil, "", "" | ||||
| 	} | ||||
|  | ||||
| 	prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(500, "GetPullRequestInfo", err) | ||||
| 		return nil, nil, nil, nil, "", "" | ||||
| 	} | ||||
|  | ||||
| 	return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch | ||||
| } | ||||
		Reference in New Issue
	
	Block a user