| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198 |
- // Copyright 2016 The Gogs Authors. All rights reserved.
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package repo
-
- import (
- "errors"
- "net/http"
-
- "code.gitea.io/gitea/models/db"
- git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/models/organization"
- repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/gitrepo"
- "code.gitea.io/gitea/modules/optional"
- repo_module "code.gitea.io/gitea/modules/repository"
- api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/routers/api/v1/utils"
- "code.gitea.io/gitea/services/context"
- "code.gitea.io/gitea/services/convert"
- pull_service "code.gitea.io/gitea/services/pull"
- release_service "code.gitea.io/gitea/services/release"
- repo_service "code.gitea.io/gitea/services/repository"
- )
-
- // GetBranch get a branch of a repository
- func GetBranch(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch
- // ---
- // summary: Retrieve a specific branch from a repository, including its effective branch protection
- // 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: branch
- // in: path
- // description: branch to get
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/Branch"
- // "404":
- // "$ref": "#/responses/notFound"
-
- branchName := ctx.PathParam("*")
-
- exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- } else if !exist {
- ctx.APIErrorNotFound(err)
- return
- }
-
- c, err := ctx.Repo.GitRepo.GetBranchCommit(branchName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branchName, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- ctx.JSON(http.StatusOK, br)
- }
-
- // DeleteBranch get a branch of a repository
- func DeleteBranch(ctx *context.APIContext) {
- // swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch
- // ---
- // summary: Delete a specific branch from a repository
- // 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: branch
- // in: path
- // description: branch to delete
- // type: string
- // required: true
- // responses:
- // "204":
- // "$ref": "#/responses/empty"
- // "403":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
- // "423":
- // "$ref": "#/responses/repoArchivedError"
- if ctx.Repo.Repository.IsEmpty {
- ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
- return
- }
-
- if ctx.Repo.Repository.IsMirror {
- ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
- return
- }
-
- branchName := ctx.PathParam("*")
-
- // check whether branches of this repository has been synced
- totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
- RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: optional.Some(false),
- })
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
- _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- }
-
- if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil {
- switch {
- case git.IsErrBranchNotExist(err):
- ctx.APIErrorNotFound(err)
- case errors.Is(err, repo_service.ErrBranchIsDefault):
- ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
- case errors.Is(err, git_model.ErrBranchIsProtected):
- ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
- default:
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- // CreateBranch creates a branch for a user's repository
- func CreateBranch(ctx *context.APIContext) {
- // swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
- // ---
- // summary: Create a branch
- // 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/CreateBranchRepoOption"
- // responses:
- // "201":
- // "$ref": "#/responses/Branch"
- // "403":
- // description: The branch is archived or a mirror.
- // "404":
- // description: The old branch does not exist.
- // "409":
- // description: The branch with the same name already exists.
- // "423":
- // "$ref": "#/responses/repoArchivedError"
-
- if ctx.Repo.Repository.IsEmpty {
- ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
- return
- }
-
- if ctx.Repo.Repository.IsMirror {
- ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
- return
- }
-
- opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
-
- var oldCommit *git.Commit
- var err error
-
- if len(opt.OldRefName) > 0 {
- oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- } else if len(opt.OldBranchName) > 0 { //nolint:staticcheck // deprecated field
- if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, opt.OldBranchName) { //nolint:staticcheck // deprecated field
- oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint:staticcheck // deprecated field
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- ctx.APIError(http.StatusNotFound, "The old branch does not exist")
- return
- }
- } else {
- oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- }
-
- err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
- if err != nil {
- if git_model.IsErrBranchNotExist(err) {
- ctx.APIError(http.StatusNotFound, "The old branch does not exist")
- } else if release_service.IsErrTagAlreadyExists(err) {
- ctx.APIError(http.StatusConflict, "The branch with the same tag already exists.")
- } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
- ctx.APIError(http.StatusConflict, "The branch already exists.")
- } else if git_model.IsErrBranchNameConflict(err) {
- ctx.APIError(http.StatusConflict, "The branch with the same name already exists.")
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- commit, err := ctx.Repo.GitRepo.GetBranchCommit(opt.BranchName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, opt.BranchName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- br, err := convert.ToBranch(ctx, ctx.Repo.Repository, opt.BranchName, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- ctx.JSON(http.StatusCreated, br)
- }
-
- // ListBranches list all the branches of a repository
- func ListBranches(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
- // ---
- // summary: List a repository's branches
- // 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: 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/BranchList"
-
- var totalNumOfBranches int64
- var apiBranches []*api.Branch
-
- listOptions := utils.GetListOptions(ctx)
-
- if !ctx.Repo.Repository.IsEmpty {
- if ctx.Repo.GitRepo == nil {
- ctx.APIErrorInternal(nil)
- return
- }
-
- branchOpts := git_model.FindBranchOptions{
- ListOptions: listOptions,
- RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: optional.Some(false),
- }
- var err error
- totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
- totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- }
-
- rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- branches, err := db.Find[git_model.Branch](ctx, branchOpts)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- apiBranches = make([]*api.Branch, 0, len(branches))
- for i := range branches {
- c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
- if err != nil {
- // Skip if this branch doesn't exist anymore.
- if git.IsErrNotExist(err) {
- totalNumOfBranches--
- continue
- }
- ctx.APIErrorInternal(err)
- return
- }
-
- branchProtection := rules.GetFirstMatched(branches[i].Name)
- apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- apiBranches = append(apiBranches, apiBranch)
- }
- }
-
- ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
- ctx.SetTotalCountHeader(totalNumOfBranches)
- ctx.JSON(http.StatusOK, apiBranches)
- }
-
- // RenameBranch renames a repository's branch.
- func RenameBranch(ctx *context.APIContext) {
- // swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoRenameBranch
- // ---
- // summary: Rename a branch
- // 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: branch
- // in: path
- // description: name of the branch
- // type: string
- // required: true
- // - name: body
- // in: body
- // schema:
- // "$ref": "#/definitions/RenameBranchRepoOption"
- // responses:
- // "204":
- // "$ref": "#/responses/empty"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
-
- opt := web.GetForm(ctx).(*api.RenameBranchRepoOption)
-
- oldName := ctx.PathParam("*")
- repo := ctx.Repo.Repository
-
- if repo.IsEmpty {
- ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
- return
- }
-
- if repo.IsMirror {
- ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
- return
- }
-
- msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
- if err != nil {
- switch {
- case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
- ctx.APIError(http.StatusForbidden, "User must be a repo or site admin to rename default or protected branches.")
- case errors.Is(err, git_model.ErrBranchIsProtected):
- ctx.APIError(http.StatusForbidden, "Branch is protected by glob-based protection rules.")
- default:
- ctx.APIErrorInternal(err)
- }
- return
- }
- if msg == "target_exist" {
- ctx.APIError(http.StatusUnprocessableEntity, "Cannot rename a branch using the same name or rename to a branch that already exists.")
- return
- }
- if msg == "from_not_exist" {
- ctx.APIError(http.StatusNotFound, "Branch doesn't exist.")
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- // GetBranchProtection gets a branch protection
- func GetBranchProtection(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
- // ---
- // summary: Get a specific branch protection for the repository
- // 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: name
- // in: path
- // description: name of protected branch
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/BranchProtection"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repo := ctx.Repo.Repository
- bpName := ctx.PathParam("name")
- bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if bp == nil || bp.RepoID != repo.ID {
- ctx.APIErrorNotFound()
- return
- }
-
- ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
- }
-
- // ListBranchProtections list branch protections for a repo
- func ListBranchProtections(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
- // ---
- // summary: List branch protections for a repository
- // 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
- // responses:
- // "200":
- // "$ref": "#/responses/BranchProtectionList"
-
- repo := ctx.Repo.Repository
- bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- apiBps := make([]*api.BranchProtection, len(bps))
- for i := range bps {
- apiBps[i] = convert.ToBranchProtection(ctx, bps[i], repo)
- }
-
- ctx.JSON(http.StatusOK, apiBps)
- }
-
- // CreateBranchProtection creates a branch protection for a repo
- func CreateBranchProtection(ctx *context.APIContext) {
- // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
- // ---
- // summary: Create a branch protections for a repository
- // 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/CreateBranchProtectionOption"
- // responses:
- // "201":
- // "$ref": "#/responses/BranchProtection"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
- // "423":
- // "$ref": "#/responses/repoArchivedError"
-
- form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
- repo := ctx.Repo.Repository
-
- ruleName := form.RuleName
- if ruleName == "" {
- ruleName = form.BranchName //nolint:staticcheck // deprecated field
- }
- if len(ruleName) == 0 {
- ctx.APIError(http.StatusBadRequest, "both rule_name and branch_name are empty")
- return
- }
-
- protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- } else if protectBranch != nil {
- ctx.APIError(http.StatusForbidden, "Branch protection already exist")
- return
- }
-
- var requiredApprovals int64
- if form.RequiredApprovals > 0 {
- requiredApprovals = form.RequiredApprovals
- }
-
- whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- forcePushAllowlistUsers, err := user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
- if repo.Owner.IsOrganization() {
- whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- }
-
- protectBranch = &git_model.ProtectedBranch{
- RepoID: ctx.Repo.Repository.ID,
- RuleName: ruleName,
- Priority: form.Priority,
- CanPush: form.EnablePush,
- EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
- WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
- CanForcePush: form.EnablePush && form.EnableForcePush,
- EnableForcePushAllowlist: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist,
- ForcePushAllowlistDeployKeys: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist && form.ForcePushAllowlistDeployKeys,
- EnableMergeWhitelist: form.EnableMergeWhitelist,
- EnableStatusCheck: form.EnableStatusCheck,
- StatusCheckContexts: form.StatusCheckContexts,
- EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
- RequiredApprovals: requiredApprovals,
- BlockOnRejectedReviews: form.BlockOnRejectedReviews,
- BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
- DismissStaleApprovals: form.DismissStaleApprovals,
- IgnoreStaleApprovals: form.IgnoreStaleApprovals,
- RequireSignedCommits: form.RequireSignedCommits,
- ProtectedFilePatterns: form.ProtectedFilePatterns,
- UnprotectedFilePatterns: form.UnprotectedFilePatterns,
- BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
- BlockAdminMergeOverride: form.BlockAdminMergeOverride,
- }
-
- if err := pull_service.CreateOrUpdateProtectedBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
- UserIDs: whitelistUsers,
- TeamIDs: whitelistTeams,
- ForcePushUserIDs: forcePushAllowlistUsers,
- ForcePushTeamIDs: forcePushAllowlistTeams,
- MergeUserIDs: mergeWhitelistUsers,
- MergeTeamIDs: mergeWhitelistTeams,
- ApprovalsUserIDs: approvalsWhitelistUsers,
- ApprovalsTeamIDs: approvalsWhitelistTeams,
- }); err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- // Reload from db to get all whitelists
- bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, ruleName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
- ctx.APIErrorInternal(err)
- return
- }
-
- ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp, repo))
- }
-
- // EditBranchProtection edits a branch protection for a repo
- func EditBranchProtection(ctx *context.APIContext) {
- // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
- // ---
- // summary: Edit a branch protections for a repository. Only fields that are set will be changed
- // 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: name
- // in: path
- // description: name of protected branch
- // type: string
- // required: true
- // - name: body
- // in: body
- // schema:
- // "$ref": "#/definitions/EditBranchProtectionOption"
- // responses:
- // "200":
- // "$ref": "#/responses/BranchProtection"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
- // "423":
- // "$ref": "#/responses/repoArchivedError"
- form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
- repo := ctx.Repo.Repository
- bpName := ctx.PathParam("name")
- protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if protectBranch == nil || protectBranch.RepoID != repo.ID {
- ctx.APIErrorNotFound()
- return
- }
-
- if form.EnablePush != nil {
- if !*form.EnablePush {
- protectBranch.CanPush = false
- protectBranch.EnableWhitelist = false
- protectBranch.WhitelistDeployKeys = false
- } else {
- protectBranch.CanPush = true
- if form.EnablePushWhitelist != nil {
- if !*form.EnablePushWhitelist {
- protectBranch.EnableWhitelist = false
- protectBranch.WhitelistDeployKeys = false
- } else {
- protectBranch.EnableWhitelist = true
- if form.PushWhitelistDeployKeys != nil {
- protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
- }
- }
- }
- }
- }
-
- if form.EnableForcePush != nil {
- if !*form.EnableForcePush {
- protectBranch.CanForcePush = false
- protectBranch.EnableForcePushAllowlist = false
- protectBranch.ForcePushAllowlistDeployKeys = false
- } else {
- protectBranch.CanForcePush = true
- if form.EnableForcePushAllowlist != nil {
- if !*form.EnableForcePushAllowlist {
- protectBranch.EnableForcePushAllowlist = false
- protectBranch.ForcePushAllowlistDeployKeys = false
- } else {
- protectBranch.EnableForcePushAllowlist = true
- if form.ForcePushAllowlistDeployKeys != nil {
- protectBranch.ForcePushAllowlistDeployKeys = *form.ForcePushAllowlistDeployKeys
- }
- }
- }
- }
- }
-
- if form.Priority != nil {
- protectBranch.Priority = *form.Priority
- }
-
- if form.EnableMergeWhitelist != nil {
- protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
- }
-
- if form.EnableStatusCheck != nil {
- protectBranch.EnableStatusCheck = *form.EnableStatusCheck
- }
-
- if form.StatusCheckContexts != nil {
- protectBranch.StatusCheckContexts = form.StatusCheckContexts
- }
-
- if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
- protectBranch.RequiredApprovals = *form.RequiredApprovals
- }
-
- if form.EnableApprovalsWhitelist != nil {
- protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
- }
-
- if form.BlockOnRejectedReviews != nil {
- protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
- }
-
- if form.BlockOnOfficialReviewRequests != nil {
- protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
- }
-
- if form.DismissStaleApprovals != nil {
- protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
- }
-
- if form.IgnoreStaleApprovals != nil {
- protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
- }
-
- if form.RequireSignedCommits != nil {
- protectBranch.RequireSignedCommits = *form.RequireSignedCommits
- }
-
- if form.ProtectedFilePatterns != nil {
- protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns
- }
-
- if form.UnprotectedFilePatterns != nil {
- protectBranch.UnprotectedFilePatterns = *form.UnprotectedFilePatterns
- }
-
- if form.BlockOnOutdatedBranch != nil {
- protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
- }
-
- if form.BlockAdminMergeOverride != nil {
- protectBranch.BlockAdminMergeOverride = *form.BlockAdminMergeOverride
- }
-
- var whitelistUsers, forcePushAllowlistUsers, mergeWhitelistUsers, approvalsWhitelistUsers []int64
- if form.PushWhitelistUsernames != nil {
- whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- whitelistUsers = protectBranch.WhitelistUserIDs
- }
- if form.ForcePushAllowlistDeployKeys != nil {
- forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- forcePushAllowlistUsers = protectBranch.ForcePushAllowlistUserIDs
- }
- if form.MergeWhitelistUsernames != nil {
- mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
- }
- if form.ApprovalsWhitelistUsernames != nil {
- approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
- }
-
- var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
- if repo.Owner.IsOrganization() {
- if form.PushWhitelistTeams != nil {
- whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- whitelistTeams = protectBranch.WhitelistTeamIDs
- }
- if form.ForcePushAllowlistTeams != nil {
- forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- forcePushAllowlistTeams = protectBranch.ForcePushAllowlistTeamIDs
- }
- if form.MergeWhitelistTeams != nil {
- mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
- }
- if form.ApprovalsWhitelistTeams != nil {
- approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
- if err != nil {
- if organization.IsErrTeamNotExist(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
- }
- }
-
- err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
- UserIDs: whitelistUsers,
- TeamIDs: whitelistTeams,
- ForcePushUserIDs: forcePushAllowlistUsers,
- ForcePushTeamIDs: forcePushAllowlistTeams,
- MergeUserIDs: mergeWhitelistUsers,
- MergeTeamIDs: mergeWhitelistTeams,
- ApprovalsUserIDs: approvalsWhitelistUsers,
- ApprovalsTeamIDs: approvalsWhitelistTeams,
- })
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- isPlainRule := !git_model.IsRuleNameSpecial(bpName)
- var isBranchExist bool
- if isPlainRule {
- isBranchExist = gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, bpName)
- }
-
- if isBranchExist {
- if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, bpName); err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- } else {
- if !isPlainRule {
- if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- }
-
- // FIXME: since we only need to recheck files protected rules, we could improve this
- matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- for _, branchName := range matchedBranches {
- if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- }
- }
- }
-
- // Reload from db to ensure get all whitelists
- bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
- ctx.APIErrorInternal(err)
- return
- }
-
- ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
- }
-
- // DeleteBranchProtection deletes a branch protection for a repo
- func DeleteBranchProtection(ctx *context.APIContext) {
- // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
- // ---
- // summary: Delete a specific branch protection for the repository
- // 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: name
- // in: path
- // description: name of protected branch
- // type: string
- // required: true
- // responses:
- // "204":
- // "$ref": "#/responses/empty"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repo := ctx.Repo.Repository
- bpName := ctx.PathParam("name")
- bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if bp == nil || bp.RepoID != repo.ID {
- ctx.APIErrorNotFound()
- return
- }
-
- if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, bp.ID); err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- // UpdateBranchProtectionPriories updates the priorities of branch protections for a repo
- func UpdateBranchProtectionPriories(ctx *context.APIContext) {
- // swagger:operation POST /repos/{owner}/{repo}/branch_protections/priority repository repoUpdateBranchProtectionPriories
- // ---
- // summary: Update the priorities of branch protections for a repository.
- // 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/UpdateBranchProtectionPriories"
- // responses:
- // "204":
- // "$ref": "#/responses/empty"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
- // "423":
- // "$ref": "#/responses/repoArchivedError"
- form := web.GetForm(ctx).(*api.UpdateBranchProtectionPriories)
- repo := ctx.Repo.Repository
-
- if err := git_model.UpdateProtectBranchPriorities(ctx, repo, form.IDs); err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- func MergeUpstream(ctx *context.APIContext) {
- // swagger:operation POST /repos/{owner}/{repo}/merge-upstream repository repoMergeUpstream
- // ---
- // summary: Merge a branch from upstream
- // 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/MergeUpstreamRequest"
- // responses:
- // "200":
- // "$ref": "#/responses/MergeUpstreamResponse"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
- form := web.GetForm(ctx).(*api.MergeUpstreamRequest)
- mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch, form.FfOnly)
- if err != nil {
- if errors.Is(err, util.ErrInvalidArgument) {
- ctx.APIError(http.StatusBadRequest, err)
- return
- } else if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- return
- }
- ctx.APIErrorInternal(err)
- return
- }
- ctx.JSON(http.StatusOK, &api.MergeUpstreamResponse{MergeStyle: mergeStyle})
- }
|