]> source.dussan.org Git - gitea.git/commitdiff
Creating a repo from a template repo via API (#15958)
authora1012112796 <1012112796@qq.com>
Mon, 5 Jul 2021 15:29:08 +0000 (23:29 +0800)
committerGitHub <noreply@github.com>
Mon, 5 Jul 2021 15:29:08 +0000 (17:29 +0200)
* 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>
integrations/api_repo_test.go
modules/structs/repo.go
routers/api/v1/api.go
routers/api/v1/repo/repo.go
routers/api/v1/swagger/options.go
templates/swagger/v1_json.tmpl

index 7052e74b018eaf6e591bf7d46cf7f4488d9dca90..3948489f56fcf3e629f4bf81b20c5ecf2a5e512e 100644 (file)
@@ -495,6 +495,43 @@ func TestAPIRepoTransfer(t *testing.T) {
        _ = 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) {
        defer prepareTestEnv(t)()
        user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
index 4fdc1e54cb282f2e4149a29fa104495972502059..cef864c0205bf29d21a36929c1a920523bbe6076 100644 (file)
@@ -180,6 +180,36 @@ type EditRepoOption struct {
        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
 // swagger:model
 type CreateBranchRepoOption struct {
index b6913ea1bc783d739f41fd67e942a020f66faa2c..b4f14bf2d1e03ef274557a3f5f12444067057ad2 100644 (file)
@@ -722,6 +722,7 @@ func Routes() *web.Route {
                                m.Combo("").Get(reqAnyRepoReader(), repo.Get).
                                        Delete(reqToken(), reqOwner(), repo.Delete).
                                        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.Combo("/notifications").
                                        Get(reqToken(), notify.ListRepoNotifications).
index 35d349051057d80770e87dbec892db3ff7d0291c..5d397191a61387e8bbf6056e91d1bb26c3ff42d9 100644 (file)
@@ -307,6 +307,115 @@ func Create(ctx *context.APIContext) {
        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
 func CreateOrgRepoDeprecated(ctx *context.APIContext) {
        // swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
index b5f34e86a381510430eb9f78e25dd47a1f7f8930..0ae96a9203543893e2a269ba5d98b70b1f3fdcfa 100644 (file)
@@ -87,6 +87,8 @@ type swaggerParameterBodies struct {
        TransferRepoOption api.TransferRepoOption
        // in:body
        CreateForkOption api.CreateForkOption
+       // in:body
+       GenerateRepoOption api.GenerateRepoOption
 
        // in:body
        CreateStatusOption api.CreateStatusOption
index a2e449228e1a61ceeb6f1d25645edb565b0dff0d..669e3552cc5dec46744ab3daf52389e8fa50f316 100644 (file)
         }
       }
     },
+    "/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}": {
       "get": {
         "produces": [
       },
       "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": {
       "description": "GitBlobResponse represents a git blob",
       "type": "object",