diff options
author | Richard Mahn <richmahn@users.noreply.github.com> | 2019-05-30 11:09:05 -0400 |
---|---|---|
committer | techknowlogick <hello@techknowlogick.com> | 2019-05-30 11:09:05 -0400 |
commit | 1831b3b57144e87ccfc4f6322eefc88a49b2300e (patch) | |
tree | 225fa569e3908c153683009d0d75c42f9dacc956 /routers/api/v1/repo/repo.go | |
parent | cdd10f145be0b5e9b94c19f1303dc01c6e9c8c29 (diff) | |
download | gitea-1831b3b57144e87ccfc4f6322eefc88a49b2300e.tar.gz gitea-1831b3b57144e87ccfc4f6322eefc88a49b2300e.zip |
Fixes #5960 - Adds API Endpoint for Repo Edit (#7006)
* Feature - #5960 - API Endpoint for Repo Editing
* Revert from merge
* Adds integration testing
* Updates to integration tests
* Revert changes
* Update year in file header
* Misspell fix
* XORM = test
* XORM = test
* revert XORM = file
* Makes RepoUnit.ID be pk and autoincr
* Fix to units
* revert header
* Remove print statement
* Adds other responses
* Improves swagger for creating repo
* Fixes import order
* Better Unit Type does not exist error
* Adds editable repo properties to the response repo structure
* Fix to api_repo_edit_test.go
* Fixes repo test
* Changes per review
* Fixes typo and standardizes comments in the EditRepoOption struct for swagger
* Fixes typo and standardizes comments in the EditRepoOption struct for swagger
* Actually can unarchive through the API
* Unlike delete, user doesn't have to be the owner of the org, just admin to the repo
* Fix to swagger comments for field name change
* Update to swagger docs
* Update swagger
* Changes allow_pull_requests to has_pull_requests
Diffstat (limited to 'routers/api/v1/repo/repo.go')
-rw-r--r-- | routers/api/v1/repo/repo.go | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 62153893a6..f8df3e9fa1 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -240,6 +240,10 @@ func Create(ctx *context.APIContext, opt api.CreateRepoOption) { // responses: // "201": // "$ref": "#/responses/Repository" + // "409": + // description: The repository with the same name already exists. + // "422": + // "$ref": "#/responses/validationError" if ctx.User.IsOrganization() { // Shouldn't reach this condition, but just in case. ctx.Error(422, "", "not allowed creating repository for organization") @@ -500,6 +504,280 @@ func GetByID(ctx *context.APIContext) { ctx.JSON(200, repo.APIFormat(perm.AccessMode)) } +// Edit edit repository properties +func Edit(ctx *context.APIContext, opts api.EditRepoOption) { + // swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit + // --- + // summary: Edit a repository's properties. Only fields that are set will be changed. + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo to edit + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo to edit + // type: string + // required: true + // required: true + // - name: body + // in: body + // description: "Properties of a repo that you can edit" + // schema: + // "$ref": "#/definitions/EditRepoOption" + // responses: + // "200": + // "$ref": "#/responses/Repository" + // "403": + // "$ref": "#/responses/forbidden" + // "422": + // "$ref": "#/responses/validationError" + if err := updateBasicProperties(ctx, opts); err != nil { + return + } + + if err := updateRepoUnits(ctx, opts); err != nil { + return + } + + if opts.Archived != nil { + if err := updateRepoArchivedState(ctx, opts); err != nil { + return + } + } + + ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode)) +} + +// updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility +func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error { + owner := ctx.Repo.Owner + repo := ctx.Repo.Repository + + oldRepoName := repo.Name + newRepoName := repo.Name + if opts.Name != nil { + newRepoName = *opts.Name + } + // Check if repository name has been changed and not just a case change + if repo.LowerName != strings.ToLower(newRepoName) { + if err := models.ChangeRepositoryName(ctx.Repo.Owner, repo.Name, newRepoName); err != nil { + switch { + case models.IsErrRepoAlreadyExist(err): + ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err) + case models.IsErrNameReserved(err): + ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err) + case models.IsErrNamePatternNotAllowed(err): + ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(models.ErrNamePatternNotAllowed).Pattern), err) + default: + ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err) + } + return err + } + + err := models.NewRepoRedirect(ctx.Repo.Owner.ID, repo.ID, repo.Name, newRepoName) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, "NewRepoRedirect", err) + return err + } + + if err := models.RenameRepoAction(ctx.User, oldRepoName, repo); err != nil { + log.Error("RenameRepoAction: %v", err) + ctx.Error(http.StatusInternalServerError, "RenameRepoActions", err) + return err + } + + log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName) + } + // Update the name in the repo object for the response + repo.Name = newRepoName + repo.LowerName = strings.ToLower(newRepoName) + + if opts.Description != nil { + repo.Description = *opts.Description + } + + if opts.Website != nil { + repo.Website = *opts.Website + } + + visibilityChanged := false + if opts.Private != nil { + // Visibility of forked repository is forced sync with base repository. + if repo.IsFork { + *opts.Private = repo.BaseRepo.IsPrivate + } + + visibilityChanged = repo.IsPrivate != *opts.Private + // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public + if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.User.IsAdmin { + err := fmt.Errorf("cannot change private repository to public") + ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err) + return err + } + + repo.IsPrivate = *opts.Private + } + + if err := models.UpdateRepository(repo, visibilityChanged); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateRepository", err) + return err + } + + log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name) + return nil +} + +func unitTypeInTypes(unitType models.UnitType, unitTypes []models.UnitType) bool { + for _, tp := range unitTypes { + if unitType == tp { + return true + } + } + return false +} + +// updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings +func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { + owner := ctx.Repo.Owner + repo := ctx.Repo.Repository + + var units []models.RepoUnit + + for _, tp := range models.MustRepoUnits { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: new(models.UnitConfig), + }) + } + + if opts.HasIssues != nil { + if *opts.HasIssues { + // We don't currently allow setting individual issue settings through the API, + // only can enable/disable issues, so when enabling issues, + // we either get the existing config which means it was already enabled, + // or create a new config since it doesn't exist. + unit, err := repo.GetUnit(models.UnitTypeIssues) + var config *models.IssuesConfig + if err != nil { + // Unit type doesn't exist so we make a new config file with default values + config = &models.IssuesConfig{ + EnableTimetracker: true, + AllowOnlyContributorsToTrackTime: true, + EnableDependencies: true, + } + } else { + config = unit.IssuesConfig() + } + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypeIssues, + Config: config, + }) + } + } + + if opts.HasWiki != nil { + if *opts.HasWiki { + // We don't currently allow setting individual wiki settings through the API, + // only can enable/disable the wiki, so when enabling the wiki, + // we either get the existing config which means it was already enabled, + // or create a new config since it doesn't exist. + config := &models.UnitConfig{} + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypeWiki, + Config: config, + }) + } + } + + if opts.HasPullRequests != nil { + if *opts.HasPullRequests { + // We do allow setting individual PR settings through the API, so + // we get the config settings and then set them + // if those settings were provided in the opts. + unit, err := repo.GetUnit(models.UnitTypePullRequests) + var config *models.PullRequestsConfig + if err != nil { + // Unit type doesn't exist so we make a new config file with default values + config = &models.PullRequestsConfig{ + IgnoreWhitespaceConflicts: false, + AllowMerge: true, + AllowRebase: true, + AllowRebaseMerge: true, + AllowSquash: true, + } + } else { + config = unit.PullRequestsConfig() + } + + if opts.IgnoreWhitespaceConflicts != nil { + config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts + } + if opts.AllowMerge != nil { + config.AllowMerge = *opts.AllowMerge + } + if opts.AllowRebase != nil { + config.AllowRebase = *opts.AllowRebase + } + if opts.AllowRebaseMerge != nil { + config.AllowRebaseMerge = *opts.AllowRebaseMerge + } + if opts.AllowSquash != nil { + config.AllowSquash = *opts.AllowSquash + } + + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypePullRequests, + Config: config, + }) + } + } + + if err := models.UpdateRepositoryUnits(repo, units); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err) + return err + } + + log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name) + return nil +} + +// updateRepoArchivedState updates repo's archive state +func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error { + repo := ctx.Repo.Repository + // archive / un-archive + if opts.Archived != nil { + if repo.IsMirror { + err := fmt.Errorf("repo is a mirror, cannot archive/un-archive") + ctx.Error(http.StatusUnprocessableEntity, err.Error(), err) + return err + } + if *opts.Archived { + if err := repo.SetArchiveRepoState(*opts.Archived); err != nil { + log.Error("Tried to archive a repo: %s", err) + ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err) + return err + } + log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) + } else { + if err := repo.SetArchiveRepoState(*opts.Archived); err != nil { + log.Error("Tried to un-archive a repo: %s", err) + ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err) + return err + } + log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) + } + } + return nil +} + // Delete one repository func Delete(ctx *context.APIContext) { // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete |