code.gitea.io/gitea@v1.21.7/routers/web/repo/setting/setting.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package setting
     6  
     7  import (
     8  	"fmt"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"code.gitea.io/gitea/models"
    15  	"code.gitea.io/gitea/models/db"
    16  	"code.gitea.io/gitea/models/organization"
    17  	repo_model "code.gitea.io/gitea/models/repo"
    18  	unit_model "code.gitea.io/gitea/models/unit"
    19  	user_model "code.gitea.io/gitea/models/user"
    20  	"code.gitea.io/gitea/modules/base"
    21  	"code.gitea.io/gitea/modules/context"
    22  	"code.gitea.io/gitea/modules/git"
    23  	"code.gitea.io/gitea/modules/indexer/code"
    24  	"code.gitea.io/gitea/modules/indexer/stats"
    25  	"code.gitea.io/gitea/modules/lfs"
    26  	"code.gitea.io/gitea/modules/log"
    27  	repo_module "code.gitea.io/gitea/modules/repository"
    28  	"code.gitea.io/gitea/modules/setting"
    29  	"code.gitea.io/gitea/modules/structs"
    30  	"code.gitea.io/gitea/modules/util"
    31  	"code.gitea.io/gitea/modules/validation"
    32  	"code.gitea.io/gitea/modules/web"
    33  	asymkey_service "code.gitea.io/gitea/services/asymkey"
    34  	"code.gitea.io/gitea/services/forms"
    35  	"code.gitea.io/gitea/services/migrations"
    36  	mirror_service "code.gitea.io/gitea/services/mirror"
    37  	repo_service "code.gitea.io/gitea/services/repository"
    38  	wiki_service "code.gitea.io/gitea/services/wiki"
    39  )
    40  
    41  const (
    42  	tplSettingsOptions base.TplName = "repo/settings/options"
    43  	tplCollaboration   base.TplName = "repo/settings/collaboration"
    44  	tplBranches        base.TplName = "repo/settings/branches"
    45  	tplGithooks        base.TplName = "repo/settings/githooks"
    46  	tplGithookEdit     base.TplName = "repo/settings/githook_edit"
    47  	tplDeployKeys      base.TplName = "repo/settings/deploy_keys"
    48  )
    49  
    50  // SettingsCtxData is a middleware that sets all the general context data for the
    51  // settings template.
    52  func SettingsCtxData(ctx *context.Context) {
    53  	ctx.Data["Title"] = ctx.Tr("repo.settings.options")
    54  	ctx.Data["PageIsSettingsOptions"] = true
    55  	ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
    56  	ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
    57  	ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
    58  	ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush
    59  	ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval
    60  	ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval
    61  
    62  	signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath())
    63  	ctx.Data["SigningKeyAvailable"] = len(signing) > 0
    64  	ctx.Data["SigningSettings"] = setting.Repository.Signing
    65  	ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
    66  
    67  	if ctx.Doer.IsAdmin {
    68  		if setting.Indexer.RepoIndexerEnabled {
    69  			status, err := repo_model.GetIndexerStatus(ctx, ctx.Repo.Repository, repo_model.RepoIndexerTypeCode)
    70  			if err != nil {
    71  				ctx.ServerError("repo.indexer_status", err)
    72  				return
    73  			}
    74  			ctx.Data["CodeIndexerStatus"] = status
    75  		}
    76  		status, err := repo_model.GetIndexerStatus(ctx, ctx.Repo.Repository, repo_model.RepoIndexerTypeStats)
    77  		if err != nil {
    78  			ctx.ServerError("repo.indexer_status", err)
    79  			return
    80  		}
    81  		ctx.Data["StatsIndexerStatus"] = status
    82  	}
    83  	pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
    84  	if err != nil {
    85  		ctx.ServerError("GetPushMirrorsByRepoID", err)
    86  		return
    87  	}
    88  	ctx.Data["PushMirrors"] = pushMirrors
    89  }
    90  
    91  // Settings show a repository's settings page
    92  func Settings(ctx *context.Context) {
    93  	ctx.HTML(http.StatusOK, tplSettingsOptions)
    94  }
    95  
    96  // SettingsPost response for changes of a repository
    97  func SettingsPost(ctx *context.Context) {
    98  	form := web.GetForm(ctx).(*forms.RepoSettingForm)
    99  
   100  	ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
   101  	ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
   102  	ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
   103  	ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush
   104  	ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval
   105  	ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval
   106  
   107  	signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath())
   108  	ctx.Data["SigningKeyAvailable"] = len(signing) > 0
   109  	ctx.Data["SigningSettings"] = setting.Repository.Signing
   110  	ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
   111  
   112  	repo := ctx.Repo.Repository
   113  
   114  	switch ctx.FormString("action") {
   115  	case "update":
   116  		if ctx.HasError() {
   117  			ctx.HTML(http.StatusOK, tplSettingsOptions)
   118  			return
   119  		}
   120  
   121  		newRepoName := form.RepoName
   122  		// Check if repository name has been changed.
   123  		if repo.LowerName != strings.ToLower(newRepoName) {
   124  			// Close the GitRepo if open
   125  			if ctx.Repo.GitRepo != nil {
   126  				ctx.Repo.GitRepo.Close()
   127  				ctx.Repo.GitRepo = nil
   128  			}
   129  			if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil {
   130  				ctx.Data["Err_RepoName"] = true
   131  				switch {
   132  				case repo_model.IsErrRepoAlreadyExist(err):
   133  					ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form)
   134  				case db.IsErrNameReserved(err):
   135  					ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
   136  				case repo_model.IsErrRepoFilesAlreadyExist(err):
   137  					ctx.Data["Err_RepoName"] = true
   138  					switch {
   139  					case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
   140  						ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form)
   141  					case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
   142  						ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form)
   143  					case setting.Repository.AllowDeleteOfUnadoptedRepositories:
   144  						ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form)
   145  					default:
   146  						ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form)
   147  					}
   148  				case db.IsErrNamePatternNotAllowed(err):
   149  					ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
   150  				default:
   151  					ctx.ServerError("ChangeRepositoryName", err)
   152  				}
   153  				return
   154  			}
   155  
   156  			log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
   157  		}
   158  		// In case it's just a case change.
   159  		repo.Name = newRepoName
   160  		repo.LowerName = strings.ToLower(newRepoName)
   161  		repo.Description = form.Description
   162  		repo.Website = form.Website
   163  		repo.IsTemplate = form.Template
   164  
   165  		// Visibility of forked repository is forced sync with base repository.
   166  		if repo.IsFork {
   167  			form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate
   168  		}
   169  
   170  		visibilityChanged := repo.IsPrivate != form.Private
   171  		// when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
   172  		if visibilityChanged && setting.Repository.ForcePrivate && !form.Private && !ctx.Doer.IsAdmin {
   173  			ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form)
   174  			return
   175  		}
   176  
   177  		repo.IsPrivate = form.Private
   178  		if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {
   179  			ctx.ServerError("UpdateRepository", err)
   180  			return
   181  		}
   182  		log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   183  
   184  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   185  		ctx.Redirect(repo.Link() + "/settings")
   186  
   187  	case "mirror":
   188  		if !setting.Mirror.Enabled || !repo.IsMirror {
   189  			ctx.NotFound("", nil)
   190  			return
   191  		}
   192  
   193  		pullMirror, err := repo_model.GetMirrorByRepoID(ctx, ctx.Repo.Repository.ID)
   194  		if err == repo_model.ErrMirrorNotExist {
   195  			ctx.NotFound("", nil)
   196  			return
   197  		}
   198  		if err != nil {
   199  			ctx.ServerError("GetMirrorByRepoID", err)
   200  			return
   201  		}
   202  		// This section doesn't require repo_name/RepoName to be set in the form, don't show it
   203  		// as an error on the UI for this action
   204  		ctx.Data["Err_RepoName"] = nil
   205  
   206  		interval, err := time.ParseDuration(form.Interval)
   207  		if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
   208  			ctx.Data["Err_Interval"] = true
   209  			ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
   210  			return
   211  		}
   212  
   213  		pullMirror.EnablePrune = form.EnablePrune
   214  		pullMirror.Interval = interval
   215  		pullMirror.ScheduleNextUpdate()
   216  		if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil {
   217  			ctx.ServerError("UpdateMirror", err)
   218  			return
   219  		}
   220  
   221  		u, err := git.GetRemoteURL(ctx, ctx.Repo.Repository.RepoPath(), pullMirror.GetRemoteName())
   222  		if err != nil {
   223  			ctx.Data["Err_MirrorAddress"] = true
   224  			handleSettingRemoteAddrError(ctx, err, form)
   225  			return
   226  		}
   227  		if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() {
   228  			form.MirrorPassword, _ = u.User.Password()
   229  		}
   230  
   231  		address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword)
   232  		if err == nil {
   233  			err = migrations.IsMigrateURLAllowed(address, ctx.Doer)
   234  		}
   235  		if err != nil {
   236  			ctx.Data["Err_MirrorAddress"] = true
   237  			handleSettingRemoteAddrError(ctx, err, form)
   238  			return
   239  		}
   240  
   241  		if err := mirror_service.UpdateAddress(ctx, pullMirror, address); err != nil {
   242  			ctx.ServerError("UpdateAddress", err)
   243  			return
   244  		}
   245  
   246  		remoteAddress, err := util.SanitizeURL(form.MirrorAddress)
   247  		if err != nil {
   248  			ctx.ServerError("SanitizeURL", err)
   249  			return
   250  		}
   251  		pullMirror.RemoteAddress = remoteAddress
   252  
   253  		form.LFS = form.LFS && setting.LFS.StartServer
   254  
   255  		if len(form.LFSEndpoint) > 0 {
   256  			ep := lfs.DetermineEndpoint("", form.LFSEndpoint)
   257  			if ep == nil {
   258  				ctx.Data["Err_LFSEndpoint"] = true
   259  				ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form)
   260  				return
   261  			}
   262  			err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer)
   263  			if err != nil {
   264  				ctx.Data["Err_LFSEndpoint"] = true
   265  				handleSettingRemoteAddrError(ctx, err, form)
   266  				return
   267  			}
   268  		}
   269  
   270  		pullMirror.LFS = form.LFS
   271  		pullMirror.LFSEndpoint = form.LFSEndpoint
   272  		if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil {
   273  			ctx.ServerError("UpdateMirror", err)
   274  			return
   275  		}
   276  
   277  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   278  		ctx.Redirect(repo.Link() + "/settings")
   279  
   280  	case "mirror-sync":
   281  		if !setting.Mirror.Enabled || !repo.IsMirror {
   282  			ctx.NotFound("", nil)
   283  			return
   284  		}
   285  
   286  		mirror_service.AddPullMirrorToQueue(repo.ID)
   287  
   288  		ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress"))
   289  		ctx.Redirect(repo.Link() + "/settings")
   290  
   291  	case "push-mirror-sync":
   292  		if !setting.Mirror.Enabled {
   293  			ctx.NotFound("", nil)
   294  			return
   295  		}
   296  
   297  		m, err := selectPushMirrorByForm(ctx, form, repo)
   298  		if err != nil {
   299  			ctx.NotFound("", nil)
   300  			return
   301  		}
   302  
   303  		mirror_service.AddPushMirrorToQueue(m.ID)
   304  
   305  		ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress"))
   306  		ctx.Redirect(repo.Link() + "/settings")
   307  
   308  	case "push-mirror-update":
   309  		if !setting.Mirror.Enabled {
   310  			ctx.NotFound("", nil)
   311  			return
   312  		}
   313  
   314  		// This section doesn't require repo_name/RepoName to be set in the form, don't show it
   315  		// as an error on the UI for this action
   316  		ctx.Data["Err_RepoName"] = nil
   317  
   318  		interval, err := time.ParseDuration(form.PushMirrorInterval)
   319  		if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
   320  			ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{})
   321  			return
   322  		}
   323  
   324  		id, err := strconv.ParseInt(form.PushMirrorID, 10, 64)
   325  		if err != nil {
   326  			ctx.ServerError("UpdatePushMirrorIntervalPushMirrorID", err)
   327  			return
   328  		}
   329  		m := &repo_model.PushMirror{
   330  			ID:       id,
   331  			Interval: interval,
   332  		}
   333  		if err := repo_model.UpdatePushMirrorInterval(ctx, m); err != nil {
   334  			ctx.ServerError("UpdatePushMirrorInterval", err)
   335  			return
   336  		}
   337  		// Background why we are adding it to Queue
   338  		// If we observed its implementation in the context of `push-mirror-sync` where it
   339  		// is evident that pushing to the queue is necessary for updates.
   340  		// So, there are updates within the given interval, it is necessary to update the queue accordingly.
   341  		mirror_service.AddPushMirrorToQueue(m.ID)
   342  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   343  		ctx.Redirect(repo.Link() + "/settings")
   344  
   345  	case "push-mirror-remove":
   346  		if !setting.Mirror.Enabled {
   347  			ctx.NotFound("", nil)
   348  			return
   349  		}
   350  
   351  		// This section doesn't require repo_name/RepoName to be set in the form, don't show it
   352  		// as an error on the UI for this action
   353  		ctx.Data["Err_RepoName"] = nil
   354  
   355  		m, err := selectPushMirrorByForm(ctx, form, repo)
   356  		if err != nil {
   357  			ctx.NotFound("", nil)
   358  			return
   359  		}
   360  
   361  		if err = mirror_service.RemovePushMirrorRemote(ctx, m); err != nil {
   362  			ctx.ServerError("RemovePushMirrorRemote", err)
   363  			return
   364  		}
   365  
   366  		if err = repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil {
   367  			ctx.ServerError("DeletePushMirrorByID", err)
   368  			return
   369  		}
   370  
   371  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   372  		ctx.Redirect(repo.Link() + "/settings")
   373  
   374  	case "push-mirror-add":
   375  		if setting.Mirror.DisableNewPush {
   376  			ctx.NotFound("", nil)
   377  			return
   378  		}
   379  
   380  		// This section doesn't require repo_name/RepoName to be set in the form, don't show it
   381  		// as an error on the UI for this action
   382  		ctx.Data["Err_RepoName"] = nil
   383  
   384  		interval, err := time.ParseDuration(form.PushMirrorInterval)
   385  		if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
   386  			ctx.Data["Err_PushMirrorInterval"] = true
   387  			ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
   388  			return
   389  		}
   390  
   391  		address, err := forms.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword)
   392  		if err == nil {
   393  			err = migrations.IsMigrateURLAllowed(address, ctx.Doer)
   394  		}
   395  		if err != nil {
   396  			ctx.Data["Err_PushMirrorAddress"] = true
   397  			handleSettingRemoteAddrError(ctx, err, form)
   398  			return
   399  		}
   400  
   401  		remoteSuffix, err := util.CryptoRandomString(10)
   402  		if err != nil {
   403  			ctx.ServerError("RandomString", err)
   404  			return
   405  		}
   406  
   407  		remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress)
   408  		if err != nil {
   409  			ctx.ServerError("SanitizeURL", err)
   410  			return
   411  		}
   412  
   413  		m := &repo_model.PushMirror{
   414  			RepoID:        repo.ID,
   415  			Repo:          repo,
   416  			RemoteName:    fmt.Sprintf("remote_mirror_%s", remoteSuffix),
   417  			SyncOnCommit:  form.PushMirrorSyncOnCommit,
   418  			Interval:      interval,
   419  			RemoteAddress: remoteAddress,
   420  		}
   421  		if err := repo_model.InsertPushMirror(ctx, m); err != nil {
   422  			ctx.ServerError("InsertPushMirror", err)
   423  			return
   424  		}
   425  
   426  		if err := mirror_service.AddPushMirrorRemote(ctx, m, address); err != nil {
   427  			if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil {
   428  				log.Error("DeletePushMirrors %v", err)
   429  			}
   430  			ctx.ServerError("AddPushMirrorRemote", err)
   431  			return
   432  		}
   433  
   434  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   435  		ctx.Redirect(repo.Link() + "/settings")
   436  
   437  	case "advanced":
   438  		var repoChanged bool
   439  		var units []repo_model.RepoUnit
   440  		var deleteUnitTypes []unit_model.Type
   441  
   442  		// This section doesn't require repo_name/RepoName to be set in the form, don't show it
   443  		// as an error on the UI for this action
   444  		ctx.Data["Err_RepoName"] = nil
   445  
   446  		if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch {
   447  			repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch
   448  			repoChanged = true
   449  		}
   450  
   451  		if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() {
   452  			units = append(units, repo_model.RepoUnit{
   453  				RepoID: repo.ID,
   454  				Type:   unit_model.TypeCode,
   455  			})
   456  		} else if !unit_model.TypeCode.UnitGlobalDisabled() {
   457  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode)
   458  		}
   459  
   460  		if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
   461  			if !validation.IsValidExternalURL(form.ExternalWikiURL) {
   462  				ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error"))
   463  				ctx.Redirect(repo.Link() + "/settings")
   464  				return
   465  			}
   466  
   467  			units = append(units, repo_model.RepoUnit{
   468  				RepoID: repo.ID,
   469  				Type:   unit_model.TypeExternalWiki,
   470  				Config: &repo_model.ExternalWikiConfig{
   471  					ExternalWikiURL: form.ExternalWikiURL,
   472  				},
   473  			})
   474  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
   475  		} else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
   476  			units = append(units, repo_model.RepoUnit{
   477  				RepoID: repo.ID,
   478  				Type:   unit_model.TypeWiki,
   479  				Config: new(repo_model.UnitConfig),
   480  			})
   481  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
   482  		} else {
   483  			if !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
   484  				deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
   485  			}
   486  			if !unit_model.TypeWiki.UnitGlobalDisabled() {
   487  				deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
   488  			}
   489  		}
   490  
   491  		if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
   492  			if !validation.IsValidExternalURL(form.ExternalTrackerURL) {
   493  				ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error"))
   494  				ctx.Redirect(repo.Link() + "/settings")
   495  				return
   496  			}
   497  			if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) {
   498  				ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error"))
   499  				ctx.Redirect(repo.Link() + "/settings")
   500  				return
   501  			}
   502  			units = append(units, repo_model.RepoUnit{
   503  				RepoID: repo.ID,
   504  				Type:   unit_model.TypeExternalTracker,
   505  				Config: &repo_model.ExternalTrackerConfig{
   506  					ExternalTrackerURL:           form.ExternalTrackerURL,
   507  					ExternalTrackerFormat:        form.TrackerURLFormat,
   508  					ExternalTrackerStyle:         form.TrackerIssueStyle,
   509  					ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
   510  				},
   511  			})
   512  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
   513  		} else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() {
   514  			units = append(units, repo_model.RepoUnit{
   515  				RepoID: repo.ID,
   516  				Type:   unit_model.TypeIssues,
   517  				Config: &repo_model.IssuesConfig{
   518  					EnableTimetracker:                form.EnableTimetracker,
   519  					AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
   520  					EnableDependencies:               form.EnableIssueDependencies,
   521  				},
   522  			})
   523  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
   524  		} else {
   525  			if !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
   526  				deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
   527  			}
   528  			if !unit_model.TypeIssues.UnitGlobalDisabled() {
   529  				deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
   530  			}
   531  		}
   532  
   533  		if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
   534  			units = append(units, repo_model.RepoUnit{
   535  				RepoID: repo.ID,
   536  				Type:   unit_model.TypeProjects,
   537  			})
   538  		} else if !unit_model.TypeProjects.UnitGlobalDisabled() {
   539  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
   540  		}
   541  
   542  		if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() {
   543  			units = append(units, repo_model.RepoUnit{
   544  				RepoID: repo.ID,
   545  				Type:   unit_model.TypeReleases,
   546  			})
   547  		} else if !unit_model.TypeReleases.UnitGlobalDisabled() {
   548  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases)
   549  		}
   550  
   551  		if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() {
   552  			units = append(units, repo_model.RepoUnit{
   553  				RepoID: repo.ID,
   554  				Type:   unit_model.TypePackages,
   555  			})
   556  		} else if !unit_model.TypePackages.UnitGlobalDisabled() {
   557  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
   558  		}
   559  
   560  		if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
   561  			units = append(units, repo_model.RepoUnit{
   562  				RepoID: repo.ID,
   563  				Type:   unit_model.TypeActions,
   564  			})
   565  		} else if !unit_model.TypeActions.UnitGlobalDisabled() {
   566  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
   567  		}
   568  
   569  		if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
   570  			units = append(units, repo_model.RepoUnit{
   571  				RepoID: repo.ID,
   572  				Type:   unit_model.TypePullRequests,
   573  				Config: &repo_model.PullRequestsConfig{
   574  					IgnoreWhitespaceConflicts:     form.PullsIgnoreWhitespace,
   575  					AllowMerge:                    form.PullsAllowMerge,
   576  					AllowRebase:                   form.PullsAllowRebase,
   577  					AllowRebaseMerge:              form.PullsAllowRebaseMerge,
   578  					AllowSquash:                   form.PullsAllowSquash,
   579  					AllowManualMerge:              form.PullsAllowManualMerge,
   580  					AutodetectManualMerge:         form.EnableAutodetectManualMerge,
   581  					AllowRebaseUpdate:             form.PullsAllowRebaseUpdate,
   582  					DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
   583  					DefaultMergeStyle:             repo_model.MergeStyle(form.PullsDefaultMergeStyle),
   584  					DefaultAllowMaintainerEdit:    form.DefaultAllowMaintainerEdit,
   585  				},
   586  			})
   587  		} else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
   588  			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
   589  		}
   590  
   591  		if len(units) == 0 {
   592  			ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit"))
   593  			ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   594  			return
   595  		}
   596  
   597  		if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
   598  			ctx.ServerError("UpdateRepositoryUnits", err)
   599  			return
   600  		}
   601  		if repoChanged {
   602  			if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
   603  				ctx.ServerError("UpdateRepository", err)
   604  				return
   605  			}
   606  		}
   607  		log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   608  
   609  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   610  		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   611  
   612  	case "signing":
   613  		changed := false
   614  		trustModel := repo_model.ToTrustModel(form.TrustModel)
   615  		if trustModel != repo.TrustModel {
   616  			repo.TrustModel = trustModel
   617  			changed = true
   618  		}
   619  
   620  		if changed {
   621  			if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
   622  				ctx.ServerError("UpdateRepository", err)
   623  				return
   624  			}
   625  		}
   626  		log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   627  
   628  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   629  		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   630  
   631  	case "admin":
   632  		if !ctx.Doer.IsAdmin {
   633  			ctx.Error(http.StatusForbidden)
   634  			return
   635  		}
   636  
   637  		if repo.IsFsckEnabled != form.EnableHealthCheck {
   638  			repo.IsFsckEnabled = form.EnableHealthCheck
   639  		}
   640  
   641  		if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
   642  			ctx.ServerError("UpdateRepository", err)
   643  			return
   644  		}
   645  
   646  		log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   647  
   648  		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
   649  		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   650  
   651  	case "admin_index":
   652  		if !ctx.Doer.IsAdmin {
   653  			ctx.Error(http.StatusForbidden)
   654  			return
   655  		}
   656  
   657  		switch form.RequestReindexType {
   658  		case "stats":
   659  			if err := stats.UpdateRepoIndexer(ctx.Repo.Repository); err != nil {
   660  				ctx.ServerError("UpdateStatsRepondexer", err)
   661  				return
   662  			}
   663  		case "code":
   664  			if !setting.Indexer.RepoIndexerEnabled {
   665  				ctx.Error(http.StatusForbidden)
   666  				return
   667  			}
   668  			code.UpdateRepoIndexer(ctx.Repo.Repository)
   669  		default:
   670  			ctx.NotFound("", nil)
   671  			return
   672  		}
   673  
   674  		log.Trace("Repository reindex for %s requested: %s/%s", form.RequestReindexType, ctx.Repo.Owner.Name, repo.Name)
   675  
   676  		ctx.Flash.Success(ctx.Tr("repo.settings.reindex_requested"))
   677  		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   678  
   679  	case "convert":
   680  		if !ctx.Repo.IsOwner() {
   681  			ctx.Error(http.StatusNotFound)
   682  			return
   683  		}
   684  		if repo.Name != form.RepoName {
   685  			ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
   686  			return
   687  		}
   688  
   689  		if !repo.IsMirror {
   690  			ctx.Error(http.StatusNotFound)
   691  			return
   692  		}
   693  		repo.IsMirror = false
   694  
   695  		if _, err := repo_module.CleanUpMigrateInfo(ctx, repo); err != nil {
   696  			ctx.ServerError("CleanUpMigrateInfo", err)
   697  			return
   698  		} else if err = repo_model.DeleteMirrorByRepoID(ctx, ctx.Repo.Repository.ID); err != nil {
   699  			ctx.ServerError("DeleteMirrorByRepoID", err)
   700  			return
   701  		}
   702  		log.Trace("Repository converted from mirror to regular: %s", repo.FullName())
   703  		ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed"))
   704  		ctx.Redirect(repo.Link())
   705  
   706  	case "convert_fork":
   707  		if !ctx.Repo.IsOwner() {
   708  			ctx.Error(http.StatusNotFound)
   709  			return
   710  		}
   711  		if err := repo.LoadOwner(ctx); err != nil {
   712  			ctx.ServerError("Convert Fork", err)
   713  			return
   714  		}
   715  		if repo.Name != form.RepoName {
   716  			ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
   717  			return
   718  		}
   719  
   720  		if !repo.IsFork {
   721  			ctx.Error(http.StatusNotFound)
   722  			return
   723  		}
   724  
   725  		if !ctx.Repo.Owner.CanCreateRepo() {
   726  			maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit()
   727  			msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
   728  			ctx.Flash.Error(msg)
   729  			ctx.Redirect(repo.Link() + "/settings")
   730  			return
   731  		}
   732  
   733  		if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil {
   734  			log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err)
   735  			ctx.ServerError("Convert Fork", err)
   736  			return
   737  		}
   738  
   739  		log.Trace("Repository converted from fork to regular: %s", repo.FullName())
   740  		ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed"))
   741  		ctx.Redirect(repo.Link())
   742  
   743  	case "transfer":
   744  		if !ctx.Repo.IsOwner() {
   745  			ctx.Error(http.StatusNotFound)
   746  			return
   747  		}
   748  		if repo.Name != form.RepoName {
   749  			ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
   750  			return
   751  		}
   752  
   753  		newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name"))
   754  		if err != nil {
   755  			if user_model.IsErrUserNotExist(err) {
   756  				ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
   757  				return
   758  			}
   759  			ctx.ServerError("IsUserExist", err)
   760  			return
   761  		}
   762  
   763  		if newOwner.Type == user_model.UserTypeOrganization {
   764  			if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) {
   765  				// The user shouldn't know about this organization
   766  				ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
   767  				return
   768  			}
   769  		}
   770  
   771  		// Close the GitRepo if open
   772  		if ctx.Repo.GitRepo != nil {
   773  			ctx.Repo.GitRepo.Close()
   774  			ctx.Repo.GitRepo = nil
   775  		}
   776  
   777  		if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil {
   778  			if repo_model.IsErrRepoAlreadyExist(err) {
   779  				ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
   780  			} else if models.IsErrRepoTransferInProgress(err) {
   781  				ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil)
   782  			} else {
   783  				ctx.ServerError("TransferOwnership", err)
   784  			}
   785  
   786  			return
   787  		}
   788  
   789  		log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner)
   790  		ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName()))
   791  		ctx.Redirect(repo.Link() + "/settings")
   792  
   793  	case "cancel_transfer":
   794  		if !ctx.Repo.IsOwner() {
   795  			ctx.Error(http.StatusNotFound)
   796  			return
   797  		}
   798  
   799  		repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
   800  		if err != nil {
   801  			if models.IsErrNoPendingTransfer(err) {
   802  				ctx.Flash.Error("repo.settings.transfer_abort_invalid")
   803  				ctx.Redirect(repo.Link() + "/settings")
   804  			} else {
   805  				ctx.ServerError("GetPendingRepositoryTransfer", err)
   806  			}
   807  
   808  			return
   809  		}
   810  
   811  		if err := repoTransfer.LoadAttributes(ctx); err != nil {
   812  			ctx.ServerError("LoadRecipient", err)
   813  			return
   814  		}
   815  
   816  		if err := models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
   817  			ctx.ServerError("CancelRepositoryTransfer", err)
   818  			return
   819  		}
   820  
   821  		log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name)
   822  		ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name))
   823  		ctx.Redirect(repo.Link() + "/settings")
   824  
   825  	case "delete":
   826  		if !ctx.Repo.IsOwner() {
   827  			ctx.Error(http.StatusNotFound)
   828  			return
   829  		}
   830  		if repo.Name != form.RepoName {
   831  			ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
   832  			return
   833  		}
   834  
   835  		// Close the gitrepository before doing this.
   836  		if ctx.Repo.GitRepo != nil {
   837  			ctx.Repo.GitRepo.Close()
   838  		}
   839  
   840  		if err := repo_service.DeleteRepository(ctx, ctx.Doer, ctx.Repo.Repository, true); err != nil {
   841  			ctx.ServerError("DeleteRepository", err)
   842  			return
   843  		}
   844  		log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   845  
   846  		ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
   847  		ctx.Redirect(ctx.Repo.Owner.DashboardLink())
   848  
   849  	case "delete-wiki":
   850  		if !ctx.Repo.IsOwner() {
   851  			ctx.Error(http.StatusNotFound)
   852  			return
   853  		}
   854  		if repo.Name != form.RepoName {
   855  			ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
   856  			return
   857  		}
   858  
   859  		err := wiki_service.DeleteWiki(ctx, repo)
   860  		if err != nil {
   861  			log.Error("Delete Wiki: %v", err.Error())
   862  		}
   863  		log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   864  
   865  		ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
   866  		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   867  
   868  	case "archive":
   869  		if !ctx.Repo.IsOwner() {
   870  			ctx.Error(http.StatusForbidden)
   871  			return
   872  		}
   873  
   874  		if repo.IsMirror {
   875  			ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror"))
   876  			ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   877  			return
   878  		}
   879  
   880  		if err := repo_model.SetArchiveRepoState(ctx, repo, true); err != nil {
   881  			log.Error("Tried to archive a repo: %s", err)
   882  			ctx.Flash.Error(ctx.Tr("repo.settings.archive.error"))
   883  			ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   884  			return
   885  		}
   886  
   887  		ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
   888  
   889  		log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   890  		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   891  
   892  	case "unarchive":
   893  		if !ctx.Repo.IsOwner() {
   894  			ctx.Error(http.StatusForbidden)
   895  			return
   896  		}
   897  
   898  		if err := repo_model.SetArchiveRepoState(ctx, repo, false); err != nil {
   899  			log.Error("Tried to unarchive a repo: %s", err)
   900  			ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error"))
   901  			ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   902  			return
   903  		}
   904  
   905  		ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
   906  
   907  		log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
   908  		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
   909  
   910  	default:
   911  		ctx.NotFound("", nil)
   912  	}
   913  }
   914  
   915  func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) {
   916  	if models.IsErrInvalidCloneAddr(err) {
   917  		addrErr := err.(*models.ErrInvalidCloneAddr)
   918  		switch {
   919  		case addrErr.IsProtocolInvalid:
   920  			ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form)
   921  		case addrErr.IsURLError:
   922  			ctx.RenderWithErr(ctx.Tr("form.url_error", addrErr.Host), tplSettingsOptions, form)
   923  		case addrErr.IsPermissionDenied:
   924  			if addrErr.LocalPath {
   925  				ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplSettingsOptions, form)
   926  			} else {
   927  				ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tplSettingsOptions, form)
   928  			}
   929  		case addrErr.IsInvalidPath:
   930  			ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplSettingsOptions, form)
   931  		default:
   932  			ctx.ServerError("Unknown error", err)
   933  		}
   934  		return
   935  	}
   936  	ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form)
   937  }
   938  
   939  func selectPushMirrorByForm(ctx *context.Context, form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) {
   940  	id, err := strconv.ParseInt(form.PushMirrorID, 10, 64)
   941  	if err != nil {
   942  		return nil, err
   943  	}
   944  
   945  	pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{})
   946  	if err != nil {
   947  		return nil, err
   948  	}
   949  
   950  	for _, m := range pushMirrors {
   951  		if m.ID == id {
   952  			m.Repo = repo
   953  			return m, nil
   954  		}
   955  	}
   956  
   957  	return nil, fmt.Errorf("PushMirror[%v] not associated to repository %v", id, repo)
   958  }