forgejo/routers/api/v1/repo/repo.go
zeripath 4979f15c3f
Add configurable Trust Models (#11712)
* Add configurable Trust Models

Gitea's default signature verification model differs from GitHub. GitHub
uses signatures to verify that the committer is who they say they are -
meaning that when GitHub makes a signed commit it must be the committer.
The GitHub model prevents re-publishing of commits after revocation of a
key and prevents re-signing of other people's commits to create a
completely trusted repository signed by one key or a set of trusted
keys.

The default behaviour of Gitea in contrast is to always display the
avatar and information related to a signature. This allows signatures to
be decoupled from the committer. That being said, allowing arbitary
users to present other peoples commits as theirs is not necessarily
desired therefore we have a trust model whereby signatures from
collaborators are marked trusted, signatures matching the commit line
are marked untrusted and signatures that match a user in the db but not
the committer line are marked unmatched.

The problem with this model is that this conflicts with Github therefore
we need to provide an option to allow users to choose the Github model
should they wish to.

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Adjust locale strings

Signed-off-by: Andrew Thornton <art27@cantab.net>

* as per @6543

Co-authored-by: 6543 <6543@obermui.de>

* Update models/gpg_key.go

* Add migration for repository

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-09-20 00:44:55 +08:00

846 lines
26 KiB
Go

// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/routers/api/v1/utils"
repo_service "code.gitea.io/gitea/services/repository"
)
var searchOrderByMap = map[string]map[string]models.SearchOrderBy{
"asc": {
"alpha": models.SearchOrderByAlphabetically,
"created": models.SearchOrderByOldest,
"updated": models.SearchOrderByLeastUpdated,
"size": models.SearchOrderBySize,
"id": models.SearchOrderByID,
},
"desc": {
"alpha": models.SearchOrderByAlphabeticallyReverse,
"created": models.SearchOrderByNewest,
"updated": models.SearchOrderByRecentUpdated,
"size": models.SearchOrderBySizeReverse,
"id": models.SearchOrderByIDReverse,
},
}
// Search repositories via options
func Search(ctx *context.APIContext) {
// swagger:operation GET /repos/search repository repoSearch
// ---
// summary: Search for repositories
// produces:
// - application/json
// parameters:
// - name: q
// in: query
// description: keyword
// type: string
// - name: topic
// in: query
// description: Limit search to repositories with keyword as topic
// type: boolean
// - name: includeDesc
// in: query
// description: include search of keyword within repository description
// type: boolean
// - name: uid
// in: query
// description: search only for repos that the user with the given id owns or contributes to
// type: integer
// format: int64
// - name: priority_owner_id
// in: query
// description: repo owner to prioritize in the results
// type: integer
// format: int64
// - name: starredBy
// in: query
// description: search only for repos that the user with the given id has starred
// type: integer
// format: int64
// - name: private
// in: query
// description: include private repositories this user has access to (defaults to true)
// type: boolean
// - name: is_private
// in: query
// description: show only pubic, private or all repositories (defaults to all)
// type: boolean
// - name: template
// in: query
// description: include template repositories this user has access to (defaults to true)
// type: boolean
// - name: archived
// in: query
// description: show only archived, non-archived or all repositories (defaults to all)
// type: boolean
// - name: mode
// in: query
// description: type of repository to search for. Supported values are
// "fork", "source", "mirror" and "collaborative"
// type: string
// - name: exclusive
// in: query
// description: if `uid` is given, search only for repos that the user owns
// type: boolean
// - name: sort
// in: query
// description: sort repos by attribute. Supported values are
// "alpha", "created", "updated", "size", and "id".
// Default is "alpha"
// type: string
// - name: order
// in: query
// description: sort order, either "asc" (ascending) or "desc" (descending).
// Default is "asc", ignored if "sort" is not specified.
// type: string
// - 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/SearchResults"
// "422":
// "$ref": "#/responses/validationError"
opts := &models.SearchRepoOptions{
ListOptions: utils.GetListOptions(ctx),
Actor: ctx.User,
Keyword: strings.Trim(ctx.Query("q"), " "),
OwnerID: ctx.QueryInt64("uid"),
PriorityOwnerID: ctx.QueryInt64("priority_owner_id"),
TopicOnly: ctx.QueryBool("topic"),
Collaborate: util.OptionalBoolNone,
Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")),
Template: util.OptionalBoolNone,
StarredByID: ctx.QueryInt64("starredBy"),
IncludeDescription: ctx.QueryBool("includeDesc"),
}
if ctx.Query("template") != "" {
opts.Template = util.OptionalBoolOf(ctx.QueryBool("template"))
}
if ctx.QueryBool("exclusive") {
opts.Collaborate = util.OptionalBoolFalse
}
var mode = ctx.Query("mode")
switch mode {
case "source":
opts.Fork = util.OptionalBoolFalse
opts.Mirror = util.OptionalBoolFalse
case "fork":
opts.Fork = util.OptionalBoolTrue
case "mirror":
opts.Mirror = util.OptionalBoolTrue
case "collaborative":
opts.Mirror = util.OptionalBoolFalse
opts.Collaborate = util.OptionalBoolTrue
case "":
default:
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
return
}
if ctx.Query("archived") != "" {
opts.Archived = util.OptionalBoolOf(ctx.QueryBool("archived"))
}
if ctx.Query("is_private") != "" {
opts.IsPrivate = util.OptionalBoolOf(ctx.QueryBool("is_private"))
}
var sortMode = ctx.Query("sort")
if len(sortMode) > 0 {
var sortOrder = ctx.Query("order")
if len(sortOrder) == 0 {
sortOrder = "asc"
}
if searchModeMap, ok := searchOrderByMap[sortOrder]; ok {
if orderBy, ok := searchModeMap[sortMode]; ok {
opts.OrderBy = orderBy
} else {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
return
}
} else {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
return
}
}
var err error
repos, count, err := models.SearchRepository(opts)
if err != nil {
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false,
Error: err.Error(),
})
return
}
results := make([]*api.Repository, len(repos))
for i, repo := range repos {
if err = repo.GetOwner(); err != nil {
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false,
Error: err.Error(),
})
return
}
accessMode, err := models.AccessLevel(ctx.User, repo)
if err != nil {
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false,
Error: err.Error(),
})
}
results[i] = repo.APIFormat(accessMode)
}
ctx.SetLinkHeader(int(count), opts.PageSize)
ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
ctx.JSON(http.StatusOK, api.SearchResults{
OK: true,
Data: results,
})
}
// CreateUserRepo create a repository for a user
func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateRepoOption) {
if opt.AutoInit && opt.Readme == "" {
opt.Readme = "Default"
}
repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{
Name: opt.Name,
Description: opt.Description,
IssueLabels: opt.IssueLabels,
Gitignores: opt.Gitignores,
License: opt.License,
Readme: opt.Readme,
IsPrivate: opt.Private,
AutoInit: opt.AutoInit,
DefaultBranch: opt.DefaultBranch,
TrustModel: models.ToTrustModel(opt.TrustModel),
})
if err != nil {
if models.IsErrRepoAlreadyExist(err) {
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
} else if models.IsErrNameReserved(err) ||
models.IsErrNamePatternNotAllowed(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
}
return
}
// reload repo from db to get a real state after creation
repo, err = models.GetRepositoryByID(repo.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
}
ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeOwner))
}
// Create one repository of mine
func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
// swagger:operation POST /user/repos repository user createCurrentUserRepo
// ---
// summary: Create a repository
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateRepoOption"
// responses:
// "201":
// "$ref": "#/responses/Repository"
// "409":
// description: The repository with the same name already exists.
// "422":
// "$ref": "#/responses/validationError"
if ctx.User.IsOrganization() {
// Shouldn't reach this condition, but just in case.
ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
return
}
CreateUserRepo(ctx, ctx.User, opt)
}
// CreateOrgRepoDeprecated create one repository of the organization
func CreateOrgRepoDeprecated(ctx *context.APIContext, opt api.CreateRepoOption) {
// swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
// ---
// summary: Create a repository in an organization
// deprecated: true
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateRepoOption"
// responses:
// "201":
// "$ref": "#/responses/Repository"
// "422":
// "$ref": "#/responses/validationError"
// "403":
// "$ref": "#/responses/forbidden"
CreateOrgRepo(ctx, opt)
}
// CreateOrgRepo create one repository of the organization
func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
// swagger:operation POST /orgs/{org}/repos organization createOrgRepo
// ---
// summary: Create a repository in an organization
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateRepoOption"
// responses:
// "201":
// "$ref": "#/responses/Repository"
// "404":
// "$ref": "#/responses/notFound"
// "403":
// "$ref": "#/responses/forbidden"
org, err := models.GetOrgByName(ctx.Params(":org"))
if err != nil {
if models.IsErrOrgNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
}
return
}
if !models.HasOrgVisible(org, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
return
}
if !ctx.User.IsAdmin {
canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
if err != nil {
ctx.ServerError("CanCreateOrgRepo", err)
return
} else if !canCreate {
ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
return
}
}
CreateUserRepo(ctx, org, opt)
}
// Get one repository
func Get(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo} repository repoGet
// ---
// summary: Get 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/Repository"
ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
}
// GetByID returns a single Repository
func GetByID(ctx *context.APIContext) {
// swagger:operation GET /repositories/{id} repository repoGetByID
// ---
// summary: Get a repository by id
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of the repo to get
// type: integer
// format: int64
// required: true
// responses:
// "200":
// "$ref": "#/responses/Repository"
repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrRepoNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
}
return
}
perm, err := models.GetUserRepoPermission(repo, ctx.User)
if err != nil {
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
return
} else if !perm.HasAccess() {
ctx.NotFound()
return
}
ctx.JSON(http.StatusOK, repo.APIFormat(perm.AccessMode))
}
// Edit edit repository properties
func Edit(ctx *context.APIContext, opts api.EditRepoOption) {
// swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
// ---
// summary: Edit a repository's properties. Only fields that are set will be changed.
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo to edit
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo to edit
// type: string
// required: true
// required: true
// - name: body
// in: body
// description: "Properties of a repo that you can edit"
// schema:
// "$ref": "#/definitions/EditRepoOption"
// responses:
// "200":
// "$ref": "#/responses/Repository"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
if err := updateBasicProperties(ctx, opts); err != nil {
return
}
if err := updateRepoUnits(ctx, opts); err != nil {
return
}
if opts.Archived != nil {
if err := updateRepoArchivedState(ctx, opts); err != nil {
return
}
}
ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
}
// updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error {
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
newRepoName := repo.Name
if opts.Name != nil {
newRepoName = *opts.Name
}
// Check if repository name has been changed and not just a case change
if repo.LowerName != strings.ToLower(newRepoName) {
if err := repo_service.ChangeRepositoryName(ctx.User, repo, newRepoName); err != nil {
switch {
case models.IsErrRepoAlreadyExist(err):
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
case models.IsErrNameReserved(err):
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
case models.IsErrNamePatternNotAllowed(err):
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(models.ErrNamePatternNotAllowed).Pattern), err)
default:
ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
}
return err
}
log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
}
// Update the name in the repo object for the response
repo.Name = newRepoName
repo.LowerName = strings.ToLower(newRepoName)
if opts.Description != nil {
repo.Description = *opts.Description
}
if opts.Website != nil {
repo.Website = *opts.Website
}
visibilityChanged := false
if opts.Private != nil {
// Visibility of forked repository is forced sync with base repository.
if repo.IsFork {
*opts.Private = repo.BaseRepo.IsPrivate
}
visibilityChanged = repo.IsPrivate != *opts.Private
// when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.User.IsAdmin {
err := fmt.Errorf("cannot change private repository to public")
ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
return err
}
repo.IsPrivate = *opts.Private
}
if opts.Template != nil {
repo.IsTemplate = *opts.Template
}
// Default branch only updated if changed and exist
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch) {
if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
return err
}
}
repo.DefaultBranch = *opts.DefaultBranch
}
if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
return err
}
log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
return nil
}
// updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings
func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
var units []models.RepoUnit
var deleteUnitTypes []models.UnitType
if opts.HasIssues != nil {
if *opts.HasIssues && opts.ExternalTracker != nil && !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
// Check that values are valid
if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
err := fmt.Errorf("External tracker URL not valid")
ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
return err
}
if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
err := fmt.Errorf("External tracker URL format not valid")
ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
return err
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeExternalTracker,
Config: &models.ExternalTrackerConfig{
ExternalTrackerURL: opts.ExternalTracker.ExternalTrackerURL,
ExternalTrackerFormat: opts.ExternalTracker.ExternalTrackerFormat,
ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
},
})
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
} else if *opts.HasIssues && opts.ExternalTracker == nil && !models.UnitTypeIssues.UnitGlobalDisabled() {
// Default to built-in tracker
var config *models.IssuesConfig
if opts.InternalTracker != nil {
config = &models.IssuesConfig{
EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
}
} else if unit, err := repo.GetUnit(models.UnitTypeIssues); err != nil {
// Unit type doesn't exist so we make a new config file with default values
config = &models.IssuesConfig{
EnableTimetracker: true,
AllowOnlyContributorsToTrackTime: true,
EnableDependencies: true,
}
} else {
config = unit.IssuesConfig()
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeIssues,
Config: config,
})
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
} else if !*opts.HasIssues {
if !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
}
if !models.UnitTypeIssues.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
}
}
}
if opts.HasWiki != nil {
if *opts.HasWiki && opts.ExternalWiki != nil && !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
// Check that values are valid
if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
err := fmt.Errorf("External wiki URL not valid")
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
return err
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeExternalWiki,
Config: &models.ExternalWikiConfig{
ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
},
})
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
} else if *opts.HasWiki && opts.ExternalWiki == nil && !models.UnitTypeWiki.UnitGlobalDisabled() {
config := &models.UnitConfig{}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeWiki,
Config: config,
})
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
} else if !*opts.HasWiki {
if !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
}
if !models.UnitTypeWiki.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
}
}
}
if opts.HasPullRequests != nil {
if *opts.HasPullRequests && !models.UnitTypePullRequests.UnitGlobalDisabled() {
// We do allow setting individual PR settings through the API, so
// we get the config settings and then set them
// if those settings were provided in the opts.
unit, err := repo.GetUnit(models.UnitTypePullRequests)
var config *models.PullRequestsConfig
if err != nil {
// Unit type doesn't exist so we make a new config file with default values
config = &models.PullRequestsConfig{
IgnoreWhitespaceConflicts: false,
AllowMerge: true,
AllowRebase: true,
AllowRebaseMerge: true,
AllowSquash: true,
}
} else {
config = unit.PullRequestsConfig()
}
if opts.IgnoreWhitespaceConflicts != nil {
config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
}
if opts.AllowMerge != nil {
config.AllowMerge = *opts.AllowMerge
}
if opts.AllowRebase != nil {
config.AllowRebase = *opts.AllowRebase
}
if opts.AllowRebaseMerge != nil {
config.AllowRebaseMerge = *opts.AllowRebaseMerge
}
if opts.AllowSquash != nil {
config.AllowSquash = *opts.AllowSquash
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypePullRequests,
Config: config,
})
} else if !*opts.HasPullRequests && !models.UnitTypePullRequests.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypePullRequests)
}
}
if opts.HasProjects != nil && !models.UnitTypeProjects.UnitGlobalDisabled() {
if *opts.HasProjects {
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeProjects,
})
} else {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeProjects)
}
}
if err := models.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err
}
log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name)
return nil
}
// updateRepoArchivedState updates repo's archive state
func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error {
repo := ctx.Repo.Repository
// archive / un-archive
if opts.Archived != nil {
if repo.IsMirror {
err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
return err
}
if *opts.Archived {
if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
log.Error("Tried to archive a repo: %s", err)
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
return err
}
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
} else {
if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
log.Error("Tried to un-archive a repo: %s", err)
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
return err
}
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
}
}
return nil
}
// Delete one repository
func Delete(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
// ---
// summary: Delete a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo to delete
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo to delete
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
canDelete, err := repo.CanUserDelete(ctx.User)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
return
} else if !canDelete {
ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
return
}
if err := repo_service.DeleteRepository(ctx.User, repo); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
return
}
log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
ctx.Status(http.StatusNoContent)
}
// GetIssueTemplates returns the issue templates for a repository
func GetIssueTemplates(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issue_templates repository repoGetIssueTemplates
// ---
// summary: Get available issue templates 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/IssueTemplates"
ctx.JSON(http.StatusOK, ctx.IssueTemplatesFromDefaultBranch())
}