2014-08-26 14:11:15 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2015-12-05 01:16:42 +03:00
package repo
2014-08-26 14:11:15 +04:00
import (
2017-08-17 04:31:34 +03:00
"fmt"
2017-10-27 00:16:13 +03:00
"net/http"
2017-02-11 07:00:01 +03:00
"strings"
2014-08-26 14:11:15 +04:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2017-10-27 00:16:13 +03:00
"code.gitea.io/gitea/modules/util"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/routers/api/v1/convert"
2017-12-04 04:48:03 +03:00
api "code.gitea.io/sdk/gitea"
2014-08-26 14:11:15 +04:00
)
2016-11-24 10:04:31 +03:00
// Search repositories via options
2016-03-14 01:49:16 +03:00
func Search ( ctx * context . APIContext ) {
2017-11-13 10:02:25 +03:00
// swagger:operation GET /repos/search repository repoSearch
// ---
// summary: Search for repositories
// produces:
// - application/json
// parameters:
// - name: q
// in: query
// description: keyword
// type: string
// - name: uid
// in: query
2017-11-15 11:10:26 +03:00
// description: search only for repos that the user with the given id owns or contributes to
// type: integer
// - name: page
// in: query
// description: page number of results to return (1-based)
2017-11-13 10:02:25 +03:00
// type: integer
// - name: limit
// in: query
2017-11-15 11:10:26 +03:00
// description: page size of results, maximum page size is 50
2017-11-13 10:02:25 +03:00
// type: integer
// - 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 11:10:26 +03:00
// description: if `uid` is given, search only for repos that the user owns
2017-11-13 10:02:25 +03:00
// type: boolean
// responses:
// "200":
// "$ref": "#/responses/SearchResults"
// "422":
// "$ref": "#/responses/validationError"
2016-03-11 23:33:12 +03:00
opts := & models . SearchRepoOptions {
2017-10-27 00:16:13 +03:00
Keyword : strings . Trim ( ctx . Query ( "q" ) , " " ) ,
OwnerID : ctx . QueryInt64 ( "uid" ) ,
2017-11-15 11:10:26 +03:00
Page : ctx . QueryInt ( "page" ) ,
2017-10-27 00:16:13 +03:00
PageSize : convert . ToCorrectPageSize ( ctx . QueryInt ( "limit" ) ) ,
Collaborate : util . OptionalBoolNone ,
}
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 14:11:15 +04:00
}
2017-10-27 00:16:13 +03:00
var err error
2017-10-17 18:20:22 +03:00
if opts . OwnerID > 0 {
var repoOwner * models . User
if ctx . User != nil && ctx . User . ID == opts . OwnerID {
repoOwner = ctx . User
2014-10-25 15:50:19 +04:00
} else {
2017-10-17 18:20:22 +03:00
repoOwner , err = models . GetUserByID ( opts . OwnerID )
2014-10-25 15:50:19 +04:00
if err != nil {
2017-05-02 16:35:59 +03:00
ctx . JSON ( 500 , api . SearchError {
OK : false ,
Error : err . Error ( ) ,
2014-10-25 15:50:19 +04:00
} )
return
}
2017-10-17 18:20:22 +03:00
}
2017-08-23 04:30:54 +03:00
2017-10-27 00:16:13 +03:00
if repoOwner . IsOrganization ( ) {
opts . Collaborate = util . OptionalBoolFalse
2017-10-17 18:20:22 +03:00
}
// Check visibility.
if ctx . IsSigned && ( ctx . User . ID == repoOwner . ID || ( repoOwner . IsOrganization ( ) && repoOwner . IsOwnedBy ( ctx . User . ID ) ) ) {
opts . Private = true
2014-10-25 15:50:19 +04:00
}
}
2016-07-04 12:27:06 +03:00
repos , count , err := models . SearchRepositoryByName ( opts )
2014-08-26 14:11:15 +04:00
if err != nil {
2017-05-02 16:35:59 +03:00
ctx . JSON ( 500 , api . SearchError {
OK : false ,
Error : err . Error ( ) ,
2014-08-26 14:11:15 +04:00
} )
return
}
2017-04-30 08:08:09 +03:00
var userID int64
if ctx . IsSigned {
userID = ctx . User . ID
}
2014-11-15 01:11:30 +03:00
results := make ( [ ] * api . Repository , len ( repos ) )
2017-02-10 04:30:26 +03:00
for i , repo := range repos {
if err = repo . GetOwner ( ) ; err != nil {
2017-05-02 16:35:59 +03:00
ctx . JSON ( 500 , api . SearchError {
OK : false ,
Error : err . Error ( ) ,
2014-08-26 14:11:15 +04:00
} )
return
}
2017-04-30 08:08:09 +03:00
accessMode , err := models . AccessLevel ( userID , repo )
2017-02-10 04:30:26 +03:00
if err != nil {
2017-05-02 16:35:59 +03:00
ctx . JSON ( 500 , api . SearchError {
OK : false ,
Error : err . Error ( ) ,
2017-02-10 04:30:26 +03:00
} )
2014-08-26 14:11:15 +04:00
}
2017-02-10 04:30:26 +03:00
results [ i ] = repo . APIFormat ( accessMode )
2014-08-26 14:11:15 +04:00
}
2016-07-04 12:27:06 +03:00
ctx . SetLinkHeader ( int ( count ) , setting . API . MaxResponseItems )
2017-08-17 04:31:34 +03:00
ctx . Header ( ) . Set ( "X-Total-Count" , fmt . Sprintf ( "%d" , count ) )
2017-05-02 16:35:59 +03:00
ctx . JSON ( 200 , api . SearchResults {
OK : true ,
Data : results ,
2014-08-26 14:11:15 +04:00
} )
}
2014-08-29 07:24:37 +04:00
2016-11-24 10:04:31 +03:00
// CreateUserRepo create a repository for a user
2016-03-14 01:49:16 +03:00
func CreateUserRepo ( ctx * context . APIContext , owner * models . User , opt api . CreateRepoOption ) {
2017-09-03 11:20:24 +03:00
repo , err := models . CreateRepository ( ctx . User , owner , models . CreateRepoOptions {
2015-08-28 13:33:09 +03:00
Name : opt . Name ,
Description : opt . Description ,
2015-08-28 14:06:18 +03:00
Gitignores : opt . Gitignores ,
2015-08-28 13:33:09 +03:00
License : opt . License ,
2015-08-28 14:06:18 +03:00
Readme : opt . Readme ,
IsPrivate : opt . Private ,
AutoInit : opt . AutoInit ,
2015-08-28 13:33:09 +03:00
} )
2014-12-13 04:30:32 +03:00
if err != nil {
2015-08-08 12:10:34 +03:00
if models . IsErrRepoAlreadyExist ( err ) ||
2015-03-27 00:11:47 +03:00
models . IsErrNameReserved ( err ) ||
models . IsErrNamePatternNotAllowed ( err ) {
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , err )
2014-12-13 04:30:32 +03:00
} else {
if repo != nil {
2017-09-03 11:20:24 +03:00
if err = models . DeleteRepository ( ctx . User , ctx . User . ID , repo . ID ) ; err != nil {
2014-12-13 04:30:32 +03:00
log . Error ( 4 , "DeleteRepository: %v" , err )
}
}
2016-03-14 01:49:16 +03:00
ctx . Error ( 500 , "CreateRepository" , err )
2014-12-13 04:30:32 +03:00
}
return
}
2016-12-06 02:48:51 +03:00
ctx . JSON ( 201 , repo . APIFormat ( models . AccessModeOwner ) )
2014-12-13 04:30:32 +03:00
}
2016-10-07 20:17:27 +03:00
// Create one repository of mine
2016-03-14 01:49:16 +03:00
func Create ( ctx * context . APIContext , opt api . CreateRepoOption ) {
2017-11-13 10:02:25 +03: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"
2014-12-13 04:30:32 +03:00
if ctx . User . IsOrganization ( ) {
2017-11-13 10:02:25 +03:00
// Shouldn't reach this condition, but just in case.
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , "not allowed creating repository for organization" )
2014-12-13 04:30:32 +03:00
return
}
2015-12-18 06:57:41 +03:00
CreateUserRepo ( ctx , ctx . User , opt )
2014-12-13 04:30:32 +03:00
}
2016-11-24 10:04:31 +03:00
// CreateOrgRepo create one repository of the organization
2016-03-14 01:49:16 +03:00
func CreateOrgRepo ( ctx * context . APIContext , opt api . CreateRepoOption ) {
2017-11-13 10:02:25 +03:00
// swagger:operation POST /org/{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"
// "422":
// "$ref": "#/responses/validationError"
// "403":
// "$ref": "#/responses/forbidden"
2014-12-13 04:30:32 +03:00
org , err := models . GetOrgByName ( ctx . Params ( ":org" ) )
if err != nil {
2017-07-06 16:30:19 +03:00
if models . IsErrOrgNotExist ( err ) {
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , err )
2014-12-13 04:30:32 +03:00
} else {
2016-03-14 01:49:16 +03:00
ctx . Error ( 500 , "GetOrgByName" , err )
2014-12-13 04:30:32 +03:00
}
return
}
2016-07-23 20:08:22 +03:00
if ! org . IsOwnedBy ( ctx . User . ID ) {
2016-03-14 01:49:16 +03:00
ctx . Error ( 403 , "" , "Given user is not owner of organization." )
2014-12-13 04:30:32 +03:00
return
}
2015-12-18 06:57:41 +03:00
CreateUserRepo ( ctx , org , opt )
2014-12-13 04:30:32 +03:00
}
2016-11-24 10:04:31 +03:00
// Migrate migrate remote git repository to gitea
2016-03-14 01:49:16 +03:00
func Migrate ( ctx * context . APIContext , form auth . MigrateRepoForm ) {
2017-11-13 10:02:25 +03: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"
2015-09-03 13:17:33 +03:00
ctxUser := ctx . User
2015-11-25 08:55:37 +03:00
// Not equal means context user is an organization,
// or is another user/organization if current user is admin.
2016-11-27 09:03:59 +03:00
if form . UID != ctxUser . ID {
org , err := models . GetUserByID ( form . UID )
2014-08-29 13:31:53 +04:00
if err != nil {
2015-08-05 06:14:17 +03:00
if models . IsErrUserNotExist ( err ) {
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , err )
2015-02-22 17:49:25 +03:00
} else {
2016-03-14 01:49:16 +03:00
ctx . Error ( 500 , "GetUserByID" , err )
2015-02-22 17:49:25 +03:00
}
2014-08-29 07:24:37 +04:00
return
}
ctxUser = org
}
if ctx . HasError ( ) {
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , ctx . GetErrMsg ( ) )
2014-08-29 07:24:37 +04:00
return
}
2015-11-25 08:55:37 +03:00
if ctxUser . IsOrganization ( ) && ! ctx . User . IsAdmin {
2014-08-29 07:24:37 +04:00
// Check ownership of organization.
2016-07-23 20:08:22 +03:00
if ! ctxUser . IsOwnedBy ( ctx . User . ID ) {
2016-03-14 01:49:16 +03:00
ctx . Error ( 403 , "" , "Given user is not owner of organization." )
2014-08-29 07:24:37 +04:00
return
}
}
2015-11-04 02:40:52 +03:00
remoteAddr , err := form . ParseRemoteAddr ( ctx . User )
if err != nil {
if models . IsErrInvalidCloneAddr ( err ) {
addrErr := err . ( models . ErrInvalidCloneAddr )
switch {
case addrErr . IsURLError :
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , err )
2015-11-04 02:40:52 +03:00
case addrErr . IsPermissionDenied :
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , "You are not allowed to import local repositories." )
2015-11-04 02:40:52 +03:00
case addrErr . IsInvalidPath :
2016-03-14 01:49:16 +03:00
ctx . Error ( 422 , "" , "Invalid local path, it does not exist or not a directory." )
2015-11-04 02:40:52 +03:00
default :
2016-03-14 01:49:16 +03:00
ctx . Error ( 500 , "ParseRemoteAddr" , "Unknown error type (ErrInvalidCloneAddr): " + err . Error ( ) )
2015-11-04 02:40:52 +03:00
}
} else {
2016-03-14 01:49:16 +03:00
ctx . Error ( 500 , "ParseRemoteAddr" , err )
2015-02-22 17:49:25 +03:00
}
2014-08-29 07:24:37 +04:00
return
}
2017-09-03 11:20:24 +03:00
repo , err := models . MigrateRepository ( ctx . User , ctxUser , models . MigrateRepoOptions {
2015-10-25 11:26:26 +03:00
Name : form . RepoName ,
Description : form . Description ,
IsPrivate : form . Private || setting . Repository . ForcePrivate ,
IsMirror : form . Mirror ,
RemoteAddr : remoteAddr ,
} )
2015-02-22 17:49:25 +03:00
if err != nil {
2017-12-04 04:48:03 +03:00
err = util . URLSanitizedError ( err , remoteAddr )
2015-02-22 17:49:25 +03:00
if repo != nil {
2017-09-03 11:20:24 +03:00
if errDelete := models . DeleteRepository ( ctx . User , ctxUser . ID , repo . ID ) ; errDelete != nil {
2015-02-22 17:49:25 +03:00
log . Error ( 4 , "DeleteRepository: %v" , errDelete )
}
2014-08-29 07:24:37 +04:00
}
2017-12-04 04:48:03 +03:00
ctx . Error ( 500 , "MigrateRepository" , err )
2015-02-22 17:49:25 +03:00
return
2014-08-29 07:24:37 +04:00
}
2015-02-22 17:49:25 +03:00
log . Trace ( "Repository migrated: %s/%s" , ctxUser . Name , form . RepoName )
2016-12-06 02:48:51 +03:00
ctx . JSON ( 201 , repo . APIFormat ( models . AccessModeAdmin ) )
2014-08-29 07:24:37 +04:00
}
2015-10-04 18:09:16 +03:00
2016-10-07 20:17:27 +03:00
// Get one repository
2016-03-14 01:49:16 +03:00
func Get ( ctx * context . APIContext ) {
2017-11-13 10:02:25 +03: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"
2017-07-12 04:23:41 +03:00
ctx . JSON ( 200 , ctx . Repo . Repository . APIFormat ( ctx . Repo . AccessMode ) )
2015-10-23 00:46:07 +03:00
}
2016-10-03 13:35:42 +03:00
// GetByID returns a single Repository
func GetByID ( ctx * context . APIContext ) {
2017-11-13 10:02:25 +03: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
// required: true
// responses:
// "200":
// "$ref": "#/responses/Repository"
2016-10-03 13:35:42 +03:00
repo , err := models . GetRepositoryByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrRepoNotExist ( err ) {
ctx . Status ( 404 )
} else {
ctx . Error ( 500 , "GetRepositoryByID" , err )
}
return
}
2017-03-15 03:51:46 +03:00
access , err := models . AccessLevel ( ctx . User . ID , repo )
2016-12-06 02:48:51 +03:00
if err != nil {
2017-07-30 04:13:33 +03:00
ctx . Error ( 500 , "AccessLevel" , err )
return
} else if access < models . AccessModeRead {
ctx . Status ( 404 )
2016-12-06 02:48:51 +03:00
return
}
ctx . JSON ( 200 , repo . APIFormat ( access ) )
2016-10-03 13:35:42 +03:00
}
2016-10-07 20:17:27 +03:00
// Delete one repository
2016-03-14 01:49:16 +03:00
func Delete ( ctx * context . APIContext ) {
2017-11-13 10:02:25 +03: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"
2016-12-29 16:17:32 +03:00
if ! ctx . Repo . IsAdmin ( ) {
ctx . Error ( 403 , "" , "Must have admin rights" )
return
}
2016-11-15 01:33:58 +03:00
owner := ctx . Repo . Owner
repo := ctx . Repo . Repository
2015-10-04 18:09:16 +03:00
2016-07-23 20:08:22 +03:00
if owner . IsOrganization ( ) && ! owner . IsOwnedBy ( ctx . User . ID ) {
2016-03-14 01:49:16 +03:00
ctx . Error ( 403 , "" , "Given user is not owner of organization." )
2015-10-04 18:09:16 +03:00
return
}
2017-09-03 11:20:24 +03:00
if err := models . DeleteRepository ( ctx . User , owner . ID , repo . ID ) ; err != nil {
2016-03-14 01:49:16 +03:00
ctx . Error ( 500 , "DeleteRepository" , err )
2015-10-04 18:09:16 +03:00
return
}
2015-10-23 00:46:07 +03:00
log . Trace ( "Repository deleted: %s/%s" , owner . Name , repo . Name )
2015-10-04 18:09:16 +03:00
ctx . Status ( 204 )
}
2017-04-19 14:09:49 +03:00
// MirrorSync adds a mirrored repository to the sync queue
func MirrorSync ( ctx * context . APIContext ) {
2017-11-13 10:02:25 +03: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"
2017-04-19 14:09:49 +03:00
repo := ctx . Repo . Repository
if ! ctx . Repo . IsWriter ( ) {
ctx . Error ( 403 , "MirrorSync" , "Must have write access" )
}
go models . MirrorQueue . Add ( repo . ID )
ctx . Status ( 200 )
}