mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	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:
		@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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).
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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}": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
@@ -14551,6 +14606,68 @@
 | 
			
		||||
      },
 | 
			
		||||
      "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",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user