2016-12-02 19:10:39 +08:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-28 02:20:29 +08:00
// SPDX-License-Identifier: MIT
2016-12-02 19:10:39 +08:00
package repo
import (
2021-07-13 07:26:25 +08:00
"errors"
2016-12-02 19:10:39 +08:00
"fmt"
2021-07-02 20:19:57 +08:00
"math"
2018-07-18 05:23:58 +08:00
"net/http"
2021-07-02 20:19:57 +08:00
"strconv"
2016-12-02 19:10:39 +08:00
"strings"
2019-11-03 22:46:32 +08:00
"time"
2016-12-02 19:10:39 +08:00
"code.gitea.io/gitea/models"
2022-08-25 10:31:57 +08:00
activities_model "code.gitea.io/gitea/models/activities"
2023-01-16 16:00:22 +08:00
git_model "code.gitea.io/gitea/models/git"
2022-04-08 17:11:15 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2022-05-11 18:09:36 +08:00
access_model "code.gitea.io/gitea/models/perm/access"
2022-05-08 01:05:52 +08:00
pull_model "code.gitea.io/gitea/models/pull"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 03:57:58 +08:00
"code.gitea.io/gitea/models/unit"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2024-03-21 23:07:35 +08:00
"code.gitea.io/gitea/modules/base"
2019-03-27 17:33:00 +08: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-28 04:09:51 +08:00
"code.gitea.io/gitea/modules/gitrepo"
2016-12-02 19:10:39 +08:00
"code.gitea.io/gitea/modules/log"
2022-09-29 10:27:20 +08:00
"code.gitea.io/gitea/modules/setting"
2019-05-11 18:21:34 +08:00
api "code.gitea.io/gitea/modules/structs"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
2020-01-25 03:00:29 +08:00
"code.gitea.io/gitea/routers/api/v1/utils"
2021-12-10 16:14:24 +08:00
asymkey_service "code.gitea.io/gitea/services/asymkey"
2022-05-08 01:05:52 +08:00
"code.gitea.io/gitea/services/automerge"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2022-12-29 10:57:15 +08:00
"code.gitea.io/gitea/services/convert"
2021-04-07 03:44:05 +08:00
"code.gitea.io/gitea/services/forms"
2022-09-29 10:27:20 +08:00
"code.gitea.io/gitea/services/gitdiff"
2019-10-25 22:46:37 +08:00
issue_service "code.gitea.io/gitea/services/issue"
2023-09-06 02:37:47 +08:00
notify_service "code.gitea.io/gitea/services/notify"
2019-09-27 08:22:36 +08:00
pull_service "code.gitea.io/gitea/services/pull"
2021-07-13 07:26:25 +08:00
repo_service "code.gitea.io/gitea/services/repository"
2016-12-02 19:10:39 +08:00
)
// ListPullRequests returns a list of all PRs
2021-01-26 23:36:53 +08:00
func ListPullRequests ( ctx * context . APIContext ) {
2017-11-13 15:02:25 +08: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
2024-10-08 05:21:07 +08:00
// description: Owner of the repo
2017-11-13 15:02:25 +08:00
// type: string
// required: true
// - name: repo
// in: path
2024-10-08 05:21:07 +08:00
// description: Name of the repo
2017-11-13 15:02:25 +08:00
// type: string
// required: true
2018-10-21 11:40:42 +08:00
// - name: state
// in: query
2024-10-08 05:21:07 +08:00
// description: State of pull request
2018-10-21 11:40:42 +08:00
// type: string
2024-10-08 05:21:07 +08:00
// enum: [open, closed, all]
// default: open
2018-10-21 11:40:42 +08:00
// - name: sort
// in: query
2024-10-08 05:21:07 +08:00
// description: Type of sort
2018-10-21 11:40:42 +08:00
// type: string
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
// - name: milestone
// in: query
2024-10-08 05:21:07 +08:00
// description: ID of the milestone
2018-10-21 11:40:42 +08:00
// type: integer
// format: int64
// - name: labels
// in: query
2024-10-08 05:21:07 +08:00
// description: Label IDs
2018-10-21 11:40:42 +08:00
// type: array
// collectionFormat: multi
// items:
// type: integer
// format: int64
2024-10-08 05:21:07 +08:00
// - name: poster
// in: query
// description: Filter by pull request author
// type: string
2020-01-25 03:00:29 +08:00
// - name: page
// in: query
2024-10-08 05:21:07 +08:00
// description: Page number of results to return (1-based)
2020-01-25 03:00:29 +08:00
// type: integer
2024-10-08 05:21:07 +08:00
// minimum: 1
// default: 1
2020-01-25 03:00:29 +08:00
// - name: limit
// in: query
2024-10-08 05:21:07 +08:00
// description: Page size of results
2020-01-25 03:00:29 +08:00
// type: integer
2024-10-08 05:21:07 +08:00
// minimum: 0
2017-11-13 15:02:25 +08:00
// responses:
// "200":
// "$ref": "#/responses/PullRequestList"
2023-09-13 10:37:54 +08:00
// "404":
// "$ref": "#/responses/notFound"
2024-10-08 05:21:07 +08:00
// "500":
// "$ref": "#/responses/error"
2019-12-21 01:07:12 +08:00
2024-03-21 23:07:35 +08:00
labelIDs , err := base . StringsToInt64s ( ctx . FormStrings ( "labels" ) )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "PullRequests" , err )
return
}
2024-10-08 05:21:07 +08:00
var posterID int64
if posterStr := ctx . FormString ( "poster" ) ; posterStr != "" {
poster , err := user_model . GetUserByName ( ctx , posterStr )
if err != nil {
if user_model . IsErrUserNotExist ( err ) {
ctx . Error ( http . StatusBadRequest , "Poster not found" , err )
} else {
ctx . Error ( http . StatusInternalServerError , "GetUserByName" , err )
}
return
}
posterID = poster . ID
}
2020-01-25 03:00:29 +08:00
listOptions := utils . GetListOptions ( ctx )
2023-09-15 14:13:19 +08:00
prs , maxResults , err := issues_model . PullRequests ( ctx , ctx . Repo . Repository . ID , & issues_model . PullRequestsOptions {
2020-01-25 03:00:29 +08:00
ListOptions : listOptions ,
2021-07-29 09:42:15 +08:00
State : ctx . FormTrim ( "state" ) ,
SortType : ctx . FormTrim ( "sort" ) ,
2024-03-21 23:07:35 +08:00
Labels : labelIDs ,
2021-07-29 09:42:15 +08:00
MilestoneID : ctx . FormInt64 ( "milestone" ) ,
2024-10-08 05:21:07 +08:00
PosterID : posterID ,
2016-12-02 19:10:39 +08:00
} )
if err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "PullRequests" , err )
2016-12-02 19:10:39 +08:00
return
}
2024-12-11 05:15:06 +08:00
apiPrs , err := convert . ToAPIPullRequests ( ctx , ctx . Repo . Repository , prs , ctx . Doer )
2024-05-31 20:10:11 +08:00
if err != nil {
2024-12-11 05:15:06 +08:00
ctx . Error ( http . StatusInternalServerError , "ToAPIPullRequests" , err )
2024-05-31 20:10:11 +08:00
return
}
2016-12-02 19:10:39 +08:00
2020-01-25 03:00:29 +08:00
ctx . SetLinkHeader ( int ( maxResults ) , listOptions . PageSize )
2021-08-12 20:43:08 +08:00
ctx . SetTotalCountHeader ( maxResults )
2019-12-21 01:07:12 +08:00
ctx . JSON ( http . StatusOK , & apiPrs )
2016-12-02 19:10:39 +08:00
}
// GetPullRequest returns a single PR based on index
func GetPullRequest ( ctx * context . APIContext ) {
2017-11-13 15:02:25 +08: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-21 11:40:42 +08:00
// format: int64
2017-11-13 15:02:25 +08:00
// required: true
// responses:
// "200":
// "$ref": "#/responses/PullRequest"
2020-01-09 19:56:32 +08:00
// "404":
// "$ref": "#/responses/notFound"
2019-12-21 01:07:12 +08:00
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2016-12-02 19:10:39 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2016-12-02 19:10:39 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 19:10:39 +08:00
}
return
}
2022-11-19 16:12:33 +08:00
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2020-03-03 06:31:55 +08:00
ctx . Error ( http . StatusInternalServerError , "LoadBaseRepo" , err )
2017-11-05 02:10:01 +08:00
return
}
2022-11-19 16:12:33 +08:00
if err = pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-03-03 06:31:55 +08:00
ctx . Error ( http . StatusInternalServerError , "LoadHeadRepo" , err )
2017-11-05 02:10:01 +08:00
return
}
2022-03-22 15:03:22 +08:00
ctx . JSON ( http . StatusOK , convert . ToAPIPullRequest ( ctx , pr , ctx . Doer ) )
2016-12-02 19:10:39 +08:00
}
2024-02-26 10:39:01 +08: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
2024-06-19 06:32:45 +08:00
head := ctx . PathParam ( "*" )
2024-02-26 10:39:01 +08:00
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
}
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByBaseHeadInfo ( ctx , ctx . Repo . Repository . ID , headRepoID , ctx . PathParam ( ":base" ) , headBranch )
2024-02-26 10:39:01 +08:00
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-22 07:04:53 +08: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 19:03:12 +08:00
// ---
2021-09-22 07:04:53 +08:00
// summary: Get a pull request diff or patch
2020-06-05 19:03:12 +08: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-22 07:04:53 +08:00
// - name: diffType
2020-06-05 19:03:12 +08:00
// in: path
2021-09-22 07:04:53 +08:00
// description: whether the output is diff or patch
2020-06-05 19:03:12 +08:00
// type: string
2021-09-22 07:04:53 +08:00
// enum: [diff, patch]
2020-06-05 19:03:12 +08:00
// required: true
2021-09-28 05:09:49 +08: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 19:03:12 +08:00
// responses:
// "200":
// "$ref": "#/responses/string"
// "404":
// "$ref": "#/responses/notFound"
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2020-06-05 19:03:12 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2020-06-05 19:03:12 +08:00
ctx . NotFound ( )
} else {
ctx . InternalServerError ( err )
}
return
}
2021-09-22 07:04:53 +08:00
var patch bool
2024-06-19 06:32:45 +08:00
if ctx . PathParam ( ":diffType" ) == "diff" {
2021-09-22 07:04:53 +08:00
patch = false
} else {
patch = true
}
2020-06-05 19:03:12 +08:00
2021-09-28 05:09:49 +08:00
binary := ctx . FormBool ( "binary" )
2022-01-20 07:26:57 +08:00
if err := pull_service . DownloadDiffOrPatch ( ctx , pr , ctx , patch , binary ) ; err != nil {
2020-06-05 19:03:12 +08:00
ctx . InternalServerError ( err )
return
}
}
2016-12-02 19:10:39 +08:00
// CreatePullRequest does what it says
2021-01-26 23:36:53 +08:00
func CreatePullRequest ( ctx * context . APIContext ) {
2017-11-13 15:02:25 +08: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 16:16:03 +08:00
// "403":
// "$ref": "#/responses/forbidden"
2023-09-13 10:37:54 +08:00
// "404":
// "$ref": "#/responses/notFound"
2019-12-21 01:07:12 +08:00
// "409":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2023-09-22 07:43:29 +08:00
// "423":
// "$ref": "#/responses/repoArchivedError"
2019-12-21 01:07:12 +08:00
2021-01-26 23:36:53 +08:00
form := * web . GetForm ( ctx ) . ( * api . CreatePullRequestOption )
2020-10-26 17:05:27 +08:00
if form . Head == form . Base {
2024-12-12 16:10:09 +08:00
ctx . Error ( http . StatusUnprocessableEntity , "BaseHeadSame" , "Invalid PullRequest: There are no changes between the head and the base" )
2020-10-26 17:05:27 +08:00
return
}
2016-12-02 19:10:39 +08:00
var (
repo = ctx . Repo . Repository
labelIDs [ ] int64
milestoneID int64
)
// Get repo/branch information
2024-12-12 16:10:09 +08:00
compareResult , closer := parseCompareInfo ( ctx , form )
2016-12-02 19:10:39 +08:00
if ctx . Written ( ) {
return
}
2024-12-12 16:10:09 +08:00
defer closer ( )
if ! compareResult . baseRef . IsBranch ( ) || ! compareResult . headRef . IsBranch ( ) {
ctx . Error ( http . StatusUnprocessableEntity , "BaseHeadInvalidRefType" , "Invalid PullRequest: base and head must be branches" )
return
}
2016-12-02 19:10:39 +08:00
// Check if another PR exists with the same targets
2024-12-12 16:10:09 +08:00
existingPr , err := issues_model . GetUnmergedPullRequest ( ctx , compareResult . headRepo . ID , ctx . Repo . Repository . ID ,
compareResult . headRef . ShortName ( ) , compareResult . baseRef . ShortName ( ) ,
issues_model . PullRequestFlowGithub ,
)
2016-12-02 19:10:39 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if ! issues_model . IsErrPullRequestNotExist ( err ) {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetUnmergedPullRequest" , err )
2016-12-02 19:10:39 +08:00
return
}
} else {
2022-06-13 17:37:59 +08:00
err = issues_model . ErrPullRequestAlreadyExists {
2016-12-02 19:10:39 +08:00
ID : existingPr . ID ,
IssueID : existingPr . Index ,
HeadRepoID : existingPr . HeadRepoID ,
BaseRepoID : existingPr . BaseRepoID ,
HeadBranch : existingPr . HeadBranch ,
BaseBranch : existingPr . BaseBranch ,
}
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusConflict , "GetUnmergedPullRequest" , err )
2016-12-02 19:10:39 +08:00
return
}
if len ( form . Labels ) > 0 {
2022-11-19 16:12:33 +08:00
labels , err := issues_model . GetLabelsInRepoByIDs ( ctx , ctx . Repo . Repository . ID , form . Labels )
2016-12-02 19:10:39 +08:00
if err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetLabelsInRepoByIDs" , err )
2016-12-02 19:10:39 +08:00
return
}
2023-08-29 23:47:26 +08:00
labelIDs = make ( [ ] int64 , 0 , len ( labels ) )
for _ , label := range labels {
labelIDs = append ( labelIDs , label . ID )
2016-12-02 19:10:39 +08:00
}
2020-04-20 21:10:45 +08:00
if ctx . Repo . Owner . IsOrganization ( ) {
2022-11-19 16:12:33 +08:00
orgLabels , err := issues_model . GetLabelsInOrgByIDs ( ctx , ctx . Repo . Owner . ID , form . Labels )
2020-04-20 21:10:45 +08:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetLabelsInOrgByIDs" , err )
return
}
2023-08-29 23:47:26 +08:00
orgLabelIDs := make ( [ ] int64 , 0 , len ( orgLabels ) )
for _ , orgLabel := range orgLabels {
orgLabelIDs = append ( orgLabelIDs , orgLabel . ID )
2020-04-20 21:10:45 +08:00
}
2023-08-29 23:47:26 +08:00
labelIDs = append ( labelIDs , orgLabelIDs ... )
2020-04-20 21:10:45 +08:00
}
2016-12-02 19:10:39 +08:00
}
if form . Milestone > 0 {
2022-04-08 17:11:15 +08:00
milestone , err := issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , form . Milestone )
2016-12-02 19:10:39 +08:00
if err != nil {
2022-04-08 17:11:15 +08:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2016-12-02 19:10:39 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetMilestoneByRepoID" , err )
2016-12-02 19:10:39 +08:00
}
return
}
milestoneID = milestone . ID
}
2019-08-15 22:46:21 +08:00
var deadlineUnix timeutil . TimeStamp
2018-05-02 03:05:28 +08:00
if form . Deadline != nil {
2019-08-15 22:46:21 +08:00
deadlineUnix = timeutil . TimeStamp ( form . Deadline . Unix ( ) )
2018-05-02 03:05:28 +08:00
}
2022-06-13 17:37:59 +08:00
prIssue := & issues_model . Issue {
2018-05-02 03:05:28 +08:00
RepoID : repo . ID ,
Title : form . Title ,
2022-03-22 15:03:22 +08:00
PosterID : ctx . Doer . ID ,
Poster : ctx . Doer ,
2018-05-02 03:05:28 +08:00
MilestoneID : milestoneID ,
IsPull : true ,
Content : form . Body ,
DeadlineUnix : deadlineUnix ,
2016-12-02 19:10:39 +08:00
}
2022-06-13 17:37:59 +08:00
pr := & issues_model . PullRequest {
2024-12-12 16:10:09 +08:00
HeadRepoID : compareResult . headRepo . ID ,
2019-10-18 19:13:31 +08:00
BaseRepoID : repo . ID ,
2024-12-12 16:10:09 +08:00
HeadBranch : compareResult . headRef . ShortName ( ) ,
BaseBranch : compareResult . baseRef . ShortName ( ) ,
HeadRepo : compareResult . headRepo ,
2019-10-18 19:13:31 +08:00
BaseRepo : repo ,
2024-12-12 16:10:09 +08:00
MergeBase : compareResult . compareInfo . MergeBase ,
2022-06-13 17:37:59 +08:00
Type : issues_model . PullRequestGitea ,
2016-12-02 19:10:39 +08:00
}
2018-05-10 00:29:04 +08:00
// Get all assignee IDs
2022-11-19 16:12:33 +08:00
assigneeIDs , err := issues_model . MakeIDsFromAPIAssigneesToAdd ( ctx , form . Assignee , form . Assignees )
2018-05-10 00:29:04 +08:00
if err != nil {
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Assignee does not exist: [name: %s]" , err ) )
2018-05-10 00:29:04 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "AddAssigneeByName" , err )
2018-05-10 00:29:04 +08:00
}
return
}
2019-10-25 22:46:37 +08:00
// Check if the passed assignees is assignable
for _ , aID := range assigneeIDs {
2022-12-03 10:48:26 +08:00
assignee , err := user_model . GetUserByID ( ctx , aID )
2019-10-25 22:46:37 +08:00
if err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetUserByID" , err )
2019-10-25 22:46:37 +08:00
return
}
2018-05-10 00:29:04 +08:00
2022-05-11 18:09:36 +08:00
valid , err := access_model . CanBeAssigned ( ctx , assignee , repo , true )
2019-10-25 22:46:37 +08:00
if err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "canBeAssigned" , err )
2019-10-25 22:46:37 +08:00
return
}
if ! valid {
2022-06-13 17:37:59 +08:00
ctx . Error ( http . StatusUnprocessableEntity , "canBeAssigned" , repo_model . ErrUserDoesNotHaveAccessToRepo { UserID : aID , RepoName : repo . Name } )
2019-10-25 22:46:37 +08:00
return
}
}
2024-11-09 12:48:31 +08:00
prOpts := & pull_service . NewPullRequestOptions {
Repo : repo ,
Issue : prIssue ,
LabelIDs : labelIDs ,
PullRequest : pr ,
AssigneeIDs : assigneeIDs ,
}
prOpts . Reviewers , prOpts . TeamReviewers = parseReviewersByNames ( ctx , form . Reviewers , form . TeamReviewers )
if ctx . Written ( ) {
return
}
if err := pull_service . NewPullRequest ( ctx , prOpts ) ; err != nil {
2022-06-13 17:37:59 +08:00
if repo_model . IsErrUserDoesNotHaveAccessToRepo ( err ) {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusBadRequest , "UserDoesNotHaveAccessToRepo" , err )
2024-03-04 16:16:03 +08:00
} else if errors . Is ( err , user_model . ErrBlockedUser ) {
ctx . Error ( http . StatusForbidden , "BlockedUser" , err )
2024-07-29 10:21:22 +08:00
} else if errors . Is ( err , issues_model . ErrMustCollaborator ) {
ctx . Error ( http . StatusForbidden , "MustCollaborator" , err )
2024-03-04 16:16:03 +08:00
} else {
ctx . Error ( http . StatusInternalServerError , "NewPullRequest" , err )
2018-05-10 00:29:04 +08:00
}
2016-12-02 19:10:39 +08:00
return
}
log . Trace ( "Pull request created: %d/%d" , repo . ID , prIssue . ID )
2022-03-22 15:03:22 +08:00
ctx . JSON ( http . StatusCreated , convert . ToAPIPullRequest ( ctx , pr , ctx . Doer ) )
2016-12-02 19:10:39 +08:00
}
// EditPullRequest does what it says
2021-01-26 23:36:53 +08:00
func EditPullRequest ( ctx * context . APIContext ) {
2017-11-13 15:02:25 +08:00
// swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest
// ---
2019-11-03 22:46:32 +08: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 15:02:25 +08: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-21 11:40:42 +08:00
// format: int64
2017-11-13 15:02:25 +08:00
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditPullRequestOption"
// responses:
// "201":
// "$ref": "#/responses/PullRequest"
2019-12-21 01:07:12 +08:00
// "403":
// "$ref": "#/responses/forbidden"
2023-09-13 10:37:54 +08:00
// "404":
// "$ref": "#/responses/notFound"
2020-06-08 03:13:40 +08:00
// "409":
// "$ref": "#/responses/error"
2019-12-21 01:07:12 +08:00
// "412":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2021-01-26 23:36:53 +08:00
form := web . GetForm ( ctx ) . ( * api . EditPullRequestOption )
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2016-12-02 19:10:39 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2016-12-02 19:10:39 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 19:10:39 +08:00
}
return
}
2022-11-19 16:12:33 +08:00
err = pr . LoadIssue ( ctx )
2019-06-13 03:41:28 +08:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadIssue" , err )
return
}
2016-12-02 19:10:39 +08:00
issue := pr . Issue
2018-12-13 23:55:43 +08:00
issue . Repo = ctx . Repo . Repository
2016-12-02 19:10:39 +08:00
2022-12-22 06:45:44 +08:00
if err := issue . LoadAttributes ( ctx ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "LoadAttributes" , err )
return
}
2022-03-22 15:03:22 +08:00
if ! issue . IsPoster ( ctx . Doer . ID ) && ! ctx . Repo . CanWrite ( unit . TypePullRequests ) {
2019-12-21 01:07:12 +08:00
ctx . Status ( http . StatusForbidden )
2016-12-02 19:10:39 +08:00
return
}
if len ( form . Title ) > 0 {
2024-05-03 14:11:51 +08:00
err = issue_service . ChangeTitle ( ctx , issue , ctx . Doer , form . Title )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "ChangeTitle" , err )
return
}
2016-12-02 19:10:39 +08:00
}
2024-05-03 14:11:51 +08:00
if form . Body != nil {
2024-05-27 23:34:18 +08:00
err = issue_service . ChangeContent ( ctx , issue , ctx . Doer , * form . Body , issue . ContentVersion )
2024-05-03 14:11:51 +08:00
if err != nil {
2024-05-27 23:34:18 +08:00
if errors . Is ( err , issues_model . ErrIssueAlreadyChanged ) {
ctx . Error ( http . StatusBadRequest , "ChangeContent" , err )
return
}
2024-05-03 14:11:51 +08:00
ctx . Error ( http . StatusInternalServerError , "ChangeContent" , err )
return
}
2016-12-02 19:10:39 +08:00
}
2019-11-03 22:46:32 +08: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 20:12:54 +08:00
if err := issues_model . UpdateIssueDeadline ( ctx , issue , deadlineUnix , ctx . Doer ) ; err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "UpdateIssueDeadline" , err )
2019-10-28 07:35:20 +08:00
return
}
issue . DeadlineUnix = deadlineUnix
2018-05-02 03:05:28 +08:00
}
2018-05-10 00:29:04 +08:00
// Add/delete assignees
2016-12-02 19:10:39 +08:00
2019-03-10 05:15:45 +08:00
// Deleting is done the GitHub way (quote from their api documentation):
2018-05-10 00:29:04 +08: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-10 03:57:58 +08:00
if ctx . Repo . CanWrite ( unit . TypePullRequests ) && ( form . Assignees != nil || len ( form . Assignee ) > 0 ) {
2023-04-15 02:18:28 +08:00
err = issue_service . UpdateAssignees ( ctx , issue , form . Assignee , form . Assignees , ctx . Doer )
2018-05-10 00:29:04 +08:00
if err != nil {
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Assignee does not exist: [name: %s]" , err ) )
2024-03-04 16:16:03 +08:00
} else if errors . Is ( err , user_model . ErrBlockedUser ) {
ctx . Error ( http . StatusForbidden , "UpdateAssignees" , err )
2018-05-10 00:29:04 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "UpdateAssignees" , err )
2018-05-10 00:29:04 +08:00
}
2016-12-02 19:10:39 +08:00
return
}
}
2018-05-10 00:29:04 +08:00
2021-11-10 03:57:58 +08:00
if ctx . Repo . CanWrite ( unit . TypePullRequests ) && form . Milestone != 0 &&
2016-12-02 19:10:39 +08:00
issue . MilestoneID != form . Milestone {
oldMilestoneID := issue . MilestoneID
issue . MilestoneID = form . Milestone
2023-10-11 12:24:07 +08:00
if err = issue_service . ChangeMilestoneAssign ( ctx , issue , ctx . Doer , oldMilestoneID ) ; err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "ChangeMilestoneAssign" , err )
2016-12-02 19:10:39 +08:00
return
}
}
2021-11-10 03:57:58 +08:00
if ctx . Repo . CanWrite ( unit . TypePullRequests ) && form . Labels != nil {
2022-11-19 16:12:33 +08:00
labels , err := issues_model . GetLabelsInRepoByIDs ( ctx , ctx . Repo . Repository . ID , form . Labels )
2018-11-16 19:12:44 +08:00
if err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetLabelsInRepoByIDsError" , err )
2018-11-16 19:12:44 +08:00
return
}
2020-04-20 21:10:45 +08:00
if ctx . Repo . Owner . IsOrganization ( ) {
2022-11-19 16:12:33 +08:00
orgLabels , err := issues_model . GetLabelsInOrgByIDs ( ctx , ctx . Repo . Owner . ID , form . Labels )
2020-04-20 21:10:45 +08:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetLabelsInOrgByIDs" , err )
return
}
labels = append ( labels , orgLabels ... )
}
2023-10-11 12:24:07 +08:00
if err = issues_model . ReplaceIssueLabels ( ctx , issue , labels , ctx . Doer ) ; err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "ReplaceLabelsError" , err )
2018-11-16 19:12:44 +08:00
return
}
}
2016-12-02 19:10:39 +08:00
if form . State != nil {
2021-10-03 11:11:17 +08:00
if pr . HasMerged {
ctx . Error ( http . StatusPreconditionFailed , "MergedPRState" , "cannot change state of this pull request, it was already merged" )
return
}
2024-09-02 02:08:27 +08:00
var isClosed bool
switch state := api . StateType ( * form . State ) ; state {
case api . StateOpen :
isClosed = false
case api . StateClosed :
isClosed = true
default :
ctx . Error ( http . StatusPreconditionFailed , "UnknownPRStateError" , fmt . Sprintf ( "unknown state: %s" , state ) )
return
}
if issue . IsClosed != isClosed {
if err := issue_service . ChangeStatus ( ctx , issue , ctx . Doer , "" , isClosed ) ; 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 )
2024-05-03 14:11:51 +08:00
return
}
2016-12-02 19:10:39 +08:00
}
}
2020-06-08 03:13:40 +08:00
// change pull target branch
2021-10-03 11:11:17 +08:00
if ! pr . HasMerged && len ( form . Base ) != 0 && form . Base != pr . BaseBranch {
2020-06-08 03:13:40 +08: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 15:03:22 +08:00
if err := pull_service . ChangeTargetBranch ( ctx , pr , ctx . Doer , form . Base ) ; err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestAlreadyExists ( err ) {
2020-06-08 03:13:40 +08:00
ctx . Error ( http . StatusConflict , "IsErrPullRequestAlreadyExists" , err )
return
2022-06-13 17:37:59 +08:00
} else if issues_model . IsErrIssueIsClosed ( err ) {
2020-06-08 03:13:40 +08:00
ctx . Error ( http . StatusUnprocessableEntity , "IsErrIssueIsClosed" , err )
return
} else if models . IsErrPullRequestHasMerged ( err ) {
ctx . Error ( http . StatusConflict , "IsErrPullRequestHasMerged" , err )
return
}
2023-10-24 10:54:59 +08:00
ctx . InternalServerError ( err )
2020-06-08 03:13:40 +08:00
return
}
2023-09-06 02:37:47 +08:00
notify_service . PullRequestChangeTargetBranch ( ctx , ctx . Doer , pr , form . Base )
2020-06-08 03:13:40 +08:00
}
2022-04-28 23:45:33 +08:00
// update allow edits
if form . AllowMaintainerEdit != nil {
if err := pull_service . SetAllowEdits ( ctx , ctx . Doer , pr , * form . AllowMaintainerEdit ) ; err != nil {
2024-09-10 10:23:07 +08:00
if errors . Is ( err , pull_service . ErrUserHasNoPermissionForAction ) {
2022-04-28 23:45:33 +08:00
ctx . Error ( http . StatusForbidden , "SetAllowEdits" , fmt . Sprintf ( "SetAllowEdits: %s" , err ) )
return
}
ctx . ServerError ( "SetAllowEdits" , err )
return
}
}
2016-12-02 19:10:39 +08:00
// Refetch from database
2022-06-13 17:37:59 +08:00
pr , err = issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , pr . Index )
2016-12-02 19:10:39 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2016-12-02 19:10:39 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 19:10:39 +08:00
}
return
}
2017-11-13 15:02:25 +08:00
// TODO this should be 200, not 201
2022-03-22 15:03:22 +08:00
ctx . JSON ( http . StatusCreated , convert . ToAPIPullRequest ( ctx , pr , ctx . Doer ) )
2016-12-02 19:10:39 +08:00
}
// IsPullRequestMerged checks if a PR exists given an index
func IsPullRequestMerged ( ctx * context . APIContext ) {
2017-11-13 15:02:25 +08: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-21 11:40:42 +08:00
// format: int64
2017-11-13 15:02:25 +08:00
// required: true
// responses:
// "204":
// description: pull request has been merged
// "404":
// description: pull request has not been merged
2019-12-21 01:07:12 +08:00
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2016-12-02 19:10:39 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2016-12-02 19:10:39 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 19:10:39 +08:00
}
return
}
if pr . HasMerged {
2019-12-21 01:07:12 +08:00
ctx . Status ( http . StatusNoContent )
2016-12-02 19:10:39 +08:00
}
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2016-12-02 19:10:39 +08:00
}
// MergePullRequest merges a PR given an index
2021-01-26 23:36:53 +08:00
func MergePullRequest ( ctx * context . APIContext ) {
2017-11-13 15:02:25 +08: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-21 11:40:42 +08:00
// format: int64
2017-11-13 15:02:25 +08:00
// required: true
2019-02-08 16:08:38 +08:00
// - name: body
// in: body
// schema:
// $ref: "#/definitions/MergePullRequestOption"
2017-11-13 15:02:25 +08:00
// responses:
// "200":
// "$ref": "#/responses/empty"
2023-09-13 10:37:54 +08:00
// "404":
// "$ref": "#/responses/notFound"
2017-11-13 15:02:25 +08:00
// "405":
// "$ref": "#/responses/empty"
2019-12-21 01:07:12 +08:00
// "409":
// "$ref": "#/responses/error"
2023-09-22 07:43:29 +08:00
// "423":
// "$ref": "#/responses/repoArchivedError"
2019-12-21 01:07:12 +08:00
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . MergePullRequestForm )
2022-05-04 03:46:28 +08:00
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2016-12-02 19:10:39 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2018-01-11 05:34:17 +08:00
ctx . NotFound ( "GetPullRequestByIndex" , err )
2016-12-02 19:10:39 +08:00
} else {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
2016-12-02 19:10:39 +08:00
}
return
}
2022-11-19 16:12:33 +08:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-09-21 04:20:14 +08:00
ctx . Error ( http . StatusInternalServerError , "LoadHeadRepo" , err )
2016-12-02 19:10:39 +08:00
return
}
2022-11-19 16:12:33 +08:00
if err := pr . LoadIssue ( ctx ) ; err != nil {
2019-06-13 03:41:28 +08:00
ctx . Error ( http . StatusInternalServerError , "LoadIssue" , err )
return
}
2016-12-02 19:10:39 +08:00
pr . Issue . Repo = ctx . Repo . Repository
if ctx . IsSigned {
// Update issue-user.
2022-08-25 10:31:57 +08:00
if err = activities_model . SetIssueReadBy ( ctx , pr . Issue . ID , ctx . Doer . ID ) ; err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "ReadBy" , err )
2016-12-02 19:10:39 +08:00
return
}
}
2023-02-21 22:42:07 +08: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-18 13:39:45 +08:00
2022-05-04 03:46:28 +08:00
// start with merging by checking
2024-05-09 00:11:43 +08:00
if err := pull_service . CheckPullMergeable ( ctx , ctx . Doer , & ctx . Repo . Permission , pr , mergeCheckType , form . ForceMerge ) ; err != nil {
2022-03-31 22:53:08 +08: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-09 00:11:43 +08:00
} else if errors . Is ( err , pull_service . ErrNotMergeableState ) {
2022-03-31 22:53:08 +08: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-20 05:35:06 +08:00
return
}
2021-03-04 11:41:23 +08:00
// handle manually-merged mark
2023-02-21 22:42:07 +08:00
if manuallyMerged {
2023-10-14 16:37:24 +08:00
if err := pull_service . MergedManually ( ctx , pr , ctx . Doer , ctx . Repo . GitRepo , form . MergeCommitID ) ; err != nil {
2021-03-04 11:41:23 +08:00
if models . IsErrInvalidMergeStyle ( err ) {
2021-12-10 09:27:50 +08: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-04 11:41:23 +08: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 20:32:45 +08:00
if len ( form . Do ) == 0 {
form . Do = string ( repo_model . MergeStyleMerge )
}
message := strings . TrimSpace ( form . MergeTitleField )
if len ( message ) == 0 {
2022-12-29 20:40:20 +08:00
message , _ , err = pull_service . GetDefaultMergeMessage ( ctx , ctx . Repo . GitRepo , pr , repo_model . MergeStyle ( form . Do ) )
2022-05-08 20:32:45 +08: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-20 05:35:06 +08:00
}
2022-05-08 01:05:52 +08:00
if form . MergeWhenChecksSucceed {
2022-05-08 20:32:45 +08:00
scheduled , err := automerge . ScheduleAutoMerge ( ctx , ctx . Doer , pr , repo_model . MergeStyle ( form . Do ) , message )
2022-05-08 01:05:52 +08: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 23:49:00 +08: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-06 02:56:50 +08:00
if models . IsErrInvalidMergeStyle ( err ) {
2021-12-10 09:27:50 +08: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 16:42:51 +08: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-28 12:13:18 +08:00
} else if git . IsErrPushOutOfDate ( err ) {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusConflict , "Merge" , "merge push out of date" )
2021-12-20 08:32:54 +08:00
} else if models . IsErrSHADoesNotMatch ( err ) {
ctx . Error ( http . StatusConflict , "Merge" , "head out of date" )
2020-03-28 12:13:18 +08:00
} else if git . IsErrPushRejected ( err ) {
errPushRej := err . ( * git . ErrPushRejected )
2020-02-22 21:08:48 +08:00
if len ( errPushRej . Message ) == 0 {
ctx . Error ( http . StatusConflict , "Merge" , "PushRejected without remote error message" )
2022-05-04 03:46:28 +08:00
} else {
ctx . Error ( http . StatusConflict , "Merge" , "PushRejected with remote message: " + errPushRej . Message )
2020-02-22 21:08:48 +08:00
}
2022-05-04 03:46:28 +08:00
} else {
ctx . Error ( http . StatusInternalServerError , "Merge" , err )
2018-01-06 02:56:50 +08:00
}
2016-12-02 19:10:39 +08:00
return
}
log . Trace ( "Pull request merged: %d" , pr . ID )
2021-07-13 07:26:25 +08:00
2024-12-04 11:59:48 +08:00
// for agit flow, we should not delete the agit reference after merge
if form . DeleteBranchAfterMerge && pr . Flow == issues_model . PullRequestFlowGithub {
// check permission even it has been checked in repo_service.DeleteBranch so that we don't need to
// do RetargetChildrenOnMerge
if err := repo_service . CanDeleteBranch ( ctx , pr . HeadRepo , pr . HeadBranch , ctx . Doer ) ; err == nil {
// Don't cleanup when there are other PR's that use this branch as head branch.
exist , err := issues_model . HasUnmergedPullRequestsByHeadInfo ( ctx , pr . HeadRepoID , pr . HeadBranch )
2021-07-13 07:26:25 +08:00
if err != nil {
2024-12-04 11:59:48 +08:00
ctx . ServerError ( "HasUnmergedPullRequestsByHeadInfo" , err )
2021-07-13 07:26:25 +08:00
return
}
2024-12-04 11:59:48 +08:00
if exist {
ctx . Status ( http . StatusOK )
return
}
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 {
headRepo , err = gitrepo . OpenRepository ( ctx , pr . HeadRepo )
if err != nil {
ctx . ServerError ( fmt . Sprintf ( "OpenRepository[%s]" , pr . HeadRepo . FullName ( ) ) , err )
return
}
defer headRepo . Close ( )
}
if err := pull_service . RetargetChildrenOnMerge ( ctx , ctx . Doer , pr ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "RetargetChildrenOnMerge" , err )
return
}
if err := repo_service . DeleteBranch ( ctx , ctx . Doer , pr . HeadRepo , headRepo , pr . HeadBranch ) ; err != nil {
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" ) )
case errors . Is ( err , git_model . ErrBranchIsProtected ) :
ctx . Error ( http . StatusForbidden , "IsProtectedBranch" , fmt . Errorf ( "branch protected" ) )
default :
ctx . Error ( http . StatusInternalServerError , "DeleteBranch" , err )
}
return
}
if err := issues_model . AddDeletePRBranchComment ( ctx , ctx . Doer , pr . BaseRepo , pr . Issue . ID , pr . HeadBranch ) ; err != nil {
// Do not fail here as branch has already been deleted
log . Error ( "DeleteBranch: %v" , err )
2021-07-13 07:26:25 +08:00
}
}
}
2019-12-21 01:07:12 +08:00
ctx . Status ( http . StatusOK )
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
type parseCompareInfoResult struct {
headRepo * repo_model . Repository
headGitRepo * git . Repository
compareInfo * git . CompareInfo
baseRef git . RefName
headRef git . RefName
}
2016-12-02 19:10:39 +08:00
2024-12-12 16:10:09 +08:00
// parseCompareInfo returns non-nil if it succeeds, it always writes to the context and returns nil if it fails
func parseCompareInfo ( ctx * context . APIContext , form api . CreatePullRequestOption ) ( result * parseCompareInfoResult , closer func ( ) ) {
var err error
2016-12-02 19:10:39 +08:00
// Get compared branches information
// format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature
// same repo: master...feature
2024-12-12 16:10:09 +08:00
baseRepo := ctx . Repo . Repository
baseRefToGuess := form . Base
2016-12-02 19:10:39 +08:00
2024-12-12 16:10:09 +08:00
headUser := ctx . Repo . Owner
headRefToGuess := form . Head
if headInfos := strings . Split ( form . Head , ":" ) ; len ( headInfos ) == 1 {
// If there is no head repository, it means pull request between same repository.
// Do nothing here because the head variables have been assigned above.
2016-12-02 19:10:39 +08:00
} else if len ( headInfos ) == 2 {
2024-12-12 16:10:09 +08:00
// There is a head repository (the head repository could also be the same base repo)
headRefToGuess = headInfos [ 1 ]
2022-05-20 22:08:52 +08:00
headUser , err = user_model . GetUserByName ( ctx , headInfos [ 0 ] )
2016-12-02 19:10:39 +08:00
if err != nil {
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2019-05-08 01:20:23 +08:00
ctx . NotFound ( "GetUserByName" )
2016-12-02 19:10:39 +08:00
} else {
2020-09-21 04:20:14 +08:00
ctx . Error ( http . StatusInternalServerError , "GetUserByName" , err )
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
return nil , nil
2016-12-02 19:10:39 +08:00
}
} else {
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2024-12-12 16:10:09 +08:00
return nil , nil
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
isSameRepo := ctx . Repo . Owner . ID == headUser . ID
2016-12-02 19:10:39 +08:00
// Check if current user has fork of repository or in the same repository.
2023-09-15 01:09:32 +08:00
headRepo := repo_model . GetForkedRepo ( ctx , headUser . ID , baseRepo . ID )
2021-11-22 23:21:55 +08:00
if headRepo == nil && ! isSameRepo {
2024-12-12 16:10:09 +08:00
err = baseRepo . GetBaseRepo ( ctx )
2024-10-05 01:12:48 +08:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetBaseRepo" , err )
2024-12-12 16:10:09 +08:00
return nil , nil
2024-10-05 01:12:48 +08:00
}
// Check if baseRepo's base repository is the same as headUser's repository.
if baseRepo . BaseRepo == nil || baseRepo . BaseRepo . OwnerID != headUser . ID {
log . Trace ( "parseCompareInfo[%d]: does not have fork or in same repository" , baseRepo . ID )
ctx . NotFound ( "GetBaseRepo" )
2024-12-12 16:10:09 +08:00
return nil , nil
2024-10-05 01:12:48 +08:00
}
// Assign headRepo so it can be used below.
headRepo = baseRepo . BaseRepo
2016-12-02 19:10:39 +08:00
}
var headGitRepo * git . Repository
if isSameRepo {
headRepo = ctx . Repo . Repository
headGitRepo = ctx . Repo . GitRepo
2024-12-12 16:10:09 +08:00
closer = func ( ) { } // no need to close the head repo because it shares the base repo
2016-12-02 19:10:39 +08:00
} 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-28 04:09:51 +08:00
headGitRepo , err = gitrepo . OpenRepository ( ctx , headRepo )
2016-12-02 19:10:39 +08:00
if err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "OpenRepository" , err )
2024-12-12 16:10:09 +08:00
return nil , nil
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
closer = func ( ) { _ = headGitRepo . Close ( ) }
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
defer func ( ) {
if result == nil && ! isSameRepo {
_ = headGitRepo . Close ( )
}
} ( )
2016-12-02 19:10:39 +08:00
2019-05-08 01:20:23 +08:00
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
2022-05-11 18:09:36 +08:00
permBase , err := access_model . GetUserRepoPermission ( ctx , baseRepo , ctx . Doer )
2018-11-28 19:26:14 +08:00
if err != nil {
2020-09-21 04:20:14 +08:00
ctx . Error ( http . StatusInternalServerError , "GetUserRepoPermission" , err )
2024-12-12 16:10:09 +08:00
return nil , nil
2018-11-28 19:26:14 +08:00
}
2024-12-12 16:10:09 +08:00
2021-11-10 03:57:58 +08:00
if ! permBase . CanReadIssuesOrPulls ( true ) || ! permBase . CanRead ( unit . TypeCode ) {
2024-12-12 16:10:09 +08: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" , ctx . Doer , baseRepo , permBase )
2019-05-08 01:20:23 +08:00
ctx . NotFound ( "Can't read pulls or can't read UnitTypeCode" )
2024-12-12 16:10:09 +08:00
return nil , nil
2019-05-08 01:20:23 +08:00
}
2024-12-12 16:10:09 +08:00
// user should have permission to read headRepo's codes
// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
2022-05-11 18:09:36 +08:00
permHead , err := access_model . GetUserRepoPermission ( ctx , headRepo , ctx . Doer )
2019-05-08 01:20:23 +08:00
if err != nil {
2020-09-21 04:20:14 +08:00
ctx . Error ( http . StatusInternalServerError , "GetUserRepoPermission" , err )
2024-12-12 16:10:09 +08:00
return nil , nil
2019-05-08 01:20:23 +08:00
}
2021-11-10 03:57:58 +08:00
if ! permHead . CanRead ( unit . TypeCode ) {
2024-12-12 16:10:09 +08:00
log . Trace ( "Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v" , ctx . Doer , headRepo , permHead )
2019-05-08 01:20:23 +08:00
ctx . NotFound ( "Can't read headRepo UnitTypeCode" )
2024-12-12 16:10:09 +08:00
return nil , nil
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
baseRef := ctx . Repo . GitRepo . UnstableGuessRefByShortName ( baseRefToGuess )
headRef := headGitRepo . UnstableGuessRefByShortName ( headRefToGuess )
log . Trace ( "Repo path: %q, base ref: %q->%q, head ref: %q->%q" , ctx . Repo . GitRepo . Path , baseRefToGuess , baseRef , headRefToGuess , headRef )
baseRefValid := baseRef . IsBranch ( ) || baseRef . IsTag ( ) || git . IsStringLikelyCommitID ( git . ObjectFormatFromName ( ctx . Repo . Repository . ObjectFormatName ) , baseRef . ShortName ( ) )
headRefValid := headRef . IsBranch ( ) || headRef . IsTag ( ) || git . IsStringLikelyCommitID ( git . ObjectFormatFromName ( headRepo . ObjectFormatName ) , headRef . ShortName ( ) )
// Check if base&head ref are valid.
if ! baseRefValid || ! headRefValid {
2019-03-19 10:29:43 +08:00
ctx . NotFound ( )
2024-12-12 16:10:09 +08:00
return nil , nil
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
compareInfo , err := headGitRepo . GetCompareInfo ( repo_model . RepoPath ( baseRepo . Owner . Name , baseRepo . Name ) , baseRef . ShortName ( ) , headRef . ShortName ( ) , false , false )
2016-12-02 19:10:39 +08:00
if err != nil {
2019-12-21 01:07:12 +08:00
ctx . Error ( http . StatusInternalServerError , "GetCompareInfo" , err )
2024-12-12 16:10:09 +08:00
return nil , nil
2016-12-02 19:10:39 +08:00
}
2024-12-12 16:10:09 +08:00
result = & parseCompareInfoResult { headRepo : headRepo , headGitRepo : headGitRepo , compareInfo : compareInfo , baseRef : baseRef , headRef : headRef }
return result , closer
2016-12-02 19:10:39 +08:00
}
2020-08-05 04:55:22 +08: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 22:03:45 +08:00
// - name: style
// in: query
// description: how to update pull request
// type: string
// enum: [merge, rebase]
2020-08-05 04:55:22 +08:00
// responses:
// "200":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "409":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2020-08-05 04:55:22 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2020-08-05 04:55:22 +08:00
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
}
return
}
if pr . HasMerged {
ctx . Error ( http . StatusUnprocessableEntity , "UpdatePullRequest" , err )
return
}
2022-11-19 16:12:33 +08:00
if err = pr . LoadIssue ( ctx ) ; err != nil {
2020-08-05 04:55:22 +08:00
ctx . Error ( http . StatusInternalServerError , "LoadIssue" , err )
return
}
if pr . Issue . IsClosed {
ctx . Error ( http . StatusUnprocessableEntity , "UpdatePullRequest" , err )
return
}
2022-11-19 16:12:33 +08:00
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2020-08-05 04:55:22 +08:00
ctx . Error ( http . StatusInternalServerError , "LoadBaseRepo" , err )
return
}
2022-11-19 16:12:33 +08:00
if err = pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-08-05 04:55:22 +08:00
ctx . Error ( http . StatusInternalServerError , "LoadHeadRepo" , err )
return
}
2021-08-31 22:03:45 +08:00
rebase := ctx . FormString ( "style" ) == "rebase"
2022-04-28 19:48:48 +08:00
allowedUpdateByMerge , allowedUpdateByRebase , err := pull_service . IsUserAllowedToUpdate ( ctx , pr , ctx . Doer )
2020-08-05 04:55:22 +08:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "IsUserAllowedToMerge" , err )
return
}
2021-08-31 22:03:45 +08:00
if ( ! allowedUpdateByMerge && ! rebase ) || ( rebase && ! allowedUpdateByRebase ) {
2020-08-05 04:55:22 +08: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 15:03:22 +08:00
if err = pull_service . Update ( ctx , pr , ctx . Doer , message , rebase ) ; err != nil {
2020-08-05 04:55:22 +08:00
if models . IsErrMergeConflicts ( err ) {
ctx . Error ( http . StatusConflict , "Update" , "merge failed because of conflict" )
return
2021-09-05 17:30:40 +08:00
} else if models . IsErrRebaseConflicts ( err ) {
ctx . Error ( http . StatusConflict , "Update" , "rebase failed because of conflict" )
return
2020-08-05 04:55:22 +08:00
}
ctx . Error ( http . StatusInternalServerError , "pull_service.Update" , err )
return
}
ctx . Status ( http . StatusOK )
}
2021-07-02 20:19:57 +08:00
2022-05-08 01:05:52 +08: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-22 07:43:29 +08:00
// "423":
// "$ref": "#/responses/repoArchivedError"
2022-05-08 01:05:52 +08:00
2024-06-19 06:32:45 +08:00
pullIndex := ctx . PathParamInt64 ( ":index" )
2022-06-13 17:37:59 +08:00
pull , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , pullIndex )
2022-05-08 01:05:52 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2022-05-08 01:05:52 +08: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 22:08:52 +08:00
allowed , err := access_model . IsUserRepoAdmin ( ctx , ctx . Repo . Repository , ctx . Doer )
2022-05-08 01:05:52 +08: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 21:46:34 +08:00
if err := automerge . RemoveScheduledAutoMerge ( ctx , ctx . Doer , pull ) ; err != nil {
2022-05-08 01:05:52 +08:00
ctx . InternalServerError ( err )
} else {
ctx . Status ( http . StatusNoContent )
}
}
2021-07-02 20:19:57 +08: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 19:22:12 +08: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 20:19:57 +08:00
// responses:
// "200":
// "$ref": "#/responses/CommitList"
// "404":
// "$ref": "#/responses/notFound"
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2021-07-02 20:19:57 +08:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrPullRequestNotExist ( err ) {
2021-07-02 20:19:57 +08:00
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
}
return
}
2022-11-19 16:12:33 +08:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2021-07-02 20:19:57 +08: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-28 04:09:51 +08:00
baseGitRepo , closer , err := gitrepo . RepositoryFromContextOrOpen ( ctx , pr . BaseRepo )
2021-07-02 20:19:57 +08:00
if err != nil {
ctx . ServerError ( "OpenRepository" , err )
return
}
2022-01-20 07:26:57 +08:00
defer closer . Close ( )
2021-07-02 20:19:57 +08:00
if pr . HasMerged {
2022-01-18 15:45:43 +08:00
prInfo , err = baseGitRepo . GetCompareInfo ( pr . BaseRepo . RepoPath ( ) , pr . MergeBase , pr . GetGitRefName ( ) , false , false )
2021-07-02 20:19:57 +08:00
} else {
2022-01-18 15:45:43 +08:00
prInfo , err = baseGitRepo . GetCompareInfo ( pr . BaseRepo . RepoPath ( ) , pr . BaseBranch , pr . GetGitRefName ( ) , false , false )
2021-07-02 20:19:57 +08:00
}
if err != nil {
ctx . ServerError ( "GetCompareInfo" , err )
return
}
commits := prInfo . Commits
listOptions := utils . GetListOptions ( ctx )
2021-08-10 02:08:51 +08:00
totalNumberOfCommits := len ( commits )
2021-07-02 20:19:57 +08:00
totalNumberOfPages := int ( math . Ceil ( float64 ( totalNumberOfCommits ) / float64 ( listOptions . PageSize ) ) )
2021-11-24 17:49:20 +08:00
userCache := make ( map [ string ] * user_model . User )
2021-07-02 20:19:57 +08:00
2024-01-15 10:19:25 +08:00
start , limit := listOptions . GetSkipTake ( )
2021-07-02 20:19:57 +08:00
2024-01-15 10:19:25 +08:00
limit = min ( limit , totalNumberOfCommits - start )
limit = max ( limit , 0 )
2021-07-02 20:19:57 +08:00
2023-10-09 19:22:12 +08:00
verification := ctx . FormString ( "verification" ) == "" || ctx . FormBool ( "verification" )
files := ctx . FormString ( "files" ) == "" || ctx . FormBool ( "files" )
2024-01-15 10:19:25 +08:00
apiCommits := make ( [ ] * api . Commit , 0 , limit )
for i := start ; i < start + limit ; i ++ {
2023-10-09 19:22:12 +08:00
apiCommit , err := convert . ToCommit ( ctx , ctx . Repo . Repository , baseGitRepo , commits [ i ] , userCache ,
convert . ToCommitOptions {
Stat : true ,
Verification : verification ,
Files : files ,
} )
2021-07-02 20:19:57 +08:00
if err != nil {
ctx . ServerError ( "toCommit" , err )
return
}
2021-08-10 02:08:51 +08:00
apiCommits = append ( apiCommits , apiCommit )
2021-07-02 20:19:57 +08:00
}
2021-08-12 20:43:08 +08:00
ctx . SetLinkHeader ( totalNumberOfCommits , listOptions . PageSize )
ctx . SetTotalCountHeader ( int64 ( totalNumberOfCommits ) )
2021-07-02 20:19:57 +08:00
2021-12-15 14:59:57 +08: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 20:43:08 +08:00
ctx . AppendAccessControlExposeHeaders ( "X-Page" , "X-PerPage" , "X-PageCount" , "X-HasMore" )
2021-07-02 20:19:57 +08:00
ctx . JSON ( http . StatusOK , & apiCommits )
}
2022-09-29 10:27:20 +08: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"
2024-06-19 06:32:45 +08:00
pr , err := issues_model . GetPullRequestByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":index" ) )
2022-09-29 10:27:20 +08:00
if err != nil {
if issues_model . IsErrPullRequestNotExist ( err ) {
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetPullRequestByIndex" , err )
}
return
}
2022-11-19 16:12:33 +08:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2022-09-29 10:27:20 +08:00
ctx . InternalServerError ( err )
return
}
2022-11-19 16:12:33 +08:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2022-09-29 10:27:20 +08: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 22:22:34 +08:00
maxLines := setting . Git . MaxGitDiffLines
2022-09-29 10:27:20 +08:00
2023-02-20 22:22:34 +08:00
// FIXME: If there are too many files in the repo, may cause some unpredictable issues.
2023-10-03 18:30:41 +08:00
diff , err := gitdiff . GetDiff ( ctx , baseGitRepo ,
2022-09-29 10:27:20 +08:00
& gitdiff . DiffOptions {
BeforeCommitID : startCommitID ,
AfterCommitID : endCommitID ,
SkipTo : ctx . FormString ( "skip-to" ) ,
MaxLines : maxLines ,
MaxLineCharacters : setting . Git . MaxGitDiffLineCharacters ,
2023-02-20 22:22:34 +08:00
MaxFiles : - 1 , // GetDiff() will return all files
2022-09-29 10:27:20 +08: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-15 10:19:25 +08:00
start , limit := listOptions . GetSkipTake ( )
2022-09-29 10:27:20 +08:00
2024-01-15 10:19:25 +08:00
limit = min ( limit , totalNumberOfFiles - start )
2022-09-29 10:27:20 +08:00
2024-01-15 10:19:25 +08:00
limit = max ( limit , 0 )
2023-02-20 22:22:34 +08:00
2024-01-15 10:19:25 +08:00
apiFiles := make ( [ ] * api . ChangedFile , 0 , limit )
for i := start ; i < start + limit ; i ++ {
2022-09-29 10:27:20 +08: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 )
}