diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 572738013f..6e631f98c7 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -378,6 +378,7 @@ func prepareMigrationTasks() []*migration { newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner), newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables), newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard), + newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode), } return preparedMigrations } diff --git a/models/migrations/v1_24/v318.go b/models/migrations/v1_24/v318.go new file mode 100644 index 0000000000..83fb0061d3 --- /dev/null +++ b/models/migrations/v1_24/v318.go @@ -0,0 +1,17 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_24 //nolint + +import ( + "code.gitea.io/gitea/models/perm" + + "xorm.io/xorm" +) + +func AddRepoUnitAnonymousAccessMode(x *xorm.Engine) error { + type RepoUnit struct { //revive:disable-line:exported + AnonymousAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"` + } + return x.Sync(&RepoUnit{}) +} diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 5e7ecb31ea..20ede7fee2 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -25,7 +25,8 @@ type Permission struct { units []*repo_model.RepoUnit unitsMode map[unit.Type]perm_model.AccessMode - everyoneAccessMode map[unit.Type]perm_model.AccessMode + everyoneAccessMode map[unit.Type]perm_model.AccessMode // the unit's minimal access mode for every signed-in user + anonymousAccessMode map[unit.Type]perm_model.AccessMode // the unit's minimal access mode for anonymous (non-signed-in) user } // IsOwner returns true if current user is the owner of repository. @@ -39,7 +40,7 @@ func (p *Permission) IsAdmin() bool { } // HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository. -// It doesn't count the "everyone access mode". +// It doesn't count the "public(anonymous/everyone) access mode". func (p *Permission) HasAnyUnitAccess() bool { for _, v := range p.unitsMode { if v >= perm_model.AccessModeRead { @@ -49,7 +50,12 @@ func (p *Permission) HasAnyUnitAccess() bool { return p.AccessMode >= perm_model.AccessModeRead } -func (p *Permission) HasAnyUnitAccessOrEveryoneAccess() bool { +func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool { + for _, v := range p.anonymousAccessMode { + if v >= perm_model.AccessModeRead { + return true + } + } for _, v := range p.everyoneAccessMode { if v >= perm_model.AccessModeRead { return true @@ -73,14 +79,16 @@ func (p *Permission) GetFirstUnitRepoID() int64 { } // UnitAccessMode returns current user access mode to the specify unit of the repository -// It also considers "everyone access mode" +// It also considers "public (anonymous/everyone) access mode" func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode { // if the units map contains the access mode, use it, but admin/owner mode could override it if m, ok := p.unitsMode[unitType]; ok { return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m) } // if the units map does not contain the access mode, return the default access mode if the unit exists - unitDefaultAccessMode := max(p.AccessMode, p.everyoneAccessMode[unitType]) + unitDefaultAccessMode := p.AccessMode + unitDefaultAccessMode = max(unitDefaultAccessMode, p.anonymousAccessMode[unitType]) + unitDefaultAccessMode = max(unitDefaultAccessMode, p.everyoneAccessMode[unitType]) hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType }) return util.Iif(hasUnit, unitDefaultAccessMode, perm_model.AccessModeNone) } @@ -171,27 +179,38 @@ func (p *Permission) LogString() string { format += "\n\tunitsMode[%-v]: %-v" args = append(args, key.LogString(), value.LogString()) } + format += "\n\tanonymousAccessMode: %-v" + args = append(args, p.anonymousAccessMode) format += "\n\teveryoneAccessMode: %-v" args = append(args, p.everyoneAccessMode) format += "\n\t]>" return fmt.Sprintf(format, args...) } +func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) { + if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] { + if *modeMap == nil { + *modeMap = make(map[unit.Type]perm_model.AccessMode) + } + (*modeMap)[unitType] = accessMode + } +} + func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) { + // apply public (anonymous) access permissions + for _, u := range perm.units { + applyPublicAccessPermission(u.Type, u.AnonymousAccessMode, &perm.anonymousAccessMode) + } + if user == nil || user.ID <= 0 { // for anonymous access, it could be: // AccessMode is None or Read, units has repo units, unitModes is nil return } - // apply everyone access permissions + // apply public (everyone) access permissions for _, u := range perm.units { - if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] { - if perm.everyoneAccessMode == nil { - perm.everyoneAccessMode = make(map[unit.Type]perm_model.AccessMode) - } - perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode - } + applyPublicAccessPermission(u.Type, u.EveryoneAccessMode, &perm.everyoneAccessMode) } if perm.unitsMode == nil { @@ -209,6 +228,11 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) { break } } + for t := range perm.anonymousAccessMode { + if shouldKeep = shouldKeep || u.Type == t; shouldKeep { + break + } + } for t := range perm.everyoneAccessMode { if shouldKeep = shouldKeep || u.Type == t; shouldKeep { break diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go index 9862da0673..024f4400b3 100644 --- a/models/perm/access/repo_permission_test.go +++ b/models/perm/access/repo_permission_test.go @@ -22,14 +22,21 @@ func TestHasAnyUnitAccess(t *testing.T) { units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}}, } assert.False(t, perm.HasAnyUnitAccess()) - assert.False(t, perm.HasAnyUnitAccessOrEveryoneAccess()) + assert.False(t, perm.HasAnyUnitAccessOrPublicAccess()) perm = Permission{ units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}}, everyoneAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead}, } assert.False(t, perm.HasAnyUnitAccess()) - assert.True(t, perm.HasAnyUnitAccessOrEveryoneAccess()) + assert.True(t, perm.HasAnyUnitAccessOrPublicAccess()) + + perm = Permission{ + units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}}, + anonymousAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead}, + } + assert.False(t, perm.HasAnyUnitAccess()) + assert.True(t, perm.HasAnyUnitAccessOrPublicAccess()) perm = Permission{ AccessMode: perm_model.AccessModeRead, @@ -43,7 +50,7 @@ func TestHasAnyUnitAccess(t *testing.T) { assert.True(t, perm.HasAnyUnitAccess()) } -func TestApplyEveryoneRepoPermission(t *testing.T) { +func TestApplyPublicAccessRepoPermission(t *testing.T) { perm := Permission{ AccessMode: perm_model.AccessModeNone, units: []*repo_model.RepoUnit{ @@ -53,6 +60,15 @@ func TestApplyEveryoneRepoPermission(t *testing.T) { finalProcessRepoUnitPermission(nil, &perm) assert.False(t, perm.CanRead(unit.TypeWiki)) + perm = Permission{ + AccessMode: perm_model.AccessModeNone, + units: []*repo_model.RepoUnit{ + {Type: unit.TypeWiki, AnonymousAccessMode: perm_model.AccessModeRead}, + }, + } + finalProcessRepoUnitPermission(nil, &perm) + assert.True(t, perm.CanRead(unit.TypeWiki)) + perm = Permission{ AccessMode: perm_model.AccessModeNone, units: []*repo_model.RepoUnit{ diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index cb52c2c9e2..93b0dcab31 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -42,12 +42,13 @@ func (err ErrUnitTypeNotExist) Unwrap() error { // RepoUnit describes all units of a repository type RepoUnit struct { //revive:disable-line:exported - ID int64 - RepoID int64 `xorm:"INDEX(s)"` - Type unit.Type `xorm:"INDEX(s)"` - Config convert.Conversion `xorm:"TEXT"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` - EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"` + ID int64 + RepoID int64 `xorm:"INDEX(s)"` + Type unit.Type `xorm:"INDEX(s)"` + Config convert.Conversion `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` + AnonymousAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"` + EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"` } func init() { diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index ac7eb768fa..e8da443b67 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -105,8 +105,6 @@ func Settings(ctx *context.Context) { // SettingsPost response for changes of a repository func SettingsPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.RepoSettingForm) - ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull @@ -119,867 +117,965 @@ func SettingsPost(ctx *context.Context) { ctx.Data["SigningSettings"] = setting.Repository.Signing ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled - repo := ctx.Repo.Repository - switch ctx.FormString("action") { case "update": - if ctx.HasError() { - ctx.HTML(http.StatusOK, tplSettingsOptions) - return - } - - newRepoName := form.RepoName - // Check if repository name has been changed. - if repo.LowerName != strings.ToLower(newRepoName) { - // Close the GitRepo if open - if ctx.Repo.GitRepo != nil { - ctx.Repo.GitRepo.Close() - ctx.Repo.GitRepo = nil - } - if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil { - ctx.Data["Err_RepoName"] = true - switch { - case repo_model.IsErrRepoAlreadyExist(err): - ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) - case db.IsErrNameReserved(err): - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) - case repo_model.IsErrRepoFilesAlreadyExist(err): - ctx.Data["Err_RepoName"] = true - switch { - case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form) - case setting.Repository.AllowAdoptionOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form) - case setting.Repository.AllowDeleteOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form) - default: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form) - } - case db.IsErrNamePatternNotAllowed(err): - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) - default: - ctx.ServerError("ChangeRepositoryName", err) - } - return - } - - log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName) - } - // In case it's just a case change. - repo.Name = newRepoName - repo.LowerName = strings.ToLower(newRepoName) - repo.Description = form.Description - repo.Website = form.Website - repo.IsTemplate = form.Template - - // Visibility of forked repository is forced sync with base repository. - if repo.IsFork { - form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate - } - - if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { - ctx.ServerError("UpdateRepository", err) - return - } - log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(repo.Link() + "/settings") - + handleSettingsPostUpdate(ctx) case "mirror": - if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived { - ctx.NotFound(nil) - return - } - - pullMirror, err := repo_model.GetMirrorByRepoID(ctx, ctx.Repo.Repository.ID) - if err == repo_model.ErrMirrorNotExist { - ctx.NotFound(nil) - return - } - if err != nil { - ctx.ServerError("GetMirrorByRepoID", err) - return - } - // This section doesn't require repo_name/RepoName to be set in the form, don't show it - // as an error on the UI for this action - ctx.Data["Err_RepoName"] = nil - - interval, err := time.ParseDuration(form.Interval) - if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { - ctx.Data["Err_Interval"] = true - ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) - return - } - - pullMirror.EnablePrune = form.EnablePrune - pullMirror.Interval = interval - pullMirror.ScheduleNextUpdate() - if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil { - ctx.ServerError("UpdateMirror", err) - return - } - - u, err := git.GetRemoteURL(ctx, ctx.Repo.Repository.RepoPath(), pullMirror.GetRemoteName()) - if err != nil { - ctx.Data["Err_MirrorAddress"] = true - handleSettingRemoteAddrError(ctx, err, form) - return - } - if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() { - form.MirrorPassword, _ = u.User.Password() - } - - address, err := git.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) - if err == nil { - err = migrations.IsMigrateURLAllowed(address, ctx.Doer) - } - if err != nil { - ctx.Data["Err_MirrorAddress"] = true - handleSettingRemoteAddrError(ctx, err, form) - return - } - - if err := mirror_service.UpdateAddress(ctx, pullMirror, address); err != nil { - ctx.ServerError("UpdateAddress", err) - return - } - - remoteAddress, err := util.SanitizeURL(form.MirrorAddress) - if err != nil { - ctx.Data["Err_MirrorAddress"] = true - handleSettingRemoteAddrError(ctx, err, form) - return - } - pullMirror.RemoteAddress = remoteAddress - - form.LFS = form.LFS && setting.LFS.StartServer - - if len(form.LFSEndpoint) > 0 { - ep := lfs.DetermineEndpoint("", form.LFSEndpoint) - if ep == nil { - ctx.Data["Err_LFSEndpoint"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form) - return - } - err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer) - if err != nil { - ctx.Data["Err_LFSEndpoint"] = true - handleSettingRemoteAddrError(ctx, err, form) - return - } - } - - pullMirror.LFS = form.LFS - pullMirror.LFSEndpoint = form.LFSEndpoint - if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil { - ctx.ServerError("UpdateMirror", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(repo.Link() + "/settings") - + handleSettingsPostMirror(ctx) case "mirror-sync": - if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived { - ctx.NotFound(nil) - return - } - - mirror_service.AddPullMirrorToQueue(repo.ID) - - ctx.Flash.Info(ctx.Tr("repo.settings.pull_mirror_sync_in_progress", repo.OriginalURL)) - ctx.Redirect(repo.Link() + "/settings") - + handleSettingsPostMirrorSync(ctx) case "push-mirror-sync": - if !setting.Mirror.Enabled { - ctx.NotFound(nil) - return - } - - m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) - if m == nil { - ctx.NotFound(nil) - return - } - - mirror_service.AddPushMirrorToQueue(m.ID) - - ctx.Flash.Info(ctx.Tr("repo.settings.push_mirror_sync_in_progress", m.RemoteAddress)) - ctx.Redirect(repo.Link() + "/settings") - + handleSettingsPostPushMirrorSync(ctx) case "push-mirror-update": - if !setting.Mirror.Enabled || repo.IsArchived { - ctx.NotFound(nil) - return - } - - // This section doesn't require repo_name/RepoName to be set in the form, don't show it - // as an error on the UI for this action - ctx.Data["Err_RepoName"] = nil - - interval, err := time.ParseDuration(form.PushMirrorInterval) - if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { - ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{}) - return - } - - m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) - if m == nil { - ctx.NotFound(nil) - return - } - - m.Interval = interval - if err := repo_model.UpdatePushMirrorInterval(ctx, m); err != nil { - ctx.ServerError("UpdatePushMirrorInterval", err) - return - } - // Background why we are adding it to Queue - // If we observed its implementation in the context of `push-mirror-sync` where it - // is evident that pushing to the queue is necessary for updates. - // So, there are updates within the given interval, it is necessary to update the queue accordingly. - if !ctx.FormBool("push_mirror_defer_sync") { - // push_mirror_defer_sync is mainly for testing purpose, we do not really want to sync the push mirror immediately - mirror_service.AddPushMirrorToQueue(m.ID) - } - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(repo.Link() + "/settings") - + handleSettingsPostPushMirrorUpdate(ctx) case "push-mirror-remove": - if !setting.Mirror.Enabled || repo.IsArchived { - ctx.NotFound(nil) - return - } - - // This section doesn't require repo_name/RepoName to be set in the form, don't show it - // as an error on the UI for this action - ctx.Data["Err_RepoName"] = nil - - m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) - if m == nil { - ctx.NotFound(nil) - return - } - - if err := mirror_service.RemovePushMirrorRemote(ctx, m); err != nil { - ctx.ServerError("RemovePushMirrorRemote", err) - return - } - - if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { - ctx.ServerError("DeletePushMirrorByID", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(repo.Link() + "/settings") - + handleSettingsPostPushMirrorRemove(ctx) case "push-mirror-add": - if setting.Mirror.DisableNewPush || repo.IsArchived { - ctx.NotFound(nil) - return - } - - // This section doesn't require repo_name/RepoName to be set in the form, don't show it - // as an error on the UI for this action - ctx.Data["Err_RepoName"] = nil - - interval, err := time.ParseDuration(form.PushMirrorInterval) - if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { - ctx.Data["Err_PushMirrorInterval"] = true - ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) - return - } - - address, err := git.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword) - if err == nil { - err = migrations.IsMigrateURLAllowed(address, ctx.Doer) - } - if err != nil { - ctx.Data["Err_PushMirrorAddress"] = true - handleSettingRemoteAddrError(ctx, err, form) - return - } - - remoteSuffix, err := util.CryptoRandomString(10) - if err != nil { - ctx.ServerError("RandomString", err) - return - } - - remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress) - if err != nil { - ctx.Data["Err_PushMirrorAddress"] = true - handleSettingRemoteAddrError(ctx, err, form) - return - } - - m := &repo_model.PushMirror{ - RepoID: repo.ID, - Repo: repo, - RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), - SyncOnCommit: form.PushMirrorSyncOnCommit, - Interval: interval, - RemoteAddress: remoteAddress, - } - if err := db.Insert(ctx, m); err != nil { - ctx.ServerError("InsertPushMirror", err) - return - } - - if err := mirror_service.AddPushMirrorRemote(ctx, m, address); err != nil { - if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { - log.Error("DeletePushMirrors %v", err) - } - ctx.ServerError("AddPushMirrorRemote", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(repo.Link() + "/settings") - + handleSettingsPostPushMirrorAdd(ctx) case "advanced": - var repoChanged bool - var units []repo_model.RepoUnit - var deleteUnitTypes []unit_model.Type - - // This section doesn't require repo_name/RepoName to be set in the form, don't show it - // as an error on the UI for this action - ctx.Data["Err_RepoName"] = nil - - if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch { - repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch - repoChanged = true - } - - if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeCode, - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), - }) - } else if !unit_model.TypeCode.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) - } - - if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { - if !validation.IsValidExternalURL(form.ExternalWikiURL) { - ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) - ctx.Redirect(repo.Link() + "/settings") - return - } - - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalWiki, - Config: &repo_model.ExternalWikiConfig{ - ExternalWikiURL: form.ExternalWikiURL, - }, - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) - } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeWiki, - Config: new(repo_model.UnitConfig), - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite), - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) - } else { - if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) - } - if !unit_model.TypeWiki.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) - } - } - - if form.DefaultWikiBranch != "" { - if err := wiki_service.ChangeDefaultWikiBranch(ctx, repo, form.DefaultWikiBranch); err != nil { - log.Error("ChangeDefaultWikiBranch failed, err: %v", err) - ctx.Flash.Warning(ctx.Tr("repo.settings.failed_to_change_default_wiki_branch")) - } - } - - if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { - if !validation.IsValidExternalURL(form.ExternalTrackerURL) { - ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) - ctx.Redirect(repo.Link() + "/settings") - return - } - if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { - ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) - ctx.Redirect(repo.Link() + "/settings") - return - } - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalTracker, - Config: &repo_model.ExternalTrackerConfig{ - ExternalTrackerURL: form.ExternalTrackerURL, - ExternalTrackerFormat: form.TrackerURLFormat, - ExternalTrackerStyle: form.TrackerIssueStyle, - ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, - }, - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) - } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeIssues, - Config: &repo_model.IssuesConfig{ - EnableTimetracker: form.EnableTimetracker, - AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, - EnableDependencies: form.EnableIssueDependencies, - }, - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) - } else { - if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) - } - if !unit_model.TypeIssues.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) - } - } - - if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeProjects, - Config: &repo_model.ProjectsConfig{ - ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode), - }, - }) - } else if !unit_model.TypeProjects.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) - } - - if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeReleases, - }) - } else if !unit_model.TypeReleases.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) - } - - if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePackages, - }) - } else if !unit_model.TypePackages.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) - } - - if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeActions, - }) - } else if !unit_model.TypeActions.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) - } - - if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePullRequests, - Config: &repo_model.PullRequestsConfig{ - IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, - AllowMerge: form.PullsAllowMerge, - AllowRebase: form.PullsAllowRebase, - AllowRebaseMerge: form.PullsAllowRebaseMerge, - AllowSquash: form.PullsAllowSquash, - AllowFastForwardOnly: form.PullsAllowFastForwardOnly, - AllowManualMerge: form.PullsAllowManualMerge, - AutodetectManualMerge: form.EnableAutodetectManualMerge, - AllowRebaseUpdate: form.PullsAllowRebaseUpdate, - DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, - DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), - DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, - }, - }) - } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) - } - - if len(units) == 0 { - ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - return - } - - if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { - ctx.ServerError("UpdateRepositoryUnits", err) - return - } - if repoChanged { - if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { - ctx.ServerError("UpdateRepository", err) - return - } - } - log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - + handleSettingsPostAdvanced(ctx) case "signing": - changed := false - trustModel := repo_model.ToTrustModel(form.TrustModel) - if trustModel != repo.TrustModel { - repo.TrustModel = trustModel - changed = true - } - - if changed { - if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { - ctx.ServerError("UpdateRepository", err) - return - } - } - log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - + handleSettingsPostSigning(ctx) case "admin": - if !ctx.Doer.IsAdmin { - ctx.HTTPError(http.StatusForbidden) - return - } - - if repo.IsFsckEnabled != form.EnableHealthCheck { - repo.IsFsckEnabled = form.EnableHealthCheck - } - - if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { - ctx.ServerError("UpdateRepository", err) - return - } - - log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - + handleSettingsPostAdmin(ctx) case "admin_index": - if !ctx.Doer.IsAdmin { - ctx.HTTPError(http.StatusForbidden) - return - } - - switch form.RequestReindexType { - case "stats": - if err := stats.UpdateRepoIndexer(ctx.Repo.Repository); err != nil { - ctx.ServerError("UpdateStatsRepondexer", err) - return - } - case "code": - if !setting.Indexer.RepoIndexerEnabled { - ctx.HTTPError(http.StatusForbidden) - return - } - code.UpdateRepoIndexer(ctx.Repo.Repository) - default: - ctx.NotFound(nil) - return - } - - log.Trace("Repository reindex for %s requested: %s/%s", form.RequestReindexType, ctx.Repo.Owner.Name, repo.Name) - - ctx.Flash.Success(ctx.Tr("repo.settings.reindex_requested")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - + handleSettingsPostAdminIndex(ctx) case "convert": - if !ctx.Repo.IsOwner() { - ctx.HTTPError(http.StatusNotFound) - return - } - if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) - return - } - - if !repo.IsMirror { - ctx.HTTPError(http.StatusNotFound) - return - } - repo.IsMirror = false - - if _, err := repo_service.CleanUpMigrateInfo(ctx, repo); err != nil { - ctx.ServerError("CleanUpMigrateInfo", err) - return - } else if err = repo_model.DeleteMirrorByRepoID(ctx, ctx.Repo.Repository.ID); err != nil { - ctx.ServerError("DeleteMirrorByRepoID", err) - return - } - log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) - ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) - ctx.Redirect(repo.Link()) - + handleSettingsPostConvert(ctx) case "convert_fork": - if !ctx.Repo.IsOwner() { - ctx.HTTPError(http.StatusNotFound) - return - } - if err := repo.LoadOwner(ctx); err != nil { - ctx.ServerError("Convert Fork", err) - return - } - if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) - return - } - - if !repo.IsFork { - ctx.HTTPError(http.StatusNotFound) - return - } - - if !ctx.Repo.Owner.CanCreateRepo() { - maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() - msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) - ctx.Flash.Error(msg) - ctx.Redirect(repo.Link() + "/settings") - return - } - - if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil { - log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err) - ctx.ServerError("Convert Fork", err) - return - } - - log.Trace("Repository converted from fork to regular: %s", repo.FullName()) - ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed")) - ctx.Redirect(repo.Link()) - + handleSettingsPostConvertFork(ctx) case "transfer": - if !ctx.Repo.IsOwner() { - ctx.HTTPError(http.StatusNotFound) - return - } - if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) - return - } + handleSettingsPostTransfer(ctx) + case "cancel_transfer": + handleSettingsPostCancelTransfer(ctx) + case "delete": + handleSettingsPostDelete(ctx) + case "delete-wiki": + handleSettingsPostDeleteWiki(ctx) + case "archive": + handleSettingsPostArchive(ctx) + case "unarchive": + handleSettingsPostUnarchive(ctx) + case "visibility": + handleSettingsPostVisibility(ctx) + default: + ctx.NotFound(nil) + } +} - newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name")) - if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) - return - } - ctx.ServerError("IsUserExist", err) - return - } - - if newOwner.Type == user_model.UserTypeOrganization { - if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) { - // The user shouldn't know about this organization - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) - return - } - } +func handleSettingsPostUpdate(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if ctx.HasError() { + ctx.HTML(http.StatusOK, tplSettingsOptions) + return + } + newRepoName := form.RepoName + // Check if repository name has been changed. + if repo.LowerName != strings.ToLower(newRepoName) { // Close the GitRepo if open if ctx.Repo.GitRepo != nil { ctx.Repo.GitRepo.Close() ctx.Repo.GitRepo = nil } - - oldFullname := repo.FullName() - if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil { - if repo_model.IsErrRepoAlreadyExist(err) { - ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) - } else if repo_model.IsErrRepoTransferInProgress(err) { - ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) - } else if errors.Is(err, user_model.ErrBlockedUser) { - ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) - } else { - ctx.ServerError("TransferOwnership", err) - } - - return - } - - if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer { - log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner) - ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName())) - } else { - log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName()) - ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed")) - } - ctx.Redirect(repo.Link() + "/settings") - - case "cancel_transfer": - if !ctx.Repo.IsOwner() { - ctx.HTTPError(http.StatusNotFound) - return - } - - repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) - if err != nil { - if repo_model.IsErrNoPendingTransfer(err) { - ctx.Flash.Error("repo.settings.transfer_abort_invalid") - ctx.Redirect(repo.Link() + "/settings") - } else { - ctx.ServerError("GetPendingRepositoryTransfer", err) + if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil { + ctx.Data["Err_RepoName"] = true + switch { + case repo_model.IsErrRepoAlreadyExist(err): + ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) + case db.IsErrNameReserved(err): + ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) + case repo_model.IsErrRepoFilesAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + switch { + case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form) + case setting.Repository.AllowAdoptionOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form) + case setting.Repository.AllowDeleteOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form) + default: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form) + } + case db.IsErrNamePatternNotAllowed(err): + ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) + default: + ctx.ServerError("ChangeRepositoryName", err) } return } - if err := repo_service.CancelRepositoryTransfer(ctx, repoTransfer, ctx.Doer); err != nil { - ctx.ServerError("CancelRepositoryTransfer", err) + log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName) + } + // In case it's just a case change. + repo.Name = newRepoName + repo.LowerName = strings.ToLower(newRepoName) + repo.Description = form.Description + repo.Website = form.Website + repo.IsTemplate = form.Template + + // Visibility of forked repository is forced sync with base repository. + if repo.IsFork { + form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate + } + + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { + ctx.ServerError("UpdateRepository", err) + return + } + log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostMirror(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived { + ctx.NotFound(nil) + return + } + + pullMirror, err := repo_model.GetMirrorByRepoID(ctx, ctx.Repo.Repository.ID) + if err == repo_model.ErrMirrorNotExist { + ctx.NotFound(nil) + return + } + if err != nil { + ctx.ServerError("GetMirrorByRepoID", err) + return + } + // This section doesn't require repo_name/RepoName to be set in the form, don't show it + // as an error on the UI for this action + ctx.Data["Err_RepoName"] = nil + + interval, err := time.ParseDuration(form.Interval) + if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { + ctx.Data["Err_Interval"] = true + ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) + return + } + + pullMirror.EnablePrune = form.EnablePrune + pullMirror.Interval = interval + pullMirror.ScheduleNextUpdate() + if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil { + ctx.ServerError("UpdateMirror", err) + return + } + + u, err := git.GetRemoteURL(ctx, ctx.Repo.Repository.RepoPath(), pullMirror.GetRemoteName()) + if err != nil { + ctx.Data["Err_MirrorAddress"] = true + handleSettingRemoteAddrError(ctx, err, form) + return + } + if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() { + form.MirrorPassword, _ = u.User.Password() + } + + address, err := git.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) + if err == nil { + err = migrations.IsMigrateURLAllowed(address, ctx.Doer) + } + if err != nil { + ctx.Data["Err_MirrorAddress"] = true + handleSettingRemoteAddrError(ctx, err, form) + return + } + + if err := mirror_service.UpdateAddress(ctx, pullMirror, address); err != nil { + ctx.ServerError("UpdateAddress", err) + return + } + + remoteAddress, err := util.SanitizeURL(form.MirrorAddress) + if err != nil { + ctx.Data["Err_MirrorAddress"] = true + handleSettingRemoteAddrError(ctx, err, form) + return + } + pullMirror.RemoteAddress = remoteAddress + + form.LFS = form.LFS && setting.LFS.StartServer + + if len(form.LFSEndpoint) > 0 { + ep := lfs.DetermineEndpoint("", form.LFSEndpoint) + if ep == nil { + ctx.Data["Err_LFSEndpoint"] = true + ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form) return } - - log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) - ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) - ctx.Redirect(repo.Link() + "/settings") - - case "delete": - if !ctx.Repo.IsOwner() { - ctx.HTTPError(http.StatusNotFound) - return - } - if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) - return - } - - // Close the gitrepository before doing this. - if ctx.Repo.GitRepo != nil { - ctx.Repo.GitRepo.Close() - } - - if err := repo_service.DeleteRepository(ctx, ctx.Doer, ctx.Repo.Repository, true); err != nil { - ctx.ServerError("DeleteRepository", err) - return - } - log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) - - ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) - ctx.Redirect(ctx.Repo.Owner.DashboardLink()) - - case "delete-wiki": - if !ctx.Repo.IsOwner() { - ctx.HTTPError(http.StatusNotFound) - return - } - if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) - return - } - - err := wiki_service.DeleteWiki(ctx, repo) + err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer) if err != nil { - log.Error("Delete Wiki: %v", err.Error()) + ctx.Data["Err_LFSEndpoint"] = true + handleSettingRemoteAddrError(ctx, err, form) + return } - log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) + } - ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success")) + pullMirror.LFS = form.LFS + pullMirror.LFSEndpoint = form.LFSEndpoint + if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil { + ctx.ServerError("UpdateMirror", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostMirrorSync(ctx *context.Context) { + repo := ctx.Repo.Repository + if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived { + ctx.NotFound(nil) + return + } + + mirror_service.AddPullMirrorToQueue(repo.ID) + + ctx.Flash.Info(ctx.Tr("repo.settings.pull_mirror_sync_in_progress", repo.OriginalURL)) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostPushMirrorSync(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + + if !setting.Mirror.Enabled { + ctx.NotFound(nil) + return + } + + m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) + if m == nil { + ctx.NotFound(nil) + return + } + + mirror_service.AddPushMirrorToQueue(m.ID) + + ctx.Flash.Info(ctx.Tr("repo.settings.push_mirror_sync_in_progress", m.RemoteAddress)) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostPushMirrorUpdate(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + + if !setting.Mirror.Enabled || repo.IsArchived { + ctx.NotFound(nil) + return + } + + // This section doesn't require repo_name/RepoName to be set in the form, don't show it + // as an error on the UI for this action + ctx.Data["Err_RepoName"] = nil + + interval, err := time.ParseDuration(form.PushMirrorInterval) + if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { + ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{}) + return + } + + m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) + if m == nil { + ctx.NotFound(nil) + return + } + + m.Interval = interval + if err := repo_model.UpdatePushMirrorInterval(ctx, m); err != nil { + ctx.ServerError("UpdatePushMirrorInterval", err) + return + } + // Background why we are adding it to Queue + // If we observed its implementation in the context of `push-mirror-sync` where it + // is evident that pushing to the queue is necessary for updates. + // So, there are updates within the given interval, it is necessary to update the queue accordingly. + if !ctx.FormBool("push_mirror_defer_sync") { + // push_mirror_defer_sync is mainly for testing purpose, we do not really want to sync the push mirror immediately + mirror_service.AddPushMirrorToQueue(m.ID) + } + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostPushMirrorRemove(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + + if !setting.Mirror.Enabled || repo.IsArchived { + ctx.NotFound(nil) + return + } + + // This section doesn't require repo_name/RepoName to be set in the form, don't show it + // as an error on the UI for this action + ctx.Data["Err_RepoName"] = nil + + m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID) + if m == nil { + ctx.NotFound(nil) + return + } + + if err := mirror_service.RemovePushMirrorRemote(ctx, m); err != nil { + ctx.ServerError("RemovePushMirrorRemote", err) + return + } + + if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { + ctx.ServerError("DeletePushMirrorByID", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostPushMirrorAdd(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + + if setting.Mirror.DisableNewPush || repo.IsArchived { + ctx.NotFound(nil) + return + } + + // This section doesn't require repo_name/RepoName to be set in the form, don't show it + // as an error on the UI for this action + ctx.Data["Err_RepoName"] = nil + + interval, err := time.ParseDuration(form.PushMirrorInterval) + if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { + ctx.Data["Err_PushMirrorInterval"] = true + ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) + return + } + + address, err := git.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword) + if err == nil { + err = migrations.IsMigrateURLAllowed(address, ctx.Doer) + } + if err != nil { + ctx.Data["Err_PushMirrorAddress"] = true + handleSettingRemoteAddrError(ctx, err, form) + return + } + + remoteSuffix, err := util.CryptoRandomString(10) + if err != nil { + ctx.ServerError("RandomString", err) + return + } + + remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress) + if err != nil { + ctx.Data["Err_PushMirrorAddress"] = true + handleSettingRemoteAddrError(ctx, err, form) + return + } + + m := &repo_model.PushMirror{ + RepoID: repo.ID, + Repo: repo, + RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), + SyncOnCommit: form.PushMirrorSyncOnCommit, + Interval: interval, + RemoteAddress: remoteAddress, + } + if err := db.Insert(ctx, m); err != nil { + ctx.ServerError("InsertPushMirror", err) + return + } + + if err := mirror_service.AddPushMirrorRemote(ctx, m, address); err != nil { + if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { + log.Error("DeletePushMirrors %v", err) + } + ctx.ServerError("AddPushMirrorRemote", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostAdvanced(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + var repoChanged bool + var units []repo_model.RepoUnit + var deleteUnitTypes []unit_model.Type + + // This section doesn't require repo_name/RepoName to be set in the form, don't show it + // as an error on the UI for this action + ctx.Data["Err_RepoName"] = nil + + if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch { + repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch + repoChanged = true + } + + if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeCode, + EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), + }) + } else if !unit_model.TypeCode.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) + } + + if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { + if !validation.IsValidExternalURL(form.ExternalWikiURL) { + ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) + ctx.Redirect(repo.Link() + "/settings") + return + } + + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeExternalWiki, + Config: &repo_model.ExternalWikiConfig{ + ExternalWikiURL: form.ExternalWikiURL, + }, + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) + } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeWiki, + Config: new(repo_model.UnitConfig), + EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite), + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) + } else { + if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) + } + if !unit_model.TypeWiki.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) + } + } + + if form.DefaultWikiBranch != "" { + if err := wiki_service.ChangeDefaultWikiBranch(ctx, repo, form.DefaultWikiBranch); err != nil { + log.Error("ChangeDefaultWikiBranch failed, err: %v", err) + ctx.Flash.Warning(ctx.Tr("repo.settings.failed_to_change_default_wiki_branch")) + } + } + + if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { + if !validation.IsValidExternalURL(form.ExternalTrackerURL) { + ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) + ctx.Redirect(repo.Link() + "/settings") + return + } + if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { + ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) + ctx.Redirect(repo.Link() + "/settings") + return + } + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeExternalTracker, + Config: &repo_model.ExternalTrackerConfig{ + ExternalTrackerURL: form.ExternalTrackerURL, + ExternalTrackerFormat: form.TrackerURLFormat, + ExternalTrackerStyle: form.TrackerIssueStyle, + ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, + }, + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) + } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeIssues, + Config: &repo_model.IssuesConfig{ + EnableTimetracker: form.EnableTimetracker, + AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, + EnableDependencies: form.EnableIssueDependencies, + }, + EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) + } else { + if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) + } + if !unit_model.TypeIssues.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) + } + } + + if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeProjects, + Config: &repo_model.ProjectsConfig{ + ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode), + }, + }) + } else if !unit_model.TypeProjects.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) + } + + if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeReleases, + }) + } else if !unit_model.TypeReleases.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) + } + + if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypePackages, + }) + } else if !unit_model.TypePackages.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) + } + + if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeActions, + }) + } else if !unit_model.TypeActions.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) + } + + if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypePullRequests, + Config: &repo_model.PullRequestsConfig{ + IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, + AllowMerge: form.PullsAllowMerge, + AllowRebase: form.PullsAllowRebase, + AllowRebaseMerge: form.PullsAllowRebaseMerge, + AllowSquash: form.PullsAllowSquash, + AllowFastForwardOnly: form.PullsAllowFastForwardOnly, + AllowManualMerge: form.PullsAllowManualMerge, + AutodetectManualMerge: form.EnableAutodetectManualMerge, + AllowRebaseUpdate: form.PullsAllowRebaseUpdate, + DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, + DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), + DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, + }, + }) + } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) + } + + if len(units) == 0 { + ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit")) ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } - case "archive": - if !ctx.Repo.IsOwner() { + if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { + ctx.ServerError("UpdateRepositoryUnits", err) + return + } + if repoChanged { + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { + ctx.ServerError("UpdateRepository", err) + return + } + } + log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +func handleSettingsPostSigning(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + changed := false + trustModel := repo_model.ToTrustModel(form.TrustModel) + if trustModel != repo.TrustModel { + repo.TrustModel = trustModel + changed = true + } + + if changed { + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { + ctx.ServerError("UpdateRepository", err) + return + } + } + log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +func handleSettingsPostAdmin(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !ctx.Doer.IsAdmin { + ctx.HTTPError(http.StatusForbidden) + return + } + + if repo.IsFsckEnabled != form.EnableHealthCheck { + repo.IsFsckEnabled = form.EnableHealthCheck + } + + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { + ctx.ServerError("UpdateRepository", err) + return + } + + log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +func handleSettingsPostAdminIndex(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !ctx.Doer.IsAdmin { + ctx.HTTPError(http.StatusForbidden) + return + } + + switch form.RequestReindexType { + case "stats": + if err := stats.UpdateRepoIndexer(ctx.Repo.Repository); err != nil { + ctx.ServerError("UpdateStatsRepondexer", err) + return + } + case "code": + if !setting.Indexer.RepoIndexerEnabled { ctx.HTTPError(http.StatusForbidden) return } - - if repo.IsMirror { - ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - return - } - - if err := repo_model.SetArchiveRepoState(ctx, repo, true); err != nil { - log.Error("Tried to archive a repo: %s", err) - ctx.Flash.Error(ctx.Tr("repo.settings.archive.error")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - return - } - - if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil { - log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) - } - - // update issue indexer - issue_indexer.UpdateRepoIndexer(ctx, repo.ID) - - ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) - - log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - - case "unarchive": - if !ctx.Repo.IsOwner() { - ctx.HTTPError(http.StatusForbidden) - return - } - - if err := repo_model.SetArchiveRepoState(ctx, repo, false); err != nil { - log.Error("Tried to unarchive a repo: %s", err) - ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - return - } - - if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) { - if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil { - log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) - } - } - - // update issue indexer - issue_indexer.UpdateRepoIndexer(ctx, repo.ID) - - ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) - - log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - - case "visibility": - if repo.IsFork { - ctx.Flash.Error(ctx.Tr("repo.settings.visibility.fork_error")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - return - } - - var err error - - // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public - if setting.Repository.ForcePrivate && repo.IsPrivate && !ctx.Doer.IsAdmin { - ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) - return - } - - if repo.IsPrivate { - err = repo_service.MakeRepoPublic(ctx, repo) - } else { - err = repo_service.MakeRepoPrivate(ctx, repo) - } - - if err != nil { - log.Error("Tried to change the visibility of the repo: %s", err) - ctx.Flash.Error(ctx.Tr("repo.settings.visibility.error")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.visibility.success")) - - log.Trace("Repository visibility changed: %s/%s", ctx.Repo.Owner.Name, repo.Name) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - + code.UpdateRepoIndexer(ctx.Repo.Repository) default: ctx.NotFound(nil) + return } + + log.Trace("Repository reindex for %s requested: %s/%s", form.RequestReindexType, ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.reindex_requested")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +func handleSettingsPostConvert(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusNotFound) + return + } + if repo.Name != form.RepoName { + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + return + } + + if !repo.IsMirror { + ctx.HTTPError(http.StatusNotFound) + return + } + repo.IsMirror = false + + if _, err := repo_service.CleanUpMigrateInfo(ctx, repo); err != nil { + ctx.ServerError("CleanUpMigrateInfo", err) + return + } else if err = repo_model.DeleteMirrorByRepoID(ctx, ctx.Repo.Repository.ID); err != nil { + ctx.ServerError("DeleteMirrorByRepoID", err) + return + } + log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) + ctx.Redirect(repo.Link()) +} + +func handleSettingsPostConvertFork(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusNotFound) + return + } + if err := repo.LoadOwner(ctx); err != nil { + ctx.ServerError("Convert Fork", err) + return + } + if repo.Name != form.RepoName { + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + return + } + + if !repo.IsFork { + ctx.HTTPError(http.StatusNotFound) + return + } + + if !ctx.Repo.Owner.CanCreateRepo() { + maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() + msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) + ctx.Flash.Error(msg) + ctx.Redirect(repo.Link() + "/settings") + return + } + + if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil { + log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err) + ctx.ServerError("Convert Fork", err) + return + } + + log.Trace("Repository converted from fork to regular: %s", repo.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed")) + ctx.Redirect(repo.Link()) +} + +func handleSettingsPostTransfer(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusNotFound) + return + } + if repo.Name != form.RepoName { + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + return + } + + newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name")) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) + return + } + ctx.ServerError("IsUserExist", err) + return + } + + if newOwner.Type == user_model.UserTypeOrganization { + if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) { + // The user shouldn't know about this organization + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) + return + } + } + + // Close the GitRepo if open + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + ctx.Repo.GitRepo = nil + } + + oldFullname := repo.FullName() + if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil { + if repo_model.IsErrRepoAlreadyExist(err) { + ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) + } else if repo_model.IsErrRepoTransferInProgress(err) { + ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) + } else if errors.Is(err, user_model.ErrBlockedUser) { + ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) + } else { + ctx.ServerError("TransferOwnership", err) + } + + return + } + + if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer { + log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner) + ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName())) + } else { + log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed")) + } + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostCancelTransfer(ctx *context.Context) { + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusNotFound) + return + } + + repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) + if err != nil { + if repo_model.IsErrNoPendingTransfer(err) { + ctx.Flash.Error("repo.settings.transfer_abort_invalid") + ctx.Redirect(repo.Link() + "/settings") + } else { + ctx.ServerError("GetPendingRepositoryTransfer", err) + } + return + } + + if err := repo_service.CancelRepositoryTransfer(ctx, repoTransfer, ctx.Doer); err != nil { + ctx.ServerError("CancelRepositoryTransfer", err) + return + } + + log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) + ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) + ctx.Redirect(repo.Link() + "/settings") +} + +func handleSettingsPostDelete(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusNotFound) + return + } + if repo.Name != form.RepoName { + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + return + } + + // Close the gitrepository before doing this. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + + if err := repo_service.DeleteRepository(ctx, ctx.Doer, ctx.Repo.Repository, true); err != nil { + ctx.ServerError("DeleteRepository", err) + return + } + log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) + ctx.Redirect(ctx.Repo.Owner.DashboardLink()) +} + +func handleSettingsPostDeleteWiki(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusNotFound) + return + } + if repo.Name != form.RepoName { + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + return + } + + err := wiki_service.DeleteWiki(ctx, repo) + if err != nil { + log.Error("Delete Wiki: %v", err.Error()) + } + log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +func handleSettingsPostArchive(ctx *context.Context) { + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusForbidden) + return + } + + if repo.IsMirror { + ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } + + if err := repo_model.SetArchiveRepoState(ctx, repo, true); err != nil { + log.Error("Tried to archive a repo: %s", err) + ctx.Flash.Error(ctx.Tr("repo.settings.archive.error")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } + + if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil { + log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) + } + + // update issue indexer + issue_indexer.UpdateRepoIndexer(ctx, repo.ID) + + ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) + + log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +func handleSettingsPostUnarchive(ctx *context.Context) { + repo := ctx.Repo.Repository + if !ctx.Repo.IsOwner() { + ctx.HTTPError(http.StatusForbidden) + return + } + + if err := repo_model.SetArchiveRepoState(ctx, repo, false); err != nil { + log.Error("Tried to unarchive a repo: %s", err) + ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } + + if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) { + if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil { + log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) + } + } + + // update issue indexer + issue_indexer.UpdateRepoIndexer(ctx, repo.ID) + + ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) + + log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +func handleSettingsPostVisibility(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoSettingForm) + repo := ctx.Repo.Repository + if repo.IsFork { + ctx.Flash.Error(ctx.Tr("repo.settings.visibility.fork_error")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } + + var err error + + // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public + if setting.Repository.ForcePrivate && repo.IsPrivate && !ctx.Doer.IsAdmin { + ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) + return + } + + if repo.IsPrivate { + err = repo_service.MakeRepoPublic(ctx, repo) + } else { + err = repo_service.MakeRepoPrivate(ctx, repo) + } + + if err != nil { + log.Error("Tried to change the visibility of the repo: %s", err) + ctx.Flash.Error(ctx.Tr("repo.settings.visibility.error")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.visibility.success")) + + log.Trace("Repository visibility changed: %s/%s", ctx.Repo.Owner.Name, repo.Name) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") } func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) { diff --git a/services/context/repo.go b/services/context/repo.go index a083c557e6..7d0b44c42f 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -346,7 +346,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { return } - if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() && !canWriteAsMaintainer(ctx) { + if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) { if ctx.FormString("go-get") == "1" { EarlyResponseForGoGetMeta(ctx) return