Skip to content

Commit 1bd2bff

Browse files
authored
Merge branch 'main' into delete-pull-branch-on-merge
2 parents 2bd49cf + 5bb97a1 commit 1bd2bff

File tree

6 files changed

+296
-0
lines changed

6 files changed

+296
-0
lines changed

Diff for: integrations/api_repo_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,43 @@ func TestAPIRepoTransfer(t *testing.T) {
495495
_ = models.DeleteRepository(user, repo.OwnerID, repo.ID)
496496
}
497497

498+
func TestAPIGenerateRepo(t *testing.T) {
499+
defer prepareTestEnv(t)()
500+
501+
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
502+
session := loginUser(t, user.Name)
503+
token := getTokenForLoggedInUser(t, session)
504+
505+
templateRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 44}).(*models.Repository)
506+
507+
// user
508+
repo := new(api.Repository)
509+
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate?token=%s", templateRepo.OwnerName, templateRepo.Name, token), &api.GenerateRepoOption{
510+
Owner: user.Name,
511+
Name: "new-repo",
512+
Description: "test generate repo",
513+
Private: false,
514+
GitContent: true,
515+
})
516+
resp := session.MakeRequest(t, req, http.StatusCreated)
517+
DecodeJSON(t, resp, repo)
518+
519+
assert.Equal(t, "new-repo", repo.Name)
520+
521+
// org
522+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate?token=%s", templateRepo.OwnerName, templateRepo.Name, token), &api.GenerateRepoOption{
523+
Owner: "user3",
524+
Name: "new-repo",
525+
Description: "test generate repo",
526+
Private: false,
527+
GitContent: true,
528+
})
529+
resp = session.MakeRequest(t, req, http.StatusCreated)
530+
DecodeJSON(t, resp, repo)
531+
532+
assert.Equal(t, "new-repo", repo.Name)
533+
}
534+
498535
func TestAPIRepoGetReviewers(t *testing.T) {
499536
defer prepareTestEnv(t)()
500537
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)

Diff for: modules/structs/repo.go

+30
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,36 @@ type EditRepoOption struct {
182182
MirrorInterval *string `json:"mirror_interval,omitempty"`
183183
}
184184

185+
// GenerateRepoOption options when creating repository using a template
186+
// swagger:model
187+
type GenerateRepoOption struct {
188+
// The organization or person who will own the new repository
189+
//
190+
// required: true
191+
Owner string `json:"owner"`
192+
// Name of the repository to create
193+
//
194+
// required: true
195+
// unique: true
196+
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"`
197+
// Description of the repository to create
198+
Description string `json:"description" binding:"MaxSize(255)"`
199+
// Whether the repository is private
200+
Private bool `json:"private"`
201+
// include git content of default branch in template repo
202+
GitContent bool `json:"git_content"`
203+
// include topics in template repo
204+
Topics bool `json:"topics"`
205+
// include git hooks in template repo
206+
GitHooks bool `json:"git_hooks"`
207+
// include webhooks in template repo
208+
Webhooks bool `json:"webhooks"`
209+
// include avatar of the template repo
210+
Avatar bool `json:"avatar"`
211+
// include labels in template repo
212+
Labels bool `json:"labels"`
213+
}
214+
185215
// CreateBranchRepoOption options when creating a branch in a repository
186216
// swagger:model
187217
type CreateBranchRepoOption struct {

Diff for: routers/api/v1/api.go

+1
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ func Routes() *web.Route {
722722
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
723723
Delete(reqToken(), reqOwner(), repo.Delete).
724724
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
725+
m.Post("/generate", reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
725726
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
726727
m.Combo("/notifications").
727728
Get(reqToken(), notify.ListRepoNotifications).

Diff for: routers/api/v1/repo/repo.go

+109
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,115 @@ func Create(ctx *context.APIContext) {
307307
CreateUserRepo(ctx, ctx.User, *opt)
308308
}
309309

310+
// Generate Create a repository using a template
311+
func Generate(ctx *context.APIContext) {
312+
// swagger:operation POST /repos/{template_owner}/{template_repo}/generate repository generateRepo
313+
// ---
314+
// summary: Create a repository using a template
315+
// consumes:
316+
// - application/json
317+
// produces:
318+
// - application/json
319+
// parameters:
320+
// - name: template_owner
321+
// in: path
322+
// description: name of the template repository owner
323+
// type: string
324+
// required: true
325+
// - name: template_repo
326+
// in: path
327+
// description: name of the template repository
328+
// type: string
329+
// required: true
330+
// - name: body
331+
// in: body
332+
// schema:
333+
// "$ref": "#/definitions/GenerateRepoOption"
334+
// responses:
335+
// "201":
336+
// "$ref": "#/responses/Repository"
337+
// "403":
338+
// "$ref": "#/responses/forbidden"
339+
// "404":
340+
// "$ref": "#/responses/notFound"
341+
// "409":
342+
// description: The repository with the same name already exists.
343+
// "422":
344+
// "$ref": "#/responses/validationError"
345+
form := web.GetForm(ctx).(*api.GenerateRepoOption)
346+
347+
if !ctx.Repo.Repository.IsTemplate {
348+
ctx.Error(http.StatusUnprocessableEntity, "", "this is not a template repo")
349+
return
350+
}
351+
352+
if ctx.User.IsOrganization() {
353+
ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
354+
return
355+
}
356+
357+
opts := models.GenerateRepoOptions{
358+
Name: form.Name,
359+
Description: form.Description,
360+
Private: form.Private,
361+
GitContent: form.GitContent,
362+
Topics: form.Topics,
363+
GitHooks: form.GitHooks,
364+
Webhooks: form.Webhooks,
365+
Avatar: form.Avatar,
366+
IssueLabels: form.Labels,
367+
}
368+
369+
if !opts.IsValid() {
370+
ctx.Error(http.StatusUnprocessableEntity, "", "must select at least one template item")
371+
return
372+
}
373+
374+
ctxUser := ctx.User
375+
var err error
376+
if form.Owner != ctxUser.Name {
377+
ctxUser, err = models.GetOrgByName(form.Owner)
378+
if err != nil {
379+
if models.IsErrOrgNotExist(err) {
380+
ctx.JSON(http.StatusNotFound, map[string]interface{}{
381+
"error": "request owner `" + form.Name + "` is not exist",
382+
})
383+
return
384+
}
385+
386+
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
387+
return
388+
}
389+
390+
if !ctx.User.IsAdmin {
391+
canCreate, err := ctxUser.CanCreateOrgRepo(ctx.User.ID)
392+
if err != nil {
393+
ctx.ServerError("CanCreateOrgRepo", err)
394+
return
395+
} else if !canCreate {
396+
ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
397+
return
398+
}
399+
}
400+
}
401+
402+
repo, err := repo_service.GenerateRepository(ctx.User, ctxUser, ctx.Repo.Repository, opts)
403+
if err != nil {
404+
if models.IsErrRepoAlreadyExist(err) {
405+
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
406+
} else if models.IsErrNameReserved(err) ||
407+
models.IsErrNamePatternNotAllowed(err) {
408+
ctx.Error(http.StatusUnprocessableEntity, "", err)
409+
} else {
410+
ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
411+
}
412+
return
413+
}
414+
log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
415+
416+
ctx.JSON(http.StatusCreated, convert.ToRepo(repo, models.AccessModeOwner))
417+
}
418+
310419
// CreateOrgRepoDeprecated create one repository of the organization
311420
func CreateOrgRepoDeprecated(ctx *context.APIContext) {
312421
// swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated

Diff for: routers/api/v1/swagger/options.go

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ type swaggerParameterBodies struct {
8787
TransferRepoOption api.TransferRepoOption
8888
// in:body
8989
CreateForkOption api.CreateForkOption
90+
// in:body
91+
GenerateRepoOption api.GenerateRepoOption
9092

9193
// in:body
9294
CreateStatusOption api.CreateStatusOption

Diff for: templates/swagger/v1_json.tmpl

+117
Original file line numberDiff line numberDiff line change
@@ -9777,6 +9777,61 @@
97779777
}
97789778
}
97799779
},
9780+
"/repos/{template_owner}/{template_repo}/generate": {
9781+
"post": {
9782+
"consumes": [
9783+
"application/json"
9784+
],
9785+
"produces": [
9786+
"application/json"
9787+
],
9788+
"tags": [
9789+
"repository"
9790+
],
9791+
"summary": "Create a repository using a template",
9792+
"operationId": "generateRepo",
9793+
"parameters": [
9794+
{
9795+
"type": "string",
9796+
"description": "name of the template repository owner",
9797+
"name": "template_owner",
9798+
"in": "path",
9799+
"required": true
9800+
},
9801+
{
9802+
"type": "string",
9803+
"description": "name of the template repository",
9804+
"name": "template_repo",
9805+
"in": "path",
9806+
"required": true
9807+
},
9808+
{
9809+
"name": "body",
9810+
"in": "body",
9811+
"schema": {
9812+
"$ref": "#/definitions/GenerateRepoOption"
9813+
}
9814+
}
9815+
],
9816+
"responses": {
9817+
"201": {
9818+
"$ref": "#/responses/Repository"
9819+
},
9820+
"403": {
9821+
"$ref": "#/responses/forbidden"
9822+
},
9823+
"404": {
9824+
"$ref": "#/responses/notFound"
9825+
},
9826+
"409": {
9827+
"description": "The repository with the same name already exists."
9828+
},
9829+
"422": {
9830+
"$ref": "#/responses/validationError"
9831+
}
9832+
}
9833+
}
9834+
},
97809835
"/repositories/{id}": {
97819836
"get": {
97829837
"produces": [
@@ -14556,6 +14611,68 @@
1455614611
},
1455714612
"x-go-package": "code.gitea.io/gitea/modules/structs"
1455814613
},
14614+
"GenerateRepoOption": {
14615+
"description": "GenerateRepoOption options when creating repository using a template",
14616+
"type": "object",
14617+
"required": [
14618+
"owner",
14619+
"name"
14620+
],
14621+
"properties": {
14622+
"avatar": {
14623+
"description": "include avatar of the template repo",
14624+
"type": "boolean",
14625+
"x-go-name": "Avatar"
14626+
},
14627+
"description": {
14628+
"description": "Description of the repository to create",
14629+
"type": "string",
14630+
"x-go-name": "Description"
14631+
},
14632+
"git_content": {
14633+
"description": "include git content of default branch in template repo",
14634+
"type": "boolean",
14635+
"x-go-name": "GitContent"
14636+
},
14637+
"git_hooks": {
14638+
"description": "include git hooks in template repo",
14639+
"type": "boolean",
14640+
"x-go-name": "GitHooks"
14641+
},
14642+
"labels": {
14643+
"description": "include labels in template repo",
14644+
"type": "boolean",
14645+
"x-go-name": "Labels"
14646+
},
14647+
"name": {
14648+
"description": "Name of the repository to create",
14649+
"type": "string",
14650+
"uniqueItems": true,
14651+
"x-go-name": "Name"
14652+
},
14653+
"owner": {
14654+
"description": "The organization or person who will own the new repository",
14655+
"type": "string",
14656+
"x-go-name": "Owner"
14657+
},
14658+
"private": {
14659+
"description": "Whether the repository is private",
14660+
"type": "boolean",
14661+
"x-go-name": "Private"
14662+
},
14663+
"topics": {
14664+
"description": "include topics in template repo",
14665+
"type": "boolean",
14666+
"x-go-name": "Topics"
14667+
},
14668+
"webhooks": {
14669+
"description": "include webhooks in template repo",
14670+
"type": "boolean",
14671+
"x-go-name": "Webhooks"
14672+
}
14673+
},
14674+
"x-go-package": "code.gitea.io/gitea/modules/structs"
14675+
},
1455914676
"GitBlobResponse": {
1456014677
"description": "GitBlobResponse represents a git blob",
1456114678
"type": "object",

0 commit comments

Comments
 (0)