summaryrefslogtreecommitdiffstats
path: root/routers/api/v1/repo/repo.go
diff options
context:
space:
mode:
authorRichard Mahn <richmahn@users.noreply.github.com>2019-05-30 11:09:05 -0400
committertechknowlogick <hello@techknowlogick.com>2019-05-30 11:09:05 -0400
commit1831b3b57144e87ccfc4f6322eefc88a49b2300e (patch)
tree225fa569e3908c153683009d0d75c42f9dacc956 /routers/api/v1/repo/repo.go
parentcdd10f145be0b5e9b94c19f1303dc01c6e9c8c29 (diff)
downloadgitea-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.go278
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