2014-08-26 06:11:15 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2018-11-28 06:26:14 -05:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2014-08-26 06:11:15 -04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2015-12-04 17:16:42 -05:00
package repo
2014-08-26 06:11:15 -04:00
import (
2019-11-08 17:21:00 -05:00
"bytes"
"errors"
2017-08-16 21:31:34 -04:00
"fmt"
2017-10-26 17:16:13 -04:00
"net/http"
2019-10-14 02:10:42 -04:00
"net/url"
2017-02-10 23:00:01 -05:00
"strings"
2014-08-26 06:11:15 -04:00
2016-11-10 11:24:48 -05:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
2019-11-17 01:30:01 -05:00
"code.gitea.io/gitea/modules/git"
2019-12-16 23:16:54 -05:00
"code.gitea.io/gitea/modules/graceful"
2016-11-10 11:24:48 -05:00
"code.gitea.io/gitea/modules/log"
2019-05-06 21:12:51 -04:00
"code.gitea.io/gitea/modules/migrations"
2019-07-12 04:55:46 -04:00
"code.gitea.io/gitea/modules/notification"
2020-01-12 07:11:17 -05:00
repo_module "code.gitea.io/gitea/modules/repository"
2016-11-10 11:24:48 -05:00
"code.gitea.io/gitea/modules/setting"
2019-08-23 12:40:30 -04:00
api "code.gitea.io/gitea/modules/structs"
2017-10-26 17:16:13 -04:00
"code.gitea.io/gitea/modules/util"
2019-10-02 05:30:41 -04:00
"code.gitea.io/gitea/modules/validation"
2020-01-24 14:00:29 -05:00
"code.gitea.io/gitea/routers/api/v1/utils"
2019-10-01 09:40:17 -04:00
mirror_service "code.gitea.io/gitea/services/mirror"
2019-10-26 02:54:11 -04:00
repo_service "code.gitea.io/gitea/services/repository"
2014-08-26 06:11:15 -04:00
)
2018-08-02 04:10:02 -04:00
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 ,
} ,
}
2016-11-24 02:04:31 -05:00
// Search repositories via options
2016-03-13 18:49:16 -04:00
func Search ( ctx * context . APIContext ) {
2017-11-13 02:02:25 -05:00
// swagger:operation GET /repos/search repository repoSearch
// ---
// summary: Search for repositories
// produces:
// - application/json
// parameters:
// - name: q
// in: query
// description: keyword
// type: string
2019-08-23 23:17:10 -04:00
// - name: topic
// in: query
// description: Limit search to repositories with keyword as topic
// type: boolean
2019-08-25 13:06:36 -04:00
// - name: includeDesc
// in: query
// description: include search of keyword within repository description
// type: boolean
2017-11-13 02:02:25 -05:00
// - name: uid
// in: query
2017-11-15 03:10:26 -05:00
// description: search only for repos that the user with the given id owns or contributes to
// type: integer
2018-10-20 23:40:42 -04:00
// format: int64
2019-11-11 10:15:29 -05:00
// - name: priority_owner_id
// in: query
// description: repo owner to prioritize in the results
// type: integer
// format: int64
2019-05-15 11:24:39 -04:00
// - 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
2019-11-11 10:15:29 -05:00
// - name: template
// in: query
// description: include template repositories this user has access to (defaults to true)
// type: boolean
2017-11-13 02:02:25 -05:00
// - 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
2017-11-15 03:10:26 -05:00
// description: if `uid` is given, search only for repos that the user owns
2017-11-13 02:02:25 -05:00
// type: boolean
2018-08-02 04:10:02 -04:00
// - 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
2020-01-24 14:00:29 -05:00
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results, maximum page size is 50
// type: integer
2017-11-13 02:02:25 -05:00
// responses:
// "200":
// "$ref": "#/responses/SearchResults"
// "422":
// "$ref": "#/responses/validationError"
2019-12-20 12:07:12 -05:00
2016-03-11 15:33:12 -05:00
opts := & models . SearchRepoOptions {
2020-01-24 14:00:29 -05:00
ListOptions : utils . GetListOptions ( ctx ) ,
2020-01-13 12:33:46 -05:00
Actor : ctx . User ,
2019-08-25 13:06:36 -04:00
Keyword : strings . Trim ( ctx . Query ( "q" ) , " " ) ,
OwnerID : ctx . QueryInt64 ( "uid" ) ,
2019-11-11 10:15:29 -05:00
PriorityOwnerID : ctx . QueryInt64 ( "priority_owner_id" ) ,
2019-08-25 13:06:36 -04:00
TopicOnly : ctx . QueryBool ( "topic" ) ,
Collaborate : util . OptionalBoolNone ,
Private : ctx . IsSigned && ( ctx . Query ( "private" ) == "" || ctx . QueryBool ( "private" ) ) ,
2019-11-11 10:15:29 -05:00
Template : util . OptionalBoolNone ,
2019-08-25 13:06:36 -04:00
StarredByID : ctx . QueryInt64 ( "starredBy" ) ,
IncludeDescription : ctx . QueryBool ( "includeDesc" ) ,
2017-10-26 17:16:13 -04:00
}
2019-11-11 10:15:29 -05:00
if ctx . Query ( "template" ) != "" {
opts . Template = util . OptionalBoolOf ( ctx . QueryBool ( "template" ) )
}
2017-10-26 17:16:13 -04:00
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
2014-08-26 06:11:15 -04:00
}
2018-08-02 04:10:02 -04:00
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
}
}
2017-10-26 17:16:13 -04:00
var err error
2019-08-25 13:06:36 -04:00
repos , count , err := models . SearchRepository ( opts )
2014-08-26 06:11:15 -04:00
if err != nil {
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusInternalServerError , api . SearchError {
2017-05-02 09:35:59 -04:00
OK : false ,
Error : err . Error ( ) ,
2014-08-26 06:11:15 -04:00
} )
return
}
2014-11-14 17:11:30 -05:00
results := make ( [ ] * api . Repository , len ( repos ) )
2017-02-09 20:30:26 -05:00
for i , repo := range repos {
if err = repo . GetOwner ( ) ; err != nil {
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusInternalServerError , api . SearchError {
2017-05-02 09:35:59 -04:00
OK : false ,
Error : err . Error ( ) ,
2014-08-26 06:11:15 -04:00
} )
return
}
2018-11-28 06:26:14 -05:00
accessMode , err := models . AccessLevel ( ctx . User , repo )
2017-02-09 20:30:26 -05:00
if err != nil {
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusInternalServerError , api . SearchError {
2017-05-02 09:35:59 -04:00
OK : false ,
Error : err . Error ( ) ,
2017-02-09 20:30:26 -05:00
} )
2014-08-26 06:11:15 -04:00
}
2017-02-09 20:30:26 -05:00
results [ i ] = repo . APIFormat ( accessMode )
2014-08-26 06:11:15 -04:00
}
2020-01-24 14:00:29 -05:00
ctx . SetLinkHeader ( int ( count ) , opts . PageSize )
2017-08-16 21:31:34 -04:00
ctx . Header ( ) . Set ( "X-Total-Count" , fmt . Sprintf ( "%d" , count ) )
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusOK , api . SearchResults {
2017-05-02 09:35:59 -04:00
OK : true ,
Data : results ,
2014-08-26 06:11:15 -04:00
} )
}
2014-08-28 23:24:37 -04:00
2016-11-24 02:04:31 -05:00
// CreateUserRepo create a repository for a user
2016-03-13 18:49:16 -04:00
func CreateUserRepo ( ctx * context . APIContext , owner * models . User , opt api . CreateRepoOption ) {
2019-02-21 17:07:58 -05:00
if opt . AutoInit && opt . Readme == "" {
opt . Readme = "Default"
}
2019-10-26 02:54:11 -04:00
repo , err := repo_service . CreateRepository ( ctx . User , owner , models . CreateRepoOptions {
2015-08-28 06:33:09 -04:00
Name : opt . Name ,
Description : opt . Description ,
2019-09-08 04:28:40 -04:00
IssueLabels : opt . IssueLabels ,
2015-08-28 07:06:18 -04:00
Gitignores : opt . Gitignores ,
2015-08-28 06:33:09 -04:00
License : opt . License ,
2015-08-28 07:06:18 -04:00
Readme : opt . Readme ,
IsPrivate : opt . Private ,
AutoInit : opt . AutoInit ,
2015-08-28 06:33:09 -04:00
} )
2014-12-12 20:30:32 -05:00
if err != nil {
2019-03-15 10:19:09 -04:00
if models . IsErrRepoAlreadyExist ( err ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusConflict , "" , "The repository with the same name already exists." )
2019-03-15 10:19:09 -04:00
} else if models . IsErrNameReserved ( err ) ||
2015-03-26 17:11:47 -04:00
models . IsErrNamePatternNotAllowed ( err ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , err )
2014-12-12 20:30:32 -05:00
} else {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "CreateRepository" , err )
2014-12-12 20:30:32 -05:00
}
return
}
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusCreated , repo . APIFormat ( models . AccessModeOwner ) )
2014-12-12 20:30:32 -05:00
}
2016-10-07 13:17:27 -04:00
// Create one repository of mine
2016-03-13 18:49:16 -04:00
func Create ( ctx * context . APIContext , opt api . CreateRepoOption ) {
2017-11-13 02:02:25 -05:00
// 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"
2019-05-30 11:09:05 -04:00
// "409":
// description: The repository with the same name already exists.
// "422":
// "$ref": "#/responses/validationError"
2019-12-20 12:07:12 -05:00
2014-12-12 20:30:32 -05:00
if ctx . User . IsOrganization ( ) {
2017-11-13 02:02:25 -05:00
// Shouldn't reach this condition, but just in case.
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "not allowed creating repository for organization" )
2014-12-12 20:30:32 -05:00
return
}
2015-12-17 22:57:41 -05:00
CreateUserRepo ( ctx , ctx . User , opt )
2014-12-12 20:30:32 -05:00
}
2020-01-09 11:40:01 -05:00
// CreateOrgRepoDeprecated create one repository of the organization
func CreateOrgRepoDeprecated ( ctx * context . APIContext , opt api . CreateRepoOption ) {
// swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
2017-11-13 02:02:25 -05:00
// ---
// summary: Create a repository in an organization
2020-01-09 11:40:01 -05:00
// deprecated: true
2017-11-13 02:02:25 -05:00
// 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"
2019-12-20 12:07:12 -05:00
2020-01-09 11:40:01 -05:00
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"
2014-12-12 20:30:32 -05:00
org , err := models . GetOrgByName ( ctx . Params ( ":org" ) )
if err != nil {
2017-07-06 09:30:19 -04:00
if models . IsErrOrgNotExist ( err ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , err )
2014-12-12 20:30:32 -05:00
} else {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "GetOrgByName" , err )
2014-12-12 20:30:32 -05:00
}
return
}
2019-02-18 11:00:27 -05:00
if ! models . HasOrgVisible ( org , ctx . User ) {
ctx . NotFound ( "HasOrgVisible" , nil )
return
}
2018-07-04 19:51:02 -04:00
if ! ctx . User . IsAdmin {
2019-11-20 06:27:49 -05:00
canCreate , err := org . CanCreateOrgRepo ( ctx . User . ID )
2018-07-04 19:51:02 -04:00
if err != nil {
2019-11-20 06:27:49 -05:00
ctx . ServerError ( "CanCreateOrgRepo" , err )
2018-07-04 19:51:02 -04:00
return
2019-11-20 06:27:49 -05:00
} else if ! canCreate {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusForbidden , "" , "Given user is not allowed to create repository in organization." )
2018-07-04 19:51:02 -04:00
return
}
2014-12-12 20:30:32 -05:00
}
2015-12-17 22:57:41 -05:00
CreateUserRepo ( ctx , org , opt )
2014-12-12 20:30:32 -05:00
}
2016-11-24 02:04:31 -05:00
// Migrate migrate remote git repository to gitea
2016-03-13 18:49:16 -04:00
func Migrate ( ctx * context . APIContext , form auth . MigrateRepoForm ) {
2017-11-13 02:02:25 -05:00
// swagger:operation POST /repos/migrate repository repoMigrate
// ---
// summary: Migrate a remote git repository
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/MigrateRepoForm"
// responses:
// "201":
// "$ref": "#/responses/Repository"
2019-12-20 12:07:12 -05:00
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
2015-09-03 06:17:33 -04:00
ctxUser := ctx . User
2015-11-25 00:55:37 -05:00
// Not equal means context user is an organization,
// or is another user/organization if current user is admin.
2016-11-27 01:03:59 -05:00
if form . UID != ctxUser . ID {
org , err := models . GetUserByID ( form . UID )
2014-08-29 05:31:53 -04:00
if err != nil {
2015-08-04 23:14:17 -04:00
if models . IsErrUserNotExist ( err ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , err )
2015-02-22 09:49:25 -05:00
} else {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "GetUserByID" , err )
2015-02-22 09:49:25 -05:00
}
2014-08-28 23:24:37 -04:00
return
}
ctxUser = org
}
if ctx . HasError ( ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , ctx . GetErrMsg ( ) )
2014-08-28 23:24:37 -04:00
return
}
2018-07-04 18:45:15 -04:00
if ! ctx . User . IsAdmin {
if ! ctxUser . IsOrganization ( ) && ctx . User . ID != ctxUser . ID {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusForbidden , "" , "Given user is not an organization." )
2014-08-28 23:24:37 -04:00
return
}
2018-07-04 18:45:15 -04:00
if ctxUser . IsOrganization ( ) {
// Check ownership of organization.
isOwner , err := ctxUser . IsOwnedBy ( ctx . User . ID )
if err != nil {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "IsOwnedBy" , err )
2018-07-04 18:45:15 -04:00
return
} else if ! isOwner {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusForbidden , "" , "Given user is not owner of organization." )
2018-07-04 18:45:15 -04:00
return
}
}
2014-08-28 23:24:37 -04:00
}
2015-11-03 18:40:52 -05:00
remoteAddr , err := form . ParseRemoteAddr ( ctx . User )
if err != nil {
if models . IsErrInvalidCloneAddr ( err ) {
addrErr := err . ( models . ErrInvalidCloneAddr )
switch {
case addrErr . IsURLError :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , err )
2015-11-03 18:40:52 -05:00
case addrErr . IsPermissionDenied :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "You are not allowed to import local repositories." )
2015-11-03 18:40:52 -05:00
case addrErr . IsInvalidPath :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "Invalid local path, it does not exist or not a directory." )
2015-11-03 18:40:52 -05:00
default :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "ParseRemoteAddr" , "Unknown error type (ErrInvalidCloneAddr): " + err . Error ( ) )
2015-11-03 18:40:52 -05:00
}
} else {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "ParseRemoteAddr" , err )
2015-02-22 09:49:25 -05:00
}
2014-08-28 23:24:37 -04:00
return
}
2020-01-12 07:11:17 -05:00
var gitServiceType = api . PlainGitService
2019-10-14 02:10:42 -04:00
u , err := url . Parse ( remoteAddr )
if err == nil && strings . EqualFold ( u . Host , "github.com" ) {
2020-01-12 07:11:17 -05:00
gitServiceType = api . GithubService
2019-10-14 02:10:42 -04:00
}
2019-05-06 21:12:51 -04:00
var opts = migrations . MigrateOptions {
2019-10-14 02:10:42 -04:00
CloneAddr : remoteAddr ,
RepoName : form . RepoName ,
Description : form . Description ,
Private : form . Private || setting . Repository . ForcePrivate ,
Mirror : form . Mirror ,
AuthUsername : form . AuthUsername ,
AuthPassword : form . AuthPassword ,
Wiki : form . Wiki ,
Issues : form . Issues ,
Milestones : form . Milestones ,
Labels : form . Labels ,
Comments : true ,
PullRequests : form . PullRequests ,
Releases : form . Releases ,
GitServiceType : gitServiceType ,
2019-05-06 21:12:51 -04:00
}
if opts . Mirror {
opts . Issues = false
opts . Milestones = false
opts . Labels = false
opts . Comments = false
opts . PullRequests = false
opts . Releases = false
}
2019-02-26 09:28:56 -05:00
2020-01-12 07:11:17 -05:00
repo , err := repo_module . CreateRepository ( ctx . User , ctxUser , models . CreateRepoOptions {
2020-01-10 10:35:17 -05:00
Name : opts . RepoName ,
Description : opts . Description ,
OriginalURL : form . CloneAddr ,
GitServiceType : gitServiceType ,
IsPrivate : opts . Private ,
IsMirror : opts . Mirror ,
Status : models . RepositoryBeingMigrated ,
2019-11-08 17:21:00 -05:00
} )
if err != nil {
handleMigrateError ( ctx , ctxUser , remoteAddr , err )
return
}
opts . MigrateToRepoID = repo . ID
2019-07-12 04:55:46 -04:00
2019-11-08 17:21:00 -05:00
defer func ( ) {
if e := recover ( ) ; e != nil {
var buf bytes . Buffer
fmt . Fprintf ( & buf , "Handler crashed with error: %v" , log . Stack ( 2 ) )
err = errors . New ( buf . String ( ) )
}
if err == nil {
repo . Status = models . RepositoryReady
if err := models . UpdateRepositoryCols ( repo , "status" ) ; err == nil {
notification . NotifyMigrateRepository ( ctx . User , ctxUser , repo )
return
}
}
if repo != nil {
if errDelete := models . DeleteRepository ( ctx . User , ctxUser . ID , repo . ID ) ; errDelete != nil {
log . Error ( "DeleteRepository: %v" , errDelete )
}
}
} ( )
2019-12-16 23:16:54 -05:00
if _ , err = migrations . MigrateRepository ( graceful . GetManager ( ) . HammerContext ( ) , ctx . User , ctxUser . Name , opts ) ; err != nil {
2019-11-08 17:21:00 -05:00
handleMigrateError ( ctx , ctxUser , remoteAddr , err )
2015-02-22 09:49:25 -05:00
return
2014-08-28 23:24:37 -04:00
}
2019-11-08 17:21:00 -05:00
log . Trace ( "Repository migrated: %s/%s" , ctxUser . Name , form . RepoName )
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusCreated , repo . APIFormat ( models . AccessModeAdmin ) )
2019-11-08 17:21:00 -05:00
}
func handleMigrateError ( ctx * context . APIContext , repoOwner * models . User , remoteAddr string , err error ) {
2019-05-06 21:12:51 -04:00
switch {
case models . IsErrRepoAlreadyExist ( err ) :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusConflict , "" , "The repository with the same name already exists." )
2019-05-06 21:12:51 -04:00
case migrations . IsRateLimitError ( err ) :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "Remote visit addressed rate limitation." )
2019-05-06 21:12:51 -04:00
case migrations . IsTwoFactorAuthError ( err ) :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "Remote visit required two factors authentication." )
2019-05-06 21:12:51 -04:00
case models . IsErrReachLimitOfRepo ( err ) :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "You have already reached your limit of %d repositories." , repoOwner . MaxCreationLimit ( ) ) )
2019-05-06 21:12:51 -04:00
case models . IsErrNameReserved ( err ) :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "The username '%s' is reserved." , err . ( models . ErrNameReserved ) . Name ) )
2019-05-06 21:12:51 -04:00
case models . IsErrNamePatternNotAllowed ( err ) :
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "The pattern '%s' is not allowed in a username." , err . ( models . ErrNamePatternNotAllowed ) . Pattern ) )
2019-05-06 21:12:51 -04:00
default :
err = util . URLSanitizedError ( err , remoteAddr )
if strings . Contains ( err . Error ( ) , "Authentication failed" ) ||
strings . Contains ( err . Error ( ) , "Bad credentials" ) ||
strings . Contains ( err . Error ( ) , "could not read Username" ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Authentication failed: %v." , err ) )
2019-05-06 21:12:51 -04:00
} else if strings . Contains ( err . Error ( ) , "fatal:" ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Migration failed: %v." , err ) )
2019-05-06 21:12:51 -04:00
} else {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "MigrateRepository" , err )
2019-05-06 21:12:51 -04:00
}
}
2014-08-28 23:24:37 -04:00
}
2015-10-04 11:09:16 -04:00
2016-10-07 13:17:27 -04:00
// Get one repository
2016-03-13 18:49:16 -04:00
func Get ( ctx * context . APIContext ) {
2017-11-13 02:02:25 -05:00
// 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"
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusOK , ctx . Repo . Repository . APIFormat ( ctx . Repo . AccessMode ) )
2015-10-22 17:46:07 -04:00
}
2016-10-03 06:35:42 -04:00
// GetByID returns a single Repository
func GetByID ( ctx * context . APIContext ) {
2017-11-13 02:02:25 -05:00
// 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
2018-10-20 23:40:42 -04:00
// format: int64
2017-11-13 02:02:25 -05:00
// required: true
// responses:
// "200":
// "$ref": "#/responses/Repository"
2019-12-20 12:07:12 -05:00
2016-10-03 06:35:42 -04:00
repo , err := models . GetRepositoryByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrRepoNotExist ( err ) {
2019-03-18 22:29:43 -04:00
ctx . NotFound ( )
2016-10-03 06:35:42 -04:00
} else {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "GetRepositoryByID" , err )
2016-10-03 06:35:42 -04:00
}
return
}
2018-11-28 06:26:14 -05:00
perm , err := models . GetUserRepoPermission ( repo , ctx . User )
2016-12-05 18:48:51 -05:00
if err != nil {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "AccessLevel" , err )
2017-07-29 21:13:33 -04:00
return
2018-11-28 06:26:14 -05:00
} else if ! perm . HasAccess ( ) {
2019-03-18 22:29:43 -04:00
ctx . NotFound ( )
2016-12-05 18:48:51 -05:00
return
}
2019-12-20 12:07:12 -05:00
ctx . JSON ( http . StatusOK , repo . APIFormat ( perm . AccessMode ) )
2016-10-03 06:35:42 -04:00
}
2019-05-30 11:09:05 -04:00
// 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"
2019-12-20 12:07:12 -05:00
2019-05-30 11:09:05 -04:00
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 ) {
2019-11-15 03:06:11 -05:00
if err := repo_service . ChangeRepositoryName ( ctx . User , repo , newRepoName ) ; err != nil {
2019-05-30 11:09:05 -04:00
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
}
2019-11-11 10:15:29 -05:00
if opts . Template != nil {
repo . IsTemplate = * opts . Template
}
2019-11-17 01:30:01 -05:00
// 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
}
2019-05-30 11:09:05 -04:00
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
2020-01-17 02:34:37 -05:00
var deleteUnitTypes [ ] models . UnitType
2019-05-30 11:09:05 -04:00
2020-01-17 02:34:37 -05:00
if opts . HasIssues != nil {
if * opts . HasIssues && opts . ExternalTracker != nil && ! models . UnitTypeExternalTracker . UnitGlobalDisabled ( ) {
2019-10-02 05:30:41 -04:00
// 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
2019-05-30 11:09:05 -04:00
}
2019-10-02 05:30:41 -04:00
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 ,
} ,
} )
2020-01-17 02:34:37 -05:00
deleteUnitTypes = append ( deleteUnitTypes , models . UnitTypeIssues )
} else if * opts . HasIssues && opts . ExternalTracker == nil && ! models . UnitTypeIssues . UnitGlobalDisabled ( ) {
2019-10-02 05:30:41 -04:00
// 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 ,
} )
2020-01-17 02:34:37 -05:00
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 )
}
2019-05-30 11:09:05 -04:00
}
}
2020-01-17 02:34:37 -05:00
if opts . HasWiki != nil {
if * opts . HasWiki && opts . ExternalWiki != nil && ! models . UnitTypeExternalWiki . UnitGlobalDisabled ( ) {
2019-10-02 05:30:41 -04:00
// 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 ,
} ,
} )
2020-01-17 02:34:37 -05:00
deleteUnitTypes = append ( deleteUnitTypes , models . UnitTypeWiki )
} else if * opts . HasWiki && opts . ExternalWiki == nil && ! models . UnitTypeWiki . UnitGlobalDisabled ( ) {
2019-10-02 05:30:41 -04:00
config := & models . UnitConfig { }
units = append ( units , models . RepoUnit {
RepoID : repo . ID ,
Type : models . UnitTypeWiki ,
Config : config ,
} )
2020-01-17 02:34:37 -05:00
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 )
}
2019-10-02 05:30:41 -04:00
}
2019-05-30 11:09:05 -04:00
}
2020-01-17 02:34:37 -05:00
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 ( )
2019-05-30 11:09:05 -04:00
}
2020-01-17 02:34:37 -05:00
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
}
2019-08-10 05:32:46 -04:00
2020-01-17 02:34:37 -05:00
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 )
}
2019-05-30 11:09:05 -04:00
}
2020-01-17 02:34:37 -05:00
if err := models . UpdateRepositoryUnits ( repo , units , deleteUnitTypes ) ; err != nil {
2019-05-30 11:09:05 -04:00
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
}
2016-10-07 13:17:27 -04:00
// Delete one repository
2016-03-13 18:49:16 -04:00
func Delete ( ctx * context . APIContext ) {
2017-11-13 02:02:25 -05:00
// 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"
2019-12-20 12:07:12 -05:00
2016-11-14 17:33:58 -05:00
owner := ctx . Repo . Owner
repo := ctx . Repo . Repository
2015-10-04 11:09:16 -04:00
2019-10-26 02:54:11 -04:00
canDelete , err := repo . CanUserDelete ( ctx . User )
if err != nil {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "CanUserDelete" , err )
2019-10-26 02:54:11 -04:00
return
} else if ! canDelete {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusForbidden , "" , "Given user is not owner of organization." )
2019-10-26 02:54:11 -04:00
return
2015-10-04 11:09:16 -04:00
}
2019-10-26 02:54:11 -04:00
if err := repo_service . DeleteRepository ( ctx . User , repo ) ; err != nil {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusInternalServerError , "DeleteRepository" , err )
2015-10-04 11:09:16 -04:00
return
}
2015-10-22 17:46:07 -04:00
log . Trace ( "Repository deleted: %s/%s" , owner . Name , repo . Name )
2019-12-20 12:07:12 -05:00
ctx . Status ( http . StatusNoContent )
2015-10-04 11:09:16 -04:00
}
2017-04-19 07:09:49 -04:00
// MirrorSync adds a mirrored repository to the sync queue
func MirrorSync ( ctx * context . APIContext ) {
2017-11-13 02:02:25 -05:00
// swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
// ---
// summary: Sync a mirrored repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo to sync
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo to sync
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/empty"
2019-12-20 12:07:12 -05:00
// "403":
// "$ref": "#/responses/forbidden"
2017-04-19 07:09:49 -04:00
repo := ctx . Repo . Repository
2018-11-28 06:26:14 -05:00
if ! ctx . Repo . CanWrite ( models . UnitTypeCode ) {
2019-12-20 12:07:12 -05:00
ctx . Error ( http . StatusForbidden , "MirrorSync" , "Must have write access" )
2017-04-19 07:09:49 -04:00
}
2019-10-01 09:40:17 -04:00
mirror_service . StartToMirror ( repo . ID )
2019-12-20 12:07:12 -05:00
ctx . Status ( http . StatusOK )
2017-04-19 07:09:49 -04:00
}