Creating a repo from a template repo via API (#15958)
* Creating a repo from a template repo via API fix #15934 ref: https://docs.github.com/en/rest/reference/repos#create-a-repository-using-a-template Signed-off-by: a1012112796 <1012112796@qq.com>
This commit is contained in:
parent
64122fe105
commit
5bb97a12d7
6 changed files with 296 additions and 0 deletions
|
@ -495,6 +495,43 @@ func TestAPIRepoTransfer(t *testing.T) {
|
||||||
_ = models.DeleteRepository(user, repo.OwnerID, repo.ID)
|
_ = models.DeleteRepository(user, repo.OwnerID, repo.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIGenerateRepo(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session)
|
||||||
|
|
||||||
|
templateRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 44}).(*models.Repository)
|
||||||
|
|
||||||
|
// user
|
||||||
|
repo := new(api.Repository)
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate?token=%s", templateRepo.OwnerName, templateRepo.Name, token), &api.GenerateRepoOption{
|
||||||
|
Owner: user.Name,
|
||||||
|
Name: "new-repo",
|
||||||
|
Description: "test generate repo",
|
||||||
|
Private: false,
|
||||||
|
GitContent: true,
|
||||||
|
})
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
DecodeJSON(t, resp, repo)
|
||||||
|
|
||||||
|
assert.Equal(t, "new-repo", repo.Name)
|
||||||
|
|
||||||
|
// org
|
||||||
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate?token=%s", templateRepo.OwnerName, templateRepo.Name, token), &api.GenerateRepoOption{
|
||||||
|
Owner: "user3",
|
||||||
|
Name: "new-repo",
|
||||||
|
Description: "test generate repo",
|
||||||
|
Private: false,
|
||||||
|
GitContent: true,
|
||||||
|
})
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
DecodeJSON(t, resp, repo)
|
||||||
|
|
||||||
|
assert.Equal(t, "new-repo", repo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIRepoGetReviewers(t *testing.T) {
|
func TestAPIRepoGetReviewers(t *testing.T) {
|
||||||
defer prepareTestEnv(t)()
|
defer prepareTestEnv(t)()
|
||||||
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
|
||||||
|
|
|
@ -180,6 +180,36 @@ type EditRepoOption struct {
|
||||||
MirrorInterval *string `json:"mirror_interval,omitempty"`
|
MirrorInterval *string `json:"mirror_interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateRepoOption options when creating repository using a template
|
||||||
|
// swagger:model
|
||||||
|
type GenerateRepoOption struct {
|
||||||
|
// The organization or person who will own the new repository
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
// Name of the repository to create
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
// unique: true
|
||||||
|
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||||
|
// Description of the repository to create
|
||||||
|
Description string `json:"description" binding:"MaxSize(255)"`
|
||||||
|
// Whether the repository is private
|
||||||
|
Private bool `json:"private"`
|
||||||
|
// include git content of default branch in template repo
|
||||||
|
GitContent bool `json:"git_content"`
|
||||||
|
// include topics in template repo
|
||||||
|
Topics bool `json:"topics"`
|
||||||
|
// include git hooks in template repo
|
||||||
|
GitHooks bool `json:"git_hooks"`
|
||||||
|
// include webhooks in template repo
|
||||||
|
Webhooks bool `json:"webhooks"`
|
||||||
|
// include avatar of the template repo
|
||||||
|
Avatar bool `json:"avatar"`
|
||||||
|
// include labels in template repo
|
||||||
|
Labels bool `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
// CreateBranchRepoOption options when creating a branch in a repository
|
// CreateBranchRepoOption options when creating a branch in a repository
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type CreateBranchRepoOption struct {
|
type CreateBranchRepoOption struct {
|
||||||
|
|
|
@ -722,6 +722,7 @@ func Routes() *web.Route {
|
||||||
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
|
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
|
||||||
Delete(reqToken(), reqOwner(), repo.Delete).
|
Delete(reqToken(), reqOwner(), repo.Delete).
|
||||||
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
|
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
|
||||||
|
m.Post("/generate", reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
|
||||||
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
|
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
|
||||||
m.Combo("/notifications").
|
m.Combo("/notifications").
|
||||||
Get(reqToken(), notify.ListRepoNotifications).
|
Get(reqToken(), notify.ListRepoNotifications).
|
||||||
|
|
|
@ -307,6 +307,115 @@ func Create(ctx *context.APIContext) {
|
||||||
CreateUserRepo(ctx, ctx.User, *opt)
|
CreateUserRepo(ctx, ctx.User, *opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate Create a repository using a template
|
||||||
|
func Generate(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{template_owner}/{template_repo}/generate repository generateRepo
|
||||||
|
// ---
|
||||||
|
// summary: Create a repository using a template
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: template_owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the template repository owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: template_repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the template repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/GenerateRepoOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/Repository"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "409":
|
||||||
|
// description: The repository with the same name already exists.
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
form := web.GetForm(ctx).(*api.GenerateRepoOption)
|
||||||
|
|
||||||
|
if !ctx.Repo.Repository.IsTemplate {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", "this is not a template repo")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.User.IsOrganization() {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := models.GenerateRepoOptions{
|
||||||
|
Name: form.Name,
|
||||||
|
Description: form.Description,
|
||||||
|
Private: form.Private,
|
||||||
|
GitContent: form.GitContent,
|
||||||
|
Topics: form.Topics,
|
||||||
|
GitHooks: form.GitHooks,
|
||||||
|
Webhooks: form.Webhooks,
|
||||||
|
Avatar: form.Avatar,
|
||||||
|
IssueLabels: form.Labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.IsValid() {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", "must select at least one template item")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxUser := ctx.User
|
||||||
|
var err error
|
||||||
|
if form.Owner != ctxUser.Name {
|
||||||
|
ctxUser, err = models.GetOrgByName(form.Owner)
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrOrgNotExist(err) {
|
||||||
|
ctx.JSON(http.StatusNotFound, map[string]interface{}{
|
||||||
|
"error": "request owner `" + form.Name + "` is not exist",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.User.IsAdmin {
|
||||||
|
canCreate, err := ctxUser.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := repo_service.GenerateRepository(ctx.User, ctxUser, ctx.Repo.Repository, opts)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusCreated, convert.ToRepo(repo, models.AccessModeOwner))
|
||||||
|
}
|
||||||
|
|
||||||
// CreateOrgRepoDeprecated create one repository of the organization
|
// CreateOrgRepoDeprecated create one repository of the organization
|
||||||
func CreateOrgRepoDeprecated(ctx *context.APIContext) {
|
func CreateOrgRepoDeprecated(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
|
// swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
|
||||||
|
|
|
@ -87,6 +87,8 @@ type swaggerParameterBodies struct {
|
||||||
TransferRepoOption api.TransferRepoOption
|
TransferRepoOption api.TransferRepoOption
|
||||||
// in:body
|
// in:body
|
||||||
CreateForkOption api.CreateForkOption
|
CreateForkOption api.CreateForkOption
|
||||||
|
// in:body
|
||||||
|
GenerateRepoOption api.GenerateRepoOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateStatusOption api.CreateStatusOption
|
CreateStatusOption api.CreateStatusOption
|
||||||
|
|
|
@ -9777,6 +9777,61 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{template_owner}/{template_repo}/generate": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Create a repository using a template",
|
||||||
|
"operationId": "generateRepo",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the template repository owner",
|
||||||
|
"name": "template_owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the template repository",
|
||||||
|
"name": "template_repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/GenerateRepoOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"$ref": "#/responses/Repository"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"description": "The repository with the same name already exists."
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repositories/{id}": {
|
"/repositories/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -14551,6 +14606,68 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"GenerateRepoOption": {
|
||||||
|
"description": "GenerateRepoOption options when creating repository using a template",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"owner",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"avatar": {
|
||||||
|
"description": "include avatar of the template repo",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Avatar"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"description": "Description of the repository to create",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Description"
|
||||||
|
},
|
||||||
|
"git_content": {
|
||||||
|
"description": "include git content of default branch in template repo",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "GitContent"
|
||||||
|
},
|
||||||
|
"git_hooks": {
|
||||||
|
"description": "include git hooks in template repo",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "GitHooks"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"description": "include labels in template repo",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Labels"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Name of the repository to create",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true,
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"description": "The organization or person who will own the new repository",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Owner"
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"description": "Whether the repository is private",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Private"
|
||||||
|
},
|
||||||
|
"topics": {
|
||||||
|
"description": "include topics in template repo",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Topics"
|
||||||
|
},
|
||||||
|
"webhooks": {
|
||||||
|
"description": "include webhooks in template repo",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Webhooks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"GitBlobResponse": {
|
"GitBlobResponse": {
|
||||||
"description": "GitBlobResponse represents a git blob",
|
"description": "GitBlobResponse represents a git blob",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
Reference in a new issue