2016-12-02 04:10:39 -07:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-27 11:20:29 -07:00
// SPDX-License-Identifier: MIT
2016-12-02 04:10:39 -07:00
package repo
import (
2021-07-12 17:26:25 -06:00
"errors"
2016-12-02 04:10:39 -07:00
"fmt"
2021-07-02 06:19:57 -06:00
"math"
2018-07-17 15:23:58 -06:00
"net/http"
2021-07-02 06:19:57 -06:00
"strconv"
2016-12-02 04:10:39 -07:00
"strings"
2019-11-03 07:46:32 -07:00
"time"
2016-12-02 04:10:39 -07:00
"code.gitea.io/gitea/models"
2022-08-24 20:31:57 -06:00
activities_model "code.gitea.io/gitea/models/activities"
2023-01-16 01:00:22 -07:00
git_model "code.gitea.io/gitea/models/git"
2022-04-08 03:11:15 -06:00
issues_model "code.gitea.io/gitea/models/issues"
2022-05-11 04:09:36 -06:00
access_model "code.gitea.io/gitea/models/perm/access"
2022-05-07 11:05:52 -06:00
pull_model "code.gitea.io/gitea/models/pull"
2021-12-09 18:27:50 -07:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-09 12:57:58 -07:00
"code.gitea.io/gitea/models/unit"
2021-11-24 02:49:20 -07:00
user_model "code.gitea.io/gitea/models/user"
2024-03-21 09:07:35 -06:00
"code.gitea.io/gitea/modules/base"
2019-03-27 03:33:00 -06:00
"code.gitea.io/gitea/modules/git"
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 13:09:51 -07:00
"code.gitea.io/gitea/modules/gitrepo"
2016-12-02 04:10:39 -07:00
"code.gitea.io/gitea/modules/log"
2022-09-28 20:27:20 -06:00
"code.gitea.io/gitea/modules/setting"
2019-05-11 04:21:34 -06:00
api "code.gitea.io/gitea/modules/structs"
2019-08-15 08:46:21 -06:00
"code.gitea.io/gitea/modules/timeutil"
2021-01-26 08:36:53 -07:00
"code.gitea.io/gitea/modules/web"
2020-01-24 12:00:29 -07:00
"code.gitea.io/gitea/routers/api/v1/utils"
2021-12-10 01:14:24 -07:00
asymkey_service "code.gitea.io/gitea/services/asymkey"
2022-05-07 11:05:52 -06:00
"code.gitea.io/gitea/services/automerge"
2024-02-27 00:12:22 -07:00
"code.gitea.io/gitea/services/context"
2022-12-28 19:57:15 -07:00
"code.gitea.io/gitea/services/convert"
2021-04-06 13:44:05 -06:00
"code.gitea.io/gitea/services/forms"
2022-09-28 20:27:20 -06:00
"code.gitea.io/gitea/services/gitdiff"
2019-10-25 08:46:37 -06:00
issue_service "code.gitea.io/gitea/services/issue"
2023-09-05 12:37:47 -06:00
notify_service "code.gitea.io/gitea/services/notify"
2019-09-26 18:22:36 -06:00
pull_service "code.gitea.io/gitea/services/pull"
2021-07-12 17:26:25 -06:00
repo_service "code.gitea.io/gitea/services/repository"
2016-12-02 04:10:39 -07:00
)
// ListPullRequests returns a list of all PRs
2021-01-26 08:36:53 -07:00
func ListPullRequests ( ctx * context . APIContext ) {
2017-11-13 00:02:25 -07:00
// swagger:operation GET /repos/{owner}/{repo}/pulls repository repoListPullRequests
// ---
// summary: List a repo's pull requests
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
2018-10-20 21:40:42 -06:00
// - name: state
// in: query
// description: "State of pull request: open or closed (optional)"
// type: string
// enum: [closed, open, all]
// - name: sort
// in: query
// description: "Type of sort"
// type: string
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
// - name: milestone
// in: query
// description: "ID of the milestone"
// type: integer
// format: int64
// - name: labels
// in: query
// description: "Label IDs"
// type: array
// collectionFormat: multi
// items:
// type: integer
// format: int64
2020-01-24 12:00:29 -07:00
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
2020-06-08 22:57:38 -06:00
// description: page size of results
2020-01-24 12:00:29 -07:00
// type: integer
2017-11-13 00:02:25 -07:00
// responses:
// "200":
// "$ref": "#/responses/PullRequestList"
2023-09-12 20:37:54 -06:00
// "404":
// "$ref": "#/responses/notFound"
2019-12-20 10:07:12 -07:00
2024-03-21 09:07:35 -06:00
labelIDs , err := base . StringsToInt64s ( ctx . FormStrings ( "labels" ) )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "PullRequests" , err )
return
}
2020-01-24 12:00:29 -07:00
listOptions := utils . GetListOptions ( ctx )
2023-09-15 00:13:19 -06:00
prs , maxResults , err := issues_model . PullRequests ( ctx , ctx . Repo . Repository . ID , & issues_model . PullRequestsOptions {
2020-01-24 12:00:29 -07:00
ListOptions : listOptions ,
2021-07-28 19:42:15 -06:00
State : ctx . FormTrim ( "state" ) ,
SortType : ctx . FormTrim ( "sort" ) ,
2024-03-21 09:07:35 -06:00
Labels : labelIDs ,
2021-07-28 19:42:15 -06:00
MilestoneID : ctx . FormInt64 ( "milestone" ) ,
2016-12-02 04:10:39 -07:00
} )
if err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "PullRequests" , err )
2016-12-02 04:10:39 -07:00
return
}
apiPrs := make ( [ ] * api . PullRequest , len ( prs ) )
2024-05-31 06:10:11 -06:00
// NOTE: load repository first, so that issue.Repo will be filled with pr.BaseRepo
if err := prs . LoadRepositories ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadRepositories" , err )
return
}
issueList , err := prs . LoadIssues ( ctx )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadIssues" , err )
return
}
if err := issueList . LoadLabels ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadLabels" , err )
return
}
if err := issueList . LoadPosters ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadPoster" , err )
return
}
if err := issueList . LoadAttachments ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadAttachments" , err )
return
}
if err := issueList . LoadMilestones ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadMilestones" , err )
return
}
if err := issueList . LoadAssignees ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadAssignees" , err )
return
}
2016-12-02 04:10:39 -07:00
for i := range prs {
2022-03-22 01:03:22 -06:00
apiPrs [ i ] = convert . ToAPIPullRequest ( ctx , prs [ i ] , ctx . Doer )
2016-12-02 04:10:39 -07:00
}
2020-01-24 12:00:29 -07:00
ctx . SetLinkHeader ( int ( maxResults ) , listOptions . PageSize )
2021-08-12 06:43:08 -06:00
ctx . SetTotalCountHeader ( maxResults )
2019-12-20 10:07:12 -07:00
ctx . JSON ( http . StatusOK , & apiPrs )
2016-12-02 04:10:39 -07:00
}
// GetPullRequest returns a single PR based on index
func GetPullRequest ( ctx * context . APIContext ) {
2017-11-13 00:02:25 -07:00
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index} repository repoGetPullRequest
// ---
// summary: Get a pull request
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to get
// type: integer
2018-10-20 21:40:42 -06:00
// format: int64
2017-11-13 00:02:25 -07:00
// required: true
// responses:
// "200":
// "$ref": "#/responses/PullRequest"
2020-01-09 04:56:32 -07:00
// "404":
// "$ref": "#/responses/notFound"
2019-12-20 10:07:12 -07:00
2022-06-13 03:37:59 -06:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2016-12-02 04:10:39 -07:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2016-12-02 04:10:39 -07:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 04:10:39 -07:00
}
return
}
2022-11-19 01:12:33 -07:00
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2020-03-02 15:31:55 -07:00
ctx . Error ( http . StatusInternalServerError , "LoadBaseRepo" , err )
2017-11-04 12:10:01 -06:00
return
}
2022-11-19 01:12:33 -07:00
if err = pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-03-02 15:31:55 -07:00
ctx . Error ( http . StatusInternalServerError , "LoadHeadRepo" , err )
2017-11-04 12:10:01 -06:00
return
}
2022-03-22 01:03:22 -06:00
ctx . JSON ( http . StatusOK , convert . ToAPIPullRequest ( ctx , pr , ctx . Doer ) )
2016-12-02 04:10:39 -07:00
}
2024-02-25 19:39:01 -07:00
// GetPullRequest returns a single PR based on index
func GetPullRequestByBaseHead ( ctx * context . APIContext ) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{base}/{head} repository repoGetPullRequestByBaseHead
// ---
// summary: Get a pull request by base and head
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: base
// in: path
// description: base of the pull request to get
// type: string
// required: true
// - name: head
// in: path
// description: head of the pull request to get
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/PullRequest"
// "404":
// "$ref": "#/responses/notFound"
var headRepoID int64
var headBranch string
head := ctx . Params ( "*" )
if strings . Contains ( head , ":" ) {
split := strings . SplitN ( head , ":" , 2 )
headBranch = split [ 1 ]
var owner , name string
if strings . Contains ( split [ 0 ] , "/" ) {
split = strings . Split ( split [ 0 ] , "/" )
owner = split [ 0 ]
name = split [ 1 ]
} else {
owner = split [ 0 ]
name = ctx . Repo . Repository . Name
}
repo , err := repo_model . GetRepositoryByOwnerAndName ( ctx , owner , name )
if err != nil {
if repo_model . IsErrRepoNotExist ( err ) {
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetRepositoryByOwnerName" , err )
}
return
}
headRepoID = repo . ID
} else {
headRepoID = ctx . Repo . Repository . ID
headBranch = head
}
pr , err := issues_model . GetPullRequestByBaseHeadInfo ( ctx , ctx . Repo . Repository . ID , headRepoID , ctx . Params ( ":base" ) , headBranch )
if err != nil {
if issues_model . IsErrPullRequestNotExist ( err ) {
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByBaseHeadInfo" , err )
}
return
}
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadBaseRepo" , err )
return
}
if err = pr . LoadHeadRepo ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadHeadRepo" , err )
return
}
ctx . JSON ( http . StatusOK , convert . ToAPIPullRequest ( ctx , pr , ctx . Doer ) )
}
2021-09-21 17:04:53 -06:00
// DownloadPullDiffOrPatch render a pull's raw diff or patch
func DownloadPullDiffOrPatch ( ctx * context . APIContext ) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.{diffType} repository repoDownloadPullDiffOrPatch
2020-06-05 05:03:12 -06:00
// ---
2021-09-21 17:04:53 -06:00
// summary: Get a pull request diff or patch
2020-06-05 05:03:12 -06:00
// produces:
// - text/plain
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to get
// type: integer
// format: int64
// required: true
2021-09-21 17:04:53 -06:00
// - name: diffType
2020-06-05 05:03:12 -06:00
// in: path
2021-09-21 17:04:53 -06:00
// description: whether the output is diff or patch
2020-06-05 05:03:12 -06:00
// type: string
2021-09-21 17:04:53 -06:00
// enum: [diff, patch]
2020-06-05 05:03:12 -06:00
// required: true
2021-09-27 15:09:49 -06:00
// - name: binary
// in: query
// description: whether to include binary file changes. if true, the diff is applicable with `git apply`
// type: boolean
2020-06-05 05:03:12 -06:00
// responses:
// "200":
// "$ref": "#/responses/string"
// "404":
// "$ref": "#/responses/notFound"
2022-06-13 03:37:59 -06:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2020-06-05 05:03:12 -06:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2020-06-05 05:03:12 -06:00
ctx . NotFound ( )
} else {
ctx . InternalServerError ( err )
}
return
}
2021-09-21 17:04:53 -06:00
var patch bool
if ctx . Params ( ":diffType" ) == "diff" {
patch = false
} else {
patch = true
}
2020-06-05 05:03:12 -06:00
2021-09-27 15:09:49 -06:00
binary := ctx . FormBool ( "binary" )
2022-01-19 16:26:57 -07:00
if err := pull_service . DownloadDiffOrPatch ( ctx , pr , ctx , patch , binary ) ; err != nil {
2020-06-05 05:03:12 -06:00
ctx . InternalServerError ( err )
return
}
}
2016-12-02 04:10:39 -07:00
// CreatePullRequest does what it says
2021-01-26 08:36:53 -07:00
func CreatePullRequest ( ctx * context . APIContext ) {
2017-11-13 00:02:25 -07:00
// swagger:operation POST /repos/{owner}/{repo}/pulls repository repoCreatePullRequest
// ---
// summary: Create a pull request
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreatePullRequestOption"
// responses:
// "201":
// "$ref": "#/responses/PullRequest"
2024-03-04 01:16:03 -07:00
// "403":
// "$ref": "#/responses/forbidden"
2023-09-12 20:37:54 -06:00
// "404":
// "$ref": "#/responses/notFound"
2019-12-20 10:07:12 -07:00
// "409":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2023-09-21 17:43:29 -06:00
// "423":
// "$ref": "#/responses/repoArchivedError"
2019-12-20 10:07:12 -07:00
2021-01-26 08:36:53 -07:00
form := * web . GetForm ( ctx ) . ( * api . CreatePullRequestOption )
2020-10-26 03:05:27 -06:00
if form . Head == form . Base {
ctx . Error ( http . StatusUnprocessableEntity , "BaseHeadSame" ,
"Invalid PullRequest: There are no changes between the head and the base" )
return
}
2016-12-02 04:10:39 -07:00
var (
repo = ctx . Repo . Repository
labelIDs [ ] int64
milestoneID int64
)
// Get repo/branch information
2024-06-11 12:47:45 -06:00
headRepo , headGitRepo , compareInfo , baseBranch , headBranch := parseCompareInfo ( ctx , form )
2016-12-02 04:10:39 -07:00
if ctx . Written ( ) {
return
}
2019-11-13 00:01:19 -07:00
defer headGitRepo . Close ( )
2016-12-02 04:10:39 -07:00
// Check if another PR exists with the same targets
2022-11-19 01:12:33 -07:00
existingPr , err := issues_model . GetUnmergedPullRequest ( ctx , headRepo . ID , ctx . Repo . Repository . ID , headBranch , baseBranch , issues_model . PullRequestFlowGithub )
2016-12-02 04:10:39 -07:00
if err != nil {
2022-06-13 03:37:59 -06:00
if ! issues_model . IsErrPullRequestNotExist ( err ) {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetUnmergedPullRequest" , err )
2016-12-02 04:10:39 -07:00
return
}
} else {
2022-06-13 03:37:59 -06:00
err = issues_model . ErrPullRequestAlreadyExists {
2016-12-02 04:10:39 -07:00
ID : existingPr . ID ,
IssueID : existingPr . Index ,
HeadRepoID : existingPr . HeadRepoID ,
BaseRepoID : existingPr . BaseRepoID ,
HeadBranch : existingPr . HeadBranch ,
BaseBranch : existingPr . BaseBranch ,
}
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusConflict , "GetUnmergedPullRequest" , err )
2016-12-02 04:10:39 -07:00
return
}
if len ( form . Labels ) > 0 {
2022-11-19 01:12:33 -07:00
labels , err := issues_model . GetLabelsInRepoByIDs ( ctx , ctx . Repo . Repository . ID , form . Labels )
2016-12-02 04:10:39 -07:00
if err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetLabelsInRepoByIDs" , err )
2016-12-02 04:10:39 -07:00
return
}
2023-08-29 09:47:26 -06:00
labelIDs = make ( [ ] int64 , 0 , len ( labels ) )
for _ , label := range labels {
labelIDs = append ( labelIDs , label . ID )
2016-12-02 04:10:39 -07:00
}
2020-04-20 07:10:45 -06:00
if ctx . Repo . Owner . IsOrganization ( ) {
2022-11-19 01:12:33 -07:00
orgLabels , err := issues_model . GetLabelsInOrgByIDs ( ctx , ctx . Repo . Owner . ID , form . Labels )
2020-04-20 07:10:45 -06:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetLabelsInOrgByIDs" , err )
return
}
2023-08-29 09:47:26 -06:00
orgLabelIDs := make ( [ ] int64 , 0 , len ( orgLabels ) )
for _ , orgLabel := range orgLabels {
orgLabelIDs = append ( orgLabelIDs , orgLabel . ID )
2020-04-20 07:10:45 -06:00
}
2023-08-29 09:47:26 -06:00
labelIDs = append ( labelIDs , orgLabelIDs ... )
2020-04-20 07:10:45 -06:00
}
2016-12-02 04:10:39 -07:00
}
if form . Milestone > 0 {
2022-04-08 03:11:15 -06:00
milestone , err := issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , form . Milestone )
2016-12-02 04:10:39 -07:00
if err != nil {
2022-04-08 03:11:15 -06:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2016-12-02 04:10:39 -07:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetMilestoneByRepoID" , err )
2016-12-02 04:10:39 -07:00
}
return
}
milestoneID = milestone . ID
}
2019-08-15 08:46:21 -06:00
var deadlineUnix timeutil . TimeStamp
2018-05-01 13:05:28 -06:00
if form . Deadline != nil {
2019-08-15 08:46:21 -06:00
deadlineUnix = timeutil . TimeStamp ( form . Deadline . Unix ( ) )
2018-05-01 13:05:28 -06:00
}
2022-06-13 03:37:59 -06:00
prIssue := & issues_model . Issue {
2018-05-01 13:05:28 -06:00
RepoID : repo . ID ,
Title : form . Title ,
2022-03-22 01:03:22 -06:00
PosterID : ctx . Doer . ID ,
Poster : ctx . Doer ,
2018-05-01 13:05:28 -06:00
MilestoneID : milestoneID ,
IsPull : true ,
Content : form . Body ,
DeadlineUnix : deadlineUnix ,
2016-12-02 04:10:39 -07:00
}
2022-06-13 03:37:59 -06:00
pr := & issues_model . PullRequest {
2019-10-18 05:13:31 -06:00
HeadRepoID : headRepo . ID ,
BaseRepoID : repo . ID ,
HeadBranch : headBranch ,
BaseBranch : baseBranch ,
HeadRepo : headRepo ,
BaseRepo : repo ,
MergeBase : compareInfo . MergeBase ,
2022-06-13 03:37:59 -06:00
Type : issues_model . PullRequestGitea ,
2016-12-02 04:10:39 -07:00
}
2018-05-09 10:29:04 -06:00
// Get all assignee IDs
2022-11-19 01:12:33 -07:00
assigneeIDs , err := issues_model . MakeIDsFromAPIAssigneesToAdd ( ctx , form . Assignee , form . Assignees )
2018-05-09 10:29:04 -06:00
if err != nil {
2021-11-24 02:49:20 -07:00
if user_model . IsErrUserNotExist ( err ) {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Assignee does not exist: [name: %s]" , err ) )
2018-05-09 10:29:04 -06:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "AddAssigneeByName" , err )
2018-05-09 10:29:04 -06:00
}
return
}
2019-10-25 08:46:37 -06:00
// Check if the passed assignees is assignable
for _ , aID := range assigneeIDs {
2022-12-02 19:48:26 -07:00
assignee , err := user_model . GetUserByID ( ctx , aID )
2019-10-25 08:46:37 -06:00
if err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetUserByID" , err )
2019-10-25 08:46:37 -06:00
return
}
2018-05-09 10:29:04 -06:00
2022-05-11 04:09:36 -06:00
valid , err := access_model . CanBeAssigned ( ctx , assignee , repo , true )
2019-10-25 08:46:37 -06:00
if err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "canBeAssigned" , err )
2019-10-25 08:46:37 -06:00
return
}
if ! valid {
2022-06-13 03:37:59 -06:00
ctx . Error ( http . StatusUnprocessableEntity , "canBeAssigned" , repo_model . ErrUserDoesNotHaveAccessToRepo { UserID : aID , RepoName : repo . Name } )
2019-10-25 08:46:37 -06:00
return
}
}
2022-01-19 16:26:57 -07:00
if err := pull_service . NewPullRequest ( ctx , repo , prIssue , labelIDs , [ ] string { } , pr , assigneeIDs ) ; err != nil {
2022-06-13 03:37:59 -06:00
if repo_model . IsErrUserDoesNotHaveAccessToRepo ( err ) {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusBadRequest , "UserDoesNotHaveAccessToRepo" , err )
2024-03-04 01:16:03 -07:00
} else if errors . Is ( err , user_model . ErrBlockedUser ) {
ctx . Error ( http . StatusForbidden , "BlockedUser" , err )
} else {
ctx . Error ( http . StatusInternalServerError , "NewPullRequest" , err )
2018-05-09 10:29:04 -06:00
}
2016-12-02 04:10:39 -07:00
return
}
log . Trace ( "Pull request created: %d/%d" , repo . ID , prIssue . ID )
2022-03-22 01:03:22 -06:00
ctx . JSON ( http . StatusCreated , convert . ToAPIPullRequest ( ctx , pr , ctx . Doer ) )
2016-12-02 04:10:39 -07:00
}
// EditPullRequest does what it says
2021-01-26 08:36:53 -07:00
func EditPullRequest ( ctx * context . APIContext ) {
2017-11-13 00:02:25 -07:00
// swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest
// ---
2019-11-03 07:46:32 -07:00
// summary: Update a pull request. If using deadline only the date will be taken into account, and time of day ignored.
2017-11-13 00:02:25 -07:00
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to edit
// type: integer
2018-10-20 21:40:42 -06:00
// format: int64
2017-11-13 00:02:25 -07:00
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditPullRequestOption"
// responses:
// "201":
// "$ref": "#/responses/PullRequest"
2019-12-20 10:07:12 -07:00
// "403":
// "$ref": "#/responses/forbidden"
2023-09-12 20:37:54 -06:00
// "404":
// "$ref": "#/responses/notFound"
2020-06-07 13:13:40 -06:00
// "409":
// "$ref": "#/responses/error"
2019-12-20 10:07:12 -07:00
// "412":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2021-01-26 08:36:53 -07:00
form := web . GetForm ( ctx ) . ( * api . EditPullRequestOption )
2022-06-13 03:37:59 -06:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2016-12-02 04:10:39 -07:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2016-12-02 04:10:39 -07:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 04:10:39 -07:00
}
return
}
2022-11-19 01:12:33 -07:00
err = pr . LoadIssue ( ctx )
2019-06-12 13:41:28 -06:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadIssue" , err )
return
}
2016-12-02 04:10:39 -07:00
issue := pr . Issue
2018-12-13 08:55:43 -07:00
issue . Repo = ctx . Repo . Repository
2016-12-02 04:10:39 -07:00
2022-12-21 15:45:44 -07:00
if err := issue . LoadAttributes ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadAttributes" , err )
return
}
2022-03-22 01:03:22 -06:00
if ! issue . IsPoster ( ctx . Doer . ID ) && ! ctx . Repo . CanWrite ( unit . TypePullRequests ) {
2019-12-20 10:07:12 -07:00
ctx . Status ( http . StatusForbidden )
2016-12-02 04:10:39 -07:00
return
}
if len ( form . Title ) > 0 {
2024-05-03 00:11:51 -06:00
err = issue_service . ChangeTitle ( ctx , issue , ctx . Doer , form . Title )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "ChangeTitle" , err )
return
}
2016-12-02 04:10:39 -07:00
}
2024-05-03 00:11:51 -06:00
if form . Body != nil {
2024-05-27 09:34:18 -06:00
err = issue_service . ChangeContent ( ctx , issue , ctx . Doer , * form . Body , issue . ContentVersion )
2024-05-03 00:11:51 -06:00
if err != nil {
2024-05-27 09:34:18 -06:00
if errors . Is ( err , issues_model . ErrIssueAlreadyChanged ) {
ctx . Error ( http . StatusBadRequest , "ChangeContent" , err )
return
}
2024-05-03 00:11:51 -06:00
ctx . Error ( http . StatusInternalServerError , "ChangeContent" , err )
return
}
2016-12-02 04:10:39 -07:00
}
2019-11-03 07:46:32 -07:00
// 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 ( ) )
}
2023-09-29 06:12:54 -06:00
if err := issues_model . UpdateIssueDeadline ( ctx , issue , deadlineUnix , ctx . Doer ) ; err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "UpdateIssueDeadline" , err )
2019-10-27 17:35:20 -06:00
return
}
issue . DeadlineUnix = deadlineUnix
2018-05-01 13:05:28 -06:00
}
2018-05-09 10:29:04 -06:00
// Add/delete assignees
2016-12-02 04:10:39 -07:00
2019-03-09 14:15:45 -07:00
// Deleting is done the GitHub way (quote from their api documentation):
2018-05-09 10:29:04 -06:00
// 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.
2021-11-09 12:57:58 -07:00
if ctx . Repo . CanWrite ( unit . TypePullRequests ) && ( form . Assignees != nil || len ( form . Assignee ) > 0 ) {
2023-04-14 12:18:28 -06:00
err = issue_service . UpdateAssignees ( ctx , issue , form . Assignee , form . Assignees , ctx . Doer )
2018-05-09 10:29:04 -06:00
if err != nil {
2021-11-24 02:49:20 -07:00
if user_model . IsErrUserNotExist ( err ) {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Assignee does not exist: [name: %s]" , err ) )
2024-03-04 01:16:03 -07:00
} else if errors . Is ( err , user_model . ErrBlockedUser ) {
ctx . Error ( http . StatusForbidden , "UpdateAssignees" , err )
2018-05-09 10:29:04 -06:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "UpdateAssignees" , err )
2018-05-09 10:29:04 -06:00
}
2016-12-02 04:10:39 -07:00
return
}
}
2018-05-09 10:29:04 -06:00
2021-11-09 12:57:58 -07:00
if ctx . Repo . CanWrite ( unit . TypePullRequests ) && form . Milestone != 0 &&
2016-12-02 04:10:39 -07:00
issue . MilestoneID != form . Milestone {
oldMilestoneID := issue . MilestoneID
issue . MilestoneID = form . Milestone
2023-10-10 22:24:07 -06:00
if err = issue_service . ChangeMilestoneAssign ( ctx , issue , ctx . Doer , oldMilestoneID ) ; err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "ChangeMilestoneAssign" , err )
2016-12-02 04:10:39 -07:00
return
}
}
2021-11-09 12:57:58 -07:00
if ctx . Repo . CanWrite ( unit . TypePullRequests ) && form . Labels != nil {
2022-11-19 01:12:33 -07:00
labels , err := issues_model . GetLabelsInRepoByIDs ( ctx , ctx . Repo . Repository . ID , form . Labels )
2018-11-16 04:12:44 -07:00
if err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetLabelsInRepoByIDsError" , err )
2018-11-16 04:12:44 -07:00
return
}
2020-04-20 07:10:45 -06:00
if ctx . Repo . Owner . IsOrganization ( ) {
2022-11-19 01:12:33 -07:00
orgLabels , err := issues_model . GetLabelsInOrgByIDs ( ctx , ctx . Repo . Owner . ID , form . Labels )
2020-04-20 07:10:45 -06:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetLabelsInOrgByIDs" , err )
return
}
labels = append ( labels , orgLabels ... )
}
2023-10-10 22:24:07 -06:00
if err = issues_model . ReplaceIssueLabels ( ctx , issue , labels , ctx . Doer ) ; err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "ReplaceLabelsError" , err )
2018-11-16 04:12:44 -07:00
return
}
}
2016-12-02 04:10:39 -07:00
if form . State != nil {
2021-10-02 21:11:17 -06:00
if pr . HasMerged {
ctx . Error ( http . StatusPreconditionFailed , "MergedPRState" , "cannot change state of this pull request, it was already merged" )
return
}
2024-05-03 00:11:51 -06:00
if err := issue_service . ChangeStatus ( ctx , issue , ctx . Doer , "" , api . StateClosed == api . StateType ( * form . State ) ) ; 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 , "ChangeStatus" , err )
2016-12-02 04:10:39 -07:00
return
}
}
2020-06-07 13:13:40 -06:00
// change pull target branch
2021-10-02 21:11:17 -06:00
if ! pr . HasMerged && len ( form . Base ) != 0 && form . Base != pr . BaseBranch {
2020-06-07 13:13:40 -06:00
if ! ctx . Repo . GitRepo . IsBranchExist ( form . Base ) {
ctx . Error ( http . StatusNotFound , "NewBaseBranchNotExist" , fmt . Errorf ( "new base '%s' not exist" , form . Base ) )
return
}
2022-03-22 01:03:22 -06:00
if err := pull_service . ChangeTargetBranch ( ctx , pr , ctx . Doer , form . Base ) ; err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestAlreadyExists ( err ) {
2020-06-07 13:13:40 -06:00
ctx . Error ( http . StatusConflict , "IsErrPullRequestAlreadyExists" , err )
return
2022-06-13 03:37:59 -06:00
} else if issues_model . IsErrIssueIsClosed ( err ) {
2020-06-07 13:13:40 -06:00
ctx . Error ( http . StatusUnprocessableEntity , "IsErrIssueIsClosed" , err )
return
} else if models . IsErrPullRequestHasMerged ( err ) {
ctx . Error ( http . StatusConflict , "IsErrPullRequestHasMerged" , err )
return
}
2023-10-23 20:54:59 -06:00
ctx . InternalServerError ( err )
2020-06-07 13:13:40 -06:00
return
}
2023-09-05 12:37:47 -06:00
notify_service . PullRequestChangeTargetBranch ( ctx , ctx . Doer , pr , form . Base )
2020-06-07 13:13:40 -06:00
}
2022-04-28 09:45:33 -06:00
// 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
}
}
2016-12-02 04:10:39 -07:00
// Refetch from database
2022-06-13 03:37:59 -06:00
pr , err = issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , pr . Index )
2016-12-02 04:10:39 -07:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2016-12-02 04:10:39 -07:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 04:10:39 -07:00
}
return
}
2017-11-13 00:02:25 -07:00
// TODO this should be 200, not 201
2022-03-22 01:03:22 -06:00
ctx . JSON ( http . StatusCreated , convert . ToAPIPullRequest ( ctx , pr , ctx . Doer ) )
2016-12-02 04:10:39 -07:00
}
// IsPullRequestMerged checks if a PR exists given an index
func IsPullRequestMerged ( ctx * context . APIContext ) {
2017-11-13 00:02:25 -07:00
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/merge repository repoPullRequestIsMerged
// ---
// summary: Check if a pull request has been merged
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request
// type: integer
2018-10-20 21:40:42 -06:00
// format: int64
2017-11-13 00:02:25 -07:00
// required: true
// responses:
// "204":
// description: pull request has been merged
// "404":
// description: pull request has not been merged
2019-12-20 10:07:12 -07:00
2022-06-13 03:37:59 -06:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2016-12-02 04:10:39 -07:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2016-12-02 04:10:39 -07:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 04:10:39 -07:00
}
return
}
if pr . HasMerged {
2019-12-20 10:07:12 -07:00
ctx . Status ( http . StatusNoContent )
2016-12-02 04:10:39 -07:00
}
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2016-12-02 04:10:39 -07:00
}
// MergePullRequest merges a PR given an index
2021-01-26 08:36:53 -07:00
func MergePullRequest ( ctx * context . APIContext ) {
2017-11-13 00:02:25 -07:00
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/merge repository repoMergePullRequest
// ---
// summary: Merge a pull request
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to merge
// type: integer
2018-10-20 21:40:42 -06:00
// format: int64
2017-11-13 00:02:25 -07:00
// required: true
2019-02-08 01:08:38 -07:00
// - name: body
// in: body
// schema:
// $ref: "#/definitions/MergePullRequestOption"
2017-11-13 00:02:25 -07:00
// responses:
// "200":
// "$ref": "#/responses/empty"
2023-09-12 20:37:54 -06:00
// "404":
// "$ref": "#/responses/notFound"
2017-11-13 00:02:25 -07:00
// "405":
// "$ref": "#/responses/empty"
2019-12-20 10:07:12 -07:00
// "409":
// "$ref": "#/responses/error"
2023-09-21 17:43:29 -06:00
// "423":
// "$ref": "#/responses/repoArchivedError"
2019-12-20 10:07:12 -07:00
2021-04-06 13:44:05 -06:00
form := web . GetForm ( ctx ) . ( * forms . MergePullRequestForm )
2022-05-03 13:46:28 -06:00
2022-06-13 03:37:59 -06:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2016-12-02 04:10:39 -07:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2018-01-10 14:34:17 -07:00
ctx . NotFound ( "GetPullRequestByIndex" , err )
2016-12-02 04:10:39 -07:00
} else {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 04:10:39 -07:00
}
return
}
2022-11-19 01:12:33 -07:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-09-20 14:20:14 -06:00
ctx . Error ( http . StatusInternalServerError , "LoadHeadRepo" , err )
2016-12-02 04:10:39 -07:00
return
}
2022-11-19 01:12:33 -07:00
if err := pr . LoadIssue ( ctx ) ; err != nil {
2019-06-12 13:41:28 -06:00
ctx . Error ( http . StatusInternalServerError , "LoadIssue" , err )
return
}
2016-12-02 04:10:39 -07:00
pr . Issue . Repo = ctx . Repo . Repository
if ctx . IsSigned {
// Update issue-user.
2022-08-24 20:31:57 -06:00
if err = activities_model . SetIssueReadBy ( ctx , pr . Issue . ID , ctx . Doer . ID ) ; err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "ReadBy" , err )
2016-12-02 04:10:39 -07:00
return
}
}
2023-02-21 07:42:07 -07:00
manuallyMerged := repo_model . MergeStyle ( form . Do ) == repo_model . MergeStyleManuallyMerged
mergeCheckType := pull_service . MergeCheckTypeGeneral
if form . MergeWhenChecksSucceed {
mergeCheckType = pull_service . MergeCheckTypeAuto
}
if manuallyMerged {
mergeCheckType = pull_service . MergeCheckTypeManually
}
2019-09-17 23:39:45 -06:00
2022-05-03 13:46:28 -06:00
// start with merging by checking
2024-05-08 10:11:43 -06:00
if err := pull_service . CheckPullMergeable ( ctx , ctx . Doer , & ctx . Repo . Permission , pr , mergeCheckType , form . ForceMerge ) ; err != nil {
2022-03-31 08:53:08 -06:00
if errors . Is ( err , pull_service . ErrIsClosed ) {
ctx . NotFound ( )
} else if errors . Is ( err , pull_service . ErrUserNotAllowedToMerge ) {
ctx . Error ( http . StatusMethodNotAllowed , "Merge" , "User not allowed to merge PR" )
} else if errors . Is ( err , pull_service . ErrHasMerged ) {
ctx . Error ( http . StatusMethodNotAllowed , "PR already merged" , "" )
} else if errors . Is ( err , pull_service . ErrIsWorkInProgress ) {
ctx . Error ( http . StatusMethodNotAllowed , "PR is a work in progress" , "Work in progress PRs cannot be merged" )
2024-05-08 10:11:43 -06:00
} else if errors . Is ( err , pull_service . ErrNotMergeableState ) {
2022-03-31 08:53:08 -06:00
ctx . Error ( http . StatusMethodNotAllowed , "PR not in mergeable state" , "Please try again later" )
} else if models . IsErrDisallowedToMerge ( err ) {
ctx . Error ( http . StatusMethodNotAllowed , "PR is not ready to be merged" , err )
} else if asymkey_service . IsErrWontSign ( err ) {
ctx . Error ( http . StatusMethodNotAllowed , fmt . Sprintf ( "Protected branch %s requires signed commits but this merge would not be signed" , pr . BaseBranch ) , err )
} else {
ctx . InternalServerError ( err )
}
2020-08-19 15:35:06 -06:00
return
}
2021-03-03 20:41:23 -07:00
// handle manually-merged mark
2023-02-21 07:42:07 -07:00
if manuallyMerged {
2023-10-14 02:37:24 -06:00
if err := pull_service . MergedManually ( ctx , pr , ctx . Doer , ctx . Repo . GitRepo , form . MergeCommitID ) ; err != nil {
2021-03-03 20:41:23 -07:00
if models . IsErrInvalidMergeStyle ( err ) {
2021-12-09 18:27:50 -07:00
ctx . Error ( http . StatusMethodNotAllowed , "Invalid merge style" , fmt . Errorf ( "%s is not allowed an allowed merge style for this repository" , repo_model . MergeStyle ( form . Do ) ) )
2021-03-03 20:41:23 -07:00
return
}
if strings . Contains ( err . Error ( ) , "Wrong commit ID" ) {
ctx . JSON ( http . StatusConflict , err )
return
}
ctx . Error ( http . StatusInternalServerError , "Manually-Merged" , err )
return
}
ctx . Status ( http . StatusOK )
return
}
2022-05-08 06:32:45 -06:00
if len ( form . Do ) == 0 {
form . Do = string ( repo_model . MergeStyleMerge )
}
message := strings . TrimSpace ( form . MergeTitleField )
if len ( message ) == 0 {
2022-12-29 05:40:20 -07:00
message , _ , err = pull_service . GetDefaultMergeMessage ( ctx , ctx . Repo . GitRepo , pr , repo_model . MergeStyle ( form . Do ) )
2022-05-08 06:32:45 -06:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetDefaultMergeMessage" , err )
return
}
}
form . MergeMessageField = strings . TrimSpace ( form . MergeMessageField )
if len ( form . MergeMessageField ) > 0 {
message += "\n\n" + form . MergeMessageField
2020-08-19 15:35:06 -06:00
}
2022-05-07 11:05:52 -06:00
if form . MergeWhenChecksSucceed {
2022-05-08 06:32:45 -06:00
scheduled , err := automerge . ScheduleAutoMerge ( ctx , ctx . Doer , pr , repo_model . MergeStyle ( form . Do ) , message )
2022-05-07 11:05:52 -06:00
if err != nil {
if pull_model . IsErrAlreadyScheduledToAutoMerge ( err ) {
ctx . Error ( http . StatusConflict , "ScheduleAutoMerge" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "ScheduleAutoMerge" , err )
return
} else if scheduled {
// nothing more to do ...
ctx . Status ( http . StatusCreated )
return
}
}
2022-11-03 09:49:00 -06:00
if err := pull_service . Merge ( ctx , pr , ctx . Doer , ctx . Repo . GitRepo , repo_model . MergeStyle ( form . Do ) , form . HeadCommitID , message , false ) ; err != nil {
2018-01-05 11:56:50 -07:00
if models . IsErrInvalidMergeStyle ( err ) {
2021-12-09 18:27:50 -07:00
ctx . Error ( http . StatusMethodNotAllowed , "Invalid merge style" , fmt . Errorf ( "%s is not allowed an allowed merge style for this repository" , repo_model . MergeStyle ( form . Do ) ) )
2019-11-10 01:42:51 -07:00
} else if models . IsErrMergeConflicts ( err ) {
conflictError := err . ( models . ErrMergeConflicts )
ctx . JSON ( http . StatusConflict , conflictError )
} else if models . IsErrRebaseConflicts ( err ) {
conflictError := err . ( models . ErrRebaseConflicts )
ctx . JSON ( http . StatusConflict , conflictError )
} else if models . IsErrMergeUnrelatedHistories ( err ) {
conflictError := err . ( models . ErrMergeUnrelatedHistories )
ctx . JSON ( http . StatusConflict , conflictError )
2020-03-27 22:13:18 -06:00
} else if git . IsErrPushOutOfDate ( err ) {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusConflict , "Merge" , "merge push out of date" )
2021-12-19 17:32:54 -07:00
} else if models . IsErrSHADoesNotMatch ( err ) {
ctx . Error ( http . StatusConflict , "Merge" , "head out of date" )
2020-03-27 22:13:18 -06:00
} else if git . IsErrPushRejected ( err ) {
errPushRej := err . ( * git . ErrPushRejected )
2020-02-22 06:08:48 -07:00
if len ( errPushRej . Message ) == 0 {
ctx . Error ( http . StatusConflict , "Merge" , "PushRejected without remote error message" )
2022-05-03 13:46:28 -06:00
} else {
ctx . Error ( http . StatusConflict , "Merge" , "PushRejected with remote message: " + errPushRej . Message )
2020-02-22 06:08:48 -07:00
}
2022-05-03 13:46:28 -06:00
} else {
ctx . Error ( http . StatusInternalServerError , "Merge" , err )
2018-01-05 11:56:50 -07:00
}
2016-12-02 04:10:39 -07:00
return
}
log . Trace ( "Pull request merged: %d" , pr . ID )
2021-07-12 17:26:25 -06:00
if form . DeleteBranchAfterMerge {
2022-01-03 12:45:58 -07:00
// Don't cleanup when there are other PR's that use this branch as head branch.
2022-06-13 03:37:59 -06:00
exist , err := issues_model . HasUnmergedPullRequestsByHeadInfo ( ctx , pr . HeadRepoID , pr . HeadBranch )
2022-01-03 12:45:58 -07:00
if err != nil {
ctx . ServerError ( "HasUnmergedPullRequestsByHeadInfo" , err )
return
}
if exist {
ctx . Status ( http . StatusOK )
return
}
2021-07-12 17:26:25 -06:00
var headRepo * git . Repository
if ctx . Repo != nil && ctx . Repo . Repository != nil && ctx . Repo . Repository . ID == pr . HeadRepoID && ctx . Repo . GitRepo != nil {
headRepo = ctx . Repo . GitRepo
} else {
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 13:09:51 -07:00
headRepo , err = gitrepo . OpenRepository ( ctx , pr . HeadRepo )
2021-07-12 17:26:25 -06:00
if err != nil {
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 13:09:51 -07:00
ctx . ServerError ( fmt . Sprintf ( "OpenRepository[%s]" , pr . HeadRepo . FullName ( ) ) , err )
2021-07-12 17:26:25 -06:00
return
}
defer headRepo . Close ( )
}
2024-01-16 17:44:56 -07:00
if err := pull_service . RetargetChildrenOnMerge ( ctx , ctx . Doer , pr ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "RetargetChildrenOnMerge" , err )
return
}
2023-02-28 15:17:51 -07:00
if err := repo_service . DeleteBranch ( ctx , ctx . Doer , pr . HeadRepo , headRepo , pr . HeadBranch ) ; err != nil {
2021-07-12 17:26:25 -06:00
switch {
case git . IsErrBranchNotExist ( err ) :
ctx . NotFound ( err )
case errors . Is ( err , repo_service . ErrBranchIsDefault ) :
ctx . Error ( http . StatusForbidden , "DefaultBranch" , fmt . Errorf ( "can not delete default branch" ) )
2023-01-16 01:00:22 -07:00
case errors . Is ( err , git_model . ErrBranchIsProtected ) :
2021-07-12 17:26:25 -06:00
ctx . Error ( http . StatusForbidden , "IsProtectedBranch" , fmt . Errorf ( "branch protected" ) )
default :
ctx . Error ( http . StatusInternalServerError , "DeleteBranch" , err )
}
return
}
2022-06-13 03:37:59 -06:00
if err := issues_model . AddDeletePRBranchComment ( ctx , ctx . Doer , pr . BaseRepo , pr . Issue . ID , pr . HeadBranch ) ; err != nil {
2021-07-12 17:26:25 -06:00
// Do not fail here as branch has already been deleted
log . Error ( "DeleteBranch: %v" , err )
}
}
2019-12-20 10:07:12 -07:00
ctx . Status ( http . StatusOK )
2016-12-02 04:10:39 -07:00
}
2024-06-11 12:47:45 -06:00
func parseCompareInfo ( ctx * context . APIContext , form api . CreatePullRequestOption ) ( * repo_model . Repository , * git . Repository , * git . CompareInfo , string , string ) {
2016-12-02 04:10:39 -07:00
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 (
2021-11-24 02:49:20 -07:00
headUser * user_model . User
2016-12-02 04:10:39 -07:00
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 {
2022-05-20 08:08:52 -06:00
headUser , err = user_model . GetUserByName ( ctx , headInfos [ 0 ] )
2016-12-02 04:10:39 -07:00
if err != nil {
2021-11-24 02:49:20 -07:00
if user_model . IsErrUserNotExist ( err ) {
2019-05-07 11:20:23 -06:00
ctx . NotFound ( "GetUserByName" )
2016-12-02 04:10:39 -07:00
} else {
2020-09-20 14:20:14 -06:00
ctx . Error ( http . StatusInternalServerError , "GetUserByName" , err )
2016-12-02 04:10:39 -07:00
}
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
headBranch = headInfos [ 1 ]
2024-03-16 21:56:49 -06:00
// The head repository can also point to the same repo
isSameRepo = ctx . Repo . Owner . ID == headUser . ID
2016-12-02 04:10:39 -07:00
} else {
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
ctx . Repo . PullRequest . SameRepo = isSameRepo
2024-04-21 16:19:59 -06:00
log . Trace ( "Repo path: %q, base branch: %q, head branch: %q" , ctx . Repo . GitRepo . Path , baseBranch , headBranch )
2016-12-02 04:10:39 -07:00
// Check if base branch is valid.
2024-04-21 16:19:59 -06:00
if ! ctx . Repo . GitRepo . IsBranchExist ( baseBranch ) && ! ctx . Repo . GitRepo . IsTagExist ( baseBranch ) {
ctx . NotFound ( "BaseNotExist" )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
// Check if current user has fork of repository or in the same repository.
2023-09-14 11:09:32 -06:00
headRepo := repo_model . GetForkedRepo ( ctx , headUser . ID , baseRepo . ID )
2021-11-22 08:21:55 -07:00
if headRepo == nil && ! isSameRepo {
2016-12-02 04:10:39 -07:00
log . Trace ( "parseCompareInfo[%d]: does not have fork or in same repository" , baseRepo . ID )
2021-11-22 08:21:55 -07:00
ctx . NotFound ( "GetForkedRepo" )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
var headGitRepo * git . Repository
if isSameRepo {
headRepo = ctx . Repo . Repository
headGitRepo = ctx . Repo . GitRepo
} else {
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 13:09:51 -07:00
headGitRepo , err = gitrepo . OpenRepository ( ctx , headRepo )
2016-12-02 04:10:39 -07:00
if err != nil {
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "OpenRepository" , err )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
}
2019-05-07 11:20:23 -06:00
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
2022-05-11 04:09:36 -06:00
permBase , err := access_model . GetUserRepoPermission ( ctx , baseRepo , ctx . Doer )
2018-11-28 04:26:14 -07:00
if err != nil {
2019-11-13 00:01:19 -07:00
headGitRepo . Close ( )
2020-09-20 14:20:14 -06:00
ctx . Error ( http . StatusInternalServerError , "GetUserRepoPermission" , err )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2018-11-28 04:26:14 -07:00
}
2021-11-09 12:57:58 -07:00
if ! permBase . CanReadIssuesOrPulls ( true ) || ! permBase . CanRead ( unit . TypeCode ) {
2019-04-22 14:40:51 -06:00
if log . IsTrace ( ) {
2019-05-07 11:20:23 -06:00
log . Trace ( "Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v" ,
2022-03-22 01:03:22 -06:00
ctx . Doer ,
2019-05-07 11:20:23 -06:00
baseRepo ,
permBase )
}
2019-11-13 00:01:19 -07:00
headGitRepo . Close ( )
2019-05-07 11:20:23 -06:00
ctx . NotFound ( "Can't read pulls or can't read UnitTypeCode" )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2019-05-07 11:20:23 -06:00
}
// user should have permission to read headrepo's codes
2022-05-11 04:09:36 -06:00
permHead , err := access_model . GetUserRepoPermission ( ctx , headRepo , ctx . Doer )
2019-05-07 11:20:23 -06:00
if err != nil {
2019-11-13 00:01:19 -07:00
headGitRepo . Close ( )
2020-09-20 14:20:14 -06:00
ctx . Error ( http . StatusInternalServerError , "GetUserRepoPermission" , err )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2019-05-07 11:20:23 -06:00
}
2021-11-09 12:57:58 -07:00
if ! permHead . CanRead ( unit . TypeCode ) {
2019-05-07 11:20:23 -06:00
if log . IsTrace ( ) {
log . Trace ( "Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v" ,
2022-03-22 01:03:22 -06:00
ctx . Doer ,
2019-04-22 14:40:51 -06:00
headRepo ,
2019-05-07 11:20:23 -06:00
permHead )
2019-04-22 14:40:51 -06:00
}
2019-11-13 00:01:19 -07:00
headGitRepo . Close ( )
2019-05-07 11:20:23 -06:00
ctx . NotFound ( "Can't read headRepo UnitTypeCode" )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
// Check if head branch is valid.
2024-04-21 16:19:59 -06:00
if ! headGitRepo . IsBranchExist ( headBranch ) && ! headGitRepo . IsTagExist ( headBranch ) {
2019-11-13 00:01:19 -07:00
headGitRepo . Close ( )
2019-03-18 20:29:43 -06:00
ctx . NotFound ( )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
2022-01-18 00:45:43 -07:00
compareInfo , err := headGitRepo . GetCompareInfo ( repo_model . RepoPath ( baseRepo . Owner . Name , baseRepo . Name ) , baseBranch , headBranch , false , false )
2016-12-02 04:10:39 -07:00
if err != nil {
2019-11-13 00:01:19 -07:00
headGitRepo . Close ( )
2019-12-20 10:07:12 -07:00
ctx . Error ( http . StatusInternalServerError , "GetCompareInfo" , err )
2024-06-11 12:47:45 -06:00
return nil , nil , nil , "" , ""
2016-12-02 04:10:39 -07:00
}
2024-06-11 12:47:45 -06:00
return headRepo , headGitRepo , compareInfo , baseBranch , headBranch
2016-12-02 04:10:39 -07:00
}
2020-08-04 14:55:22 -06:00
// UpdatePullRequest merge PR's baseBranch into headBranch
func UpdatePullRequest ( ctx * context . APIContext ) {
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/update repository repoUpdatePullRequest
// ---
// summary: Merge PR's baseBranch into headBranch
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to get
// type: integer
// format: int64
// required: true
2021-08-31 08:03:45 -06:00
// - name: style
// in: query
// description: how to update pull request
// type: string
// enum: [merge, rebase]
2020-08-04 14:55:22 -06:00
// responses:
// "200":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "409":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2022-06-13 03:37:59 -06:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2020-08-04 14:55:22 -06:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2020-08-04 14:55:22 -06:00
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
}
return
}
if pr . HasMerged {
ctx . Error ( http . StatusUnprocessableEntity , "UpdatePullRequest" , err )
return
}
2022-11-19 01:12:33 -07:00
if err = pr . LoadIssue ( ctx ) ; err != nil {
2020-08-04 14:55:22 -06:00
ctx . Error ( http . StatusInternalServerError , "LoadIssue" , err )
return
}
if pr . Issue . IsClosed {
ctx . Error ( http . StatusUnprocessableEntity , "UpdatePullRequest" , err )
return
}
2022-11-19 01:12:33 -07:00
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2020-08-04 14:55:22 -06:00
ctx . Error ( http . StatusInternalServerError , "LoadBaseRepo" , err )
return
}
2022-11-19 01:12:33 -07:00
if err = pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-08-04 14:55:22 -06:00
ctx . Error ( http . StatusInternalServerError , "LoadHeadRepo" , err )
return
}
2021-08-31 08:03:45 -06:00
rebase := ctx . FormString ( "style" ) == "rebase"
2022-04-28 05:48:48 -06:00
allowedUpdateByMerge , allowedUpdateByRebase , err := pull_service . IsUserAllowedToUpdate ( ctx , pr , ctx . Doer )
2020-08-04 14:55:22 -06:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "IsUserAllowedToMerge" , err )
return
}
2021-08-31 08:03:45 -06:00
if ( ! allowedUpdateByMerge && ! rebase ) || ( rebase && ! allowedUpdateByRebase ) {
2020-08-04 14:55:22 -06:00
ctx . Status ( http . StatusForbidden )
return
}
// default merge commit message
message := fmt . Sprintf ( "Merge branch '%s' into %s" , pr . BaseBranch , pr . HeadBranch )
2022-03-22 01:03:22 -06:00
if err = pull_service . Update ( ctx , pr , ctx . Doer , message , rebase ) ; err != nil {
2020-08-04 14:55:22 -06:00
if models . IsErrMergeConflicts ( err ) {
ctx . Error ( http . StatusConflict , "Update" , "merge failed because of conflict" )
return
2021-09-05 03:30:40 -06:00
} else if models . IsErrRebaseConflicts ( err ) {
ctx . Error ( http . StatusConflict , "Update" , "rebase failed because of conflict" )
return
2020-08-04 14:55:22 -06:00
}
ctx . Error ( http . StatusInternalServerError , "pull_service.Update" , err )
return
}
ctx . Status ( http . StatusOK )
}
2021-07-02 06:19:57 -06:00
2022-05-07 11:05:52 -06:00
// MergePullRequest cancel an auto merge scheduled for a given PullRequest by index
func CancelScheduledAutoMerge ( ctx * context . APIContext ) {
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/merge repository repoCancelScheduledAutoMerge
// ---
// summary: Cancel the scheduled auto merge for the given pull request
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to merge
// type: integer
// format: int64
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
2023-09-21 17:43:29 -06:00
// "423":
// "$ref": "#/responses/repoArchivedError"
2022-05-07 11:05:52 -06:00
pullIndex := ctx . ParamsInt64 ( ":index" )
2022-06-13 03:37:59 -06:00
pull , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , pullIndex )
2022-05-07 11:05:52 -06:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2022-05-07 11:05:52 -06:00
ctx . NotFound ( )
return
}
ctx . InternalServerError ( err )
return
}
exist , autoMerge , err := pull_model . GetScheduledMergeByPullID ( ctx , pull . ID )
if err != nil {
ctx . InternalServerError ( err )
return
}
if ! exist {
ctx . NotFound ( )
return
}
if ctx . Doer . ID != autoMerge . DoerID {
2022-05-20 08:08:52 -06:00
allowed , err := access_model . IsUserRepoAdmin ( ctx , ctx . Repo . Repository , ctx . Doer )
2022-05-07 11:05:52 -06:00
if err != nil {
ctx . InternalServerError ( err )
return
}
if ! allowed {
ctx . Error ( http . StatusForbidden , "No permission to cancel" , "user has no permission to cancel the scheduled auto merge" )
return
}
}
2022-05-08 07:46:34 -06:00
if err := automerge . RemoveScheduledAutoMerge ( ctx , ctx . Doer , pull ) ; err != nil {
2022-05-07 11:05:52 -06:00
ctx . InternalServerError ( err )
} else {
ctx . Status ( http . StatusNoContent )
}
}
2021-07-02 06:19:57 -06:00
// GetPullRequestCommits gets all commits associated with a given PR
func GetPullRequestCommits ( ctx * context . APIContext ) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/commits repository repoGetPullRequestCommits
// ---
// summary: Get commits for a pull request
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to get
// type: integer
// format: int64
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
2023-10-09 05:22:12 -06:00
// - name: verification
// in: query
// description: include verification for every commit (disable for speedup, default 'true')
// type: boolean
// - name: files
// in: query
// description: include a list of affected files for every commit (disable for speedup, default 'true')
// type: boolean
2021-07-02 06:19:57 -06:00
// responses:
// "200":
// "$ref": "#/responses/CommitList"
// "404":
// "$ref": "#/responses/notFound"
2022-06-13 03:37:59 -06:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2021-07-02 06:19:57 -06:00
if err != nil {
2022-06-13 03:37:59 -06:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2021-07-02 06:19:57 -06:00
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
}
return
}
2022-11-19 01:12:33 -07:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2021-07-02 06:19:57 -06:00
ctx . InternalServerError ( err )
return
}
var prInfo * git . CompareInfo
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 13:09:51 -07:00
baseGitRepo , closer , err := gitrepo . RepositoryFromContextOrOpen ( ctx , pr . BaseRepo )
2021-07-02 06:19:57 -06:00
if err != nil {
ctx . ServerError ( "OpenRepository" , err )
return
}
2022-01-19 16:26:57 -07:00
defer closer . Close ( )
2021-07-02 06:19:57 -06:00
if pr . HasMerged {
2022-01-18 00:45:43 -07:00
prInfo , err = baseGitRepo . GetCompareInfo ( pr . BaseRepo . RepoPath ( ) , pr . MergeBase , pr . GetGitRefName ( ) , false , false )
2021-07-02 06:19:57 -06:00
} else {
2022-01-18 00:45:43 -07:00
prInfo , err = baseGitRepo . GetCompareInfo ( pr . BaseRepo . RepoPath ( ) , pr . BaseBranch , pr . GetGitRefName ( ) , false , false )
2021-07-02 06:19:57 -06:00
}
if err != nil {
ctx . ServerError ( "GetCompareInfo" , err )
return
}
commits := prInfo . Commits
listOptions := utils . GetListOptions ( ctx )
2021-08-09 12:08:51 -06:00
totalNumberOfCommits := len ( commits )
2021-07-02 06:19:57 -06:00
totalNumberOfPages := int ( math . Ceil ( float64 ( totalNumberOfCommits ) / float64 ( listOptions . PageSize ) ) )
2021-11-24 02:49:20 -07:00
userCache := make ( map [ string ] * user_model . User )
2021-07-02 06:19:57 -06:00
2024-01-14 19:19:25 -07:00
start , limit := listOptions . GetSkipTake ( )
2021-07-02 06:19:57 -06:00
2024-01-14 19:19:25 -07:00
limit = min ( limit , totalNumberOfCommits - start )
limit = max ( limit , 0 )
2021-07-02 06:19:57 -06:00
2023-10-09 05:22:12 -06:00
verification := ctx . FormString ( "verification" ) == "" || ctx . FormBool ( "verification" )
files := ctx . FormString ( "files" ) == "" || ctx . FormBool ( "files" )
2024-01-14 19:19:25 -07:00
apiCommits := make ( [ ] * api . Commit , 0 , limit )
for i := start ; i < start + limit ; i ++ {
2023-10-09 05:22:12 -06:00
apiCommit , err := convert . ToCommit ( ctx , ctx . Repo . Repository , baseGitRepo , commits [ i ] , userCache ,
convert . ToCommitOptions {
Stat : true ,
Verification : verification ,
Files : files ,
} )
2021-07-02 06:19:57 -06:00
if err != nil {
ctx . ServerError ( "toCommit" , err )
return
}
2021-08-09 12:08:51 -06:00
apiCommits = append ( apiCommits , apiCommit )
2021-07-02 06:19:57 -06:00
}
2021-08-12 06:43:08 -06:00
ctx . SetLinkHeader ( totalNumberOfCommits , listOptions . PageSize )
ctx . SetTotalCountHeader ( int64 ( totalNumberOfCommits ) )
2021-07-02 06:19:57 -06:00
2021-12-14 23:59:57 -07:00
ctx . RespHeader ( ) . Set ( "X-Page" , strconv . Itoa ( listOptions . Page ) )
ctx . RespHeader ( ) . Set ( "X-PerPage" , strconv . Itoa ( listOptions . PageSize ) )
ctx . RespHeader ( ) . Set ( "X-PageCount" , strconv . Itoa ( totalNumberOfPages ) )
ctx . RespHeader ( ) . Set ( "X-HasMore" , strconv . FormatBool ( listOptions . Page < totalNumberOfPages ) )
2021-08-12 06:43:08 -06:00
ctx . AppendAccessControlExposeHeaders ( "X-Page" , "X-PerPage" , "X-PageCount" , "X-HasMore" )
2021-07-02 06:19:57 -06:00
ctx . JSON ( http . StatusOK , & apiCommits )
}
2022-09-28 20:27:20 -06:00
// GetPullRequestFiles gets all changed files associated with a given PR
func GetPullRequestFiles ( ctx * context . APIContext ) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/files repository repoGetPullRequestFiles
// ---
// summary: Get changed files for a pull request
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to get
// type: integer
// format: int64
// required: true
// - name: skip-to
// in: query
// description: skip to given file
// type: string
// - name: whitespace
// in: query
// description: whitespace behavior
// type: string
// enum: [ignore-all, ignore-change, ignore-eol, show-all]
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/ChangedFileList"
// "404":
// "$ref": "#/responses/notFound"
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
}
2022-11-19 01:12:33 -07:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2022-09-28 20:27:20 -06:00
ctx . InternalServerError ( err )
return
}
2022-11-19 01:12:33 -07:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2022-09-28 20:27:20 -06:00
ctx . InternalServerError ( err )
return
}
baseGitRepo := ctx . Repo . GitRepo
var prInfo * git . CompareInfo
if pr . HasMerged {
prInfo , err = baseGitRepo . GetCompareInfo ( pr . BaseRepo . RepoPath ( ) , pr . MergeBase , pr . GetGitRefName ( ) , true , false )
} else {
prInfo , err = baseGitRepo . GetCompareInfo ( pr . BaseRepo . RepoPath ( ) , pr . BaseBranch , pr . GetGitRefName ( ) , true , false )
}
if err != nil {
ctx . ServerError ( "GetCompareInfo" , err )
return
}
headCommitID , err := baseGitRepo . GetRefCommitID ( pr . GetGitRefName ( ) )
if err != nil {
ctx . ServerError ( "GetRefCommitID" , err )
return
}
startCommitID := prInfo . MergeBase
endCommitID := headCommitID
2023-02-20 07:22:34 -07:00
maxLines := setting . Git . MaxGitDiffLines
2022-09-28 20:27:20 -06:00
2023-02-20 07:22:34 -07:00
// FIXME: If there are too many files in the repo, may cause some unpredictable issues.
2023-10-03 04:30:41 -06:00
diff , err := gitdiff . GetDiff ( ctx , baseGitRepo ,
2022-09-28 20:27:20 -06:00
& gitdiff . DiffOptions {
BeforeCommitID : startCommitID ,
AfterCommitID : endCommitID ,
SkipTo : ctx . FormString ( "skip-to" ) ,
MaxLines : maxLines ,
MaxLineCharacters : setting . Git . MaxGitDiffLineCharacters ,
2023-02-20 07:22:34 -07:00
MaxFiles : - 1 , // GetDiff() will return all files
2022-09-28 20:27:20 -06:00
WhitespaceBehavior : gitdiff . GetWhitespaceFlag ( ctx . FormString ( "whitespace" ) ) ,
} )
if err != nil {
ctx . ServerError ( "GetDiff" , err )
return
}
listOptions := utils . GetListOptions ( ctx )
totalNumberOfFiles := diff . NumFiles
totalNumberOfPages := int ( math . Ceil ( float64 ( totalNumberOfFiles ) / float64 ( listOptions . PageSize ) ) )
2024-01-14 19:19:25 -07:00
start , limit := listOptions . GetSkipTake ( )
2022-09-28 20:27:20 -06:00
2024-01-14 19:19:25 -07:00
limit = min ( limit , totalNumberOfFiles - start )
2022-09-28 20:27:20 -06:00
2024-01-14 19:19:25 -07:00
limit = max ( limit , 0 )
2023-02-20 07:22:34 -07:00
2024-01-14 19:19:25 -07:00
apiFiles := make ( [ ] * api . ChangedFile , 0 , limit )
for i := start ; i < start + limit ; i ++ {
2022-09-28 20:27:20 -06:00
apiFiles = append ( apiFiles , convert . ToChangedFile ( diff . Files [ i ] , pr . HeadRepo , endCommitID ) )
}
ctx . SetLinkHeader ( totalNumberOfFiles , listOptions . PageSize )
ctx . SetTotalCountHeader ( int64 ( totalNumberOfFiles ) )
ctx . RespHeader ( ) . Set ( "X-Page" , strconv . Itoa ( listOptions . Page ) )
ctx . RespHeader ( ) . Set ( "X-PerPage" , strconv . Itoa ( listOptions . PageSize ) )
ctx . RespHeader ( ) . Set ( "X-PageCount" , strconv . Itoa ( totalNumberOfPages ) )
ctx . RespHeader ( ) . Set ( "X-HasMore" , strconv . FormatBool ( listOptions . Page < totalNumberOfPages ) )
ctx . AppendAccessControlExposeHeaders ( "X-Page" , "X-PerPage" , "X-PageCount" , "X-HasMore" )
ctx . JSON ( http . StatusOK , & apiFiles )
}