Require additional user confirmation for making repo private (#36959)

To align with how GitHub requires additional explicit user interaction
to make a repo private, including informing them of implications on what
happens if they do.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
techknowlogick
2026-03-24 16:13:08 -04:00
committed by GitHub
parent cfd9008891
commit 943ff75233
9 changed files with 173 additions and 181 deletions
+8
View File
@@ -45,6 +45,14 @@ func ParseJSONError(buf []byte) (ret struct {
return ret return ret
} }
func ParseJSONRedirect(buf []byte) (ret struct {
Redirect string `json:"redirect"`
},
) {
_ = json.Unmarshal(buf, &ret)
return ret
}
func IsNormalPageCompleted(s string) bool { func IsNormalPageCompleted(s string) bool {
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`) return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
} }
+6 -3
View File
@@ -969,7 +969,6 @@
"repo.visibility_description": "Only the owner or the organization members if they have rights, will be able to see it.", "repo.visibility_description": "Only the owner or the organization members if they have rights, will be able to see it.",
"repo.visibility_helper": "Make repository private", "repo.visibility_helper": "Make repository private",
"repo.visibility_helper_forced": "Your site administrator forces new repositories to be private.", "repo.visibility_helper_forced": "Your site administrator forces new repositories to be private.",
"repo.visibility_fork_helper": "(Changing this will affect all forks.)",
"repo.clone_helper": "Need help cloning? Visit <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Help</a>.", "repo.clone_helper": "Need help cloning? Visit <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Help</a>.",
"repo.fork_repo": "Fork Repository", "repo.fork_repo": "Fork Repository",
"repo.fork_from": "Fork From", "repo.fork_from": "Fork From",
@@ -2174,7 +2173,8 @@
"repo.settings.transfer_abort_invalid": "You cannot cancel a non existent repository transfer.", "repo.settings.transfer_abort_invalid": "You cannot cancel a non existent repository transfer.",
"repo.settings.transfer_abort_success": "The repository transfer to %s was successfully canceled.", "repo.settings.transfer_abort_success": "The repository transfer to %s was successfully canceled.",
"repo.settings.transfer_desc": "Transfer this repository to a user or to an organization for which you have administrator rights.", "repo.settings.transfer_desc": "Transfer this repository to a user or to an organization for which you have administrator rights.",
"repo.settings.transfer_form_title": "Enter the repository name as confirmation:", "repo.settings.enter_repo_name_to_confirm": "Enter the repository name as confirmation:",
"repo.settings.enter_repo_full_name_to_confirm": "Enter the full repository name (owner/name) as confirmation:",
"repo.settings.transfer_in_progress": "There is currently an ongoing transfer. Please cancel it if you would like to transfer this repository to another user.", "repo.settings.transfer_in_progress": "There is currently an ongoing transfer. Please cancel it if you would like to transfer this repository to another user.",
"repo.settings.transfer_notices_1": "- You will lose access to the repository if you transfer it to an individual user.", "repo.settings.transfer_notices_1": "- You will lose access to the repository if you transfer it to an individual user.",
"repo.settings.transfer_notices_2": "- You will keep access to the repository if you transfer it to an organization that you (co-)own.", "repo.settings.transfer_notices_2": "- You will keep access to the repository if you transfer it to an organization that you (co-)own.",
@@ -2477,7 +2477,10 @@
"repo.settings.visibility.private.text": "Changing the visibility to private will make the repo visible only to allowed members and may remove the relationship between it and existing forks, watchers, and stars.", "repo.settings.visibility.private.text": "Changing the visibility to private will make the repo visible only to allowed members and may remove the relationship between it and existing forks, watchers, and stars.",
"repo.settings.visibility.private.bullet_title": "<strong>Changing the visibility to private will:</strong>", "repo.settings.visibility.private.bullet_title": "<strong>Changing the visibility to private will:</strong>",
"repo.settings.visibility.private.bullet_one": "Make the repo visible only to allowed members.", "repo.settings.visibility.private.bullet_one": "Make the repo visible only to allowed members.",
"repo.settings.visibility.private.bullet_two": "May remove the relationship between it and <strong>forks</strong>, <strong>watchers</strong>, and <strong>stars</strong>.", "repo.settings.visibility.private.bullet_two": "Apply the visibility to its forks, and remove the <strong>watchers</strong> and <strong>stars</strong>.",
"repo.settings.visibility.private.stats_stars": "This repository has <strong>%d</strong> star(s) that may be lost.",
"repo.settings.visibility.private.stats_watchers": "This repository has <strong>%d</strong> watcher(s) that may be lost.",
"repo.settings.visibility.private.stats_forks": "This repository has <strong>%d</strong> fork(s) that are associated.",
"repo.settings.visibility.public.button": "Make Public", "repo.settings.visibility.public.button": "Make Public",
"repo.settings.visibility.public.text": "Changing the visibility to public will make the repo visible to anyone.", "repo.settings.visibility.public.text": "Changing the visibility to public will make the repo visible to anyone.",
"repo.settings.visibility.public.bullet_title": "<strong>Changing the visibility to public will:</strong>", "repo.settings.visibility.public.bullet_title": "<strong>Changing the visibility to public will:</strong>",
+11 -17
View File
@@ -999,39 +999,33 @@ func handleSettingsPostUnarchive(ctx *context.Context) {
} }
func handleSettingsPostVisibility(ctx *context.Context) { func handleSettingsPostVisibility(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
if repo.IsFork { if repo.IsFork {
ctx.Flash.Error(ctx.Tr("repo.settings.visibility.fork_error")) ctx.JSONError(ctx.Tr("repo.settings.visibility.fork_error"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
return return
} }
var err error private := ctx.FormOptionalBool("private").ValueOrDefault(true) // default to true for privacy & safety
// when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public // 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 { if !private && setting.Repository.ForcePrivate && !ctx.Doer.IsAdmin {
ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) ctx.JSONError(ctx.Tr("form.repository_force_private"))
return
}
if private && repo.FullName() != ctx.FormString("confirm_repo_name") {
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
return return
} }
if repo.IsPrivate { err := repo_service.MakeRepoPrivate(ctx, repo, private)
err = repo_service.MakeRepoPublic(ctx, repo)
} else {
err = repo_service.MakeRepoPrivate(ctx, repo)
}
if err != nil { if err != nil {
log.Error("Tried to change the visibility of the repo: %s", err) log.Error("Tried to change the visibility of the repo: %s", err)
ctx.Flash.Error(ctx.Tr("repo.settings.visibility.error")) ctx.JSONError(ctx.Tr("repo.settings.visibility.error"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
return return
} }
ctx.Flash.Success(ctx.Tr("repo.settings.visibility.success")) ctx.Flash.Success(ctx.Tr("repo.settings.visibility.success"))
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings")
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) { func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) {
+27 -66
View File
@@ -122,9 +122,9 @@ func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibili
}) })
} }
func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) { func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository, private bool) (err error) {
return db.WithTx(ctx, func(ctx context.Context) error { return db.WithTx(ctx, func(ctx context.Context) error {
repo.IsPrivate = false repo.IsPrivate = private
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil { if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
return err return err
} }
@@ -144,15 +144,33 @@ func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error
return err return err
} }
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) // If repo has become private, we need to set its actions to private, and clear stars and watches.
if err != nil { if private {
return fmt.Errorf("getRepositoriesByForkID: %w", err) _, err = db.GetEngine(ctx).
Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{IsPrivate: true})
if err != nil {
return err
}
if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
return err
}
if err = repo_model.ClearRepoWatches(ctx, repo.ID); err != nil {
return err
}
} }
if repo.Owner.Visibility != structs.VisibleTypePrivate { shouldUpdateForks := private
for i := range forkRepos { if !private && repo.Owner.Visibility != structs.VisibleTypePrivate {
if err = MakeRepoPublic(ctx, forkRepos[i]); err != nil { shouldUpdateForks = true
return fmt.Errorf("MakeRepoPublic[%d]: %w", forkRepos[i].ID, err) }
if shouldUpdateForks {
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
if err != nil {
return fmt.Errorf("getRepositoriesByForkID: %w", err)
}
for _, forkRepo := range forkRepos {
if err = MakeRepoPrivate(ctx, forkRepo, private); err != nil {
return fmt.Errorf("MakeRepoPrivate[%d]: %w", forkRepo.ID, err)
} }
} }
} }
@@ -160,63 +178,6 @@ func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error
// If visibility is changed, we need to update the issue indexer. // If visibility is changed, we need to update the issue indexer.
// Since the data in the issue indexer have field to indicate if the repo is public or not. // Since the data in the issue indexer have field to indicate if the repo is public or not.
issue_indexer.UpdateRepoIndexer(ctx, repo.ID) issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
return nil
})
}
func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) {
return db.WithTx(ctx, func(ctx context.Context) error {
repo.IsPrivate = true
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
return err
}
if err = repo.LoadOwner(ctx); err != nil {
return fmt.Errorf("LoadOwner: %w", err)
}
if repo.Owner.IsOrganization() {
// Organization repository need to recalculate access table when visibility is changed.
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
return fmt.Errorf("recalculateTeamAccesses: %w", err)
}
}
// If repo has become private, we need to set its actions to private.
_, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{
IsPrivate: true,
})
if err != nil {
return err
}
if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
return err
}
if err = repo_model.ClearRepoWatches(ctx, repo.ID); err != nil {
return err
}
// Create/Remove git-daemon-export-ok for git-daemon...
if err := CheckDaemonExportOK(ctx, repo); err != nil {
return err
}
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
if err != nil {
return fmt.Errorf("getRepositoriesByForkID: %w", err)
}
for i := range forkRepos {
if err = MakeRepoPrivate(ctx, forkRepos[i]); err != nil {
return fmt.Errorf("MakeRepoPrivate[%d]: %w", forkRepos[i].ID, err)
}
}
// If visibility is changed, we need to update the issue indexer.
// Since the data in the issue indexer have field to indicate if the repo is public or not.
issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
return nil return nil
}) })
} }
+2 -2
View File
@@ -74,13 +74,13 @@ func TestMakeRepoPrivateClearsWatches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo.IsPrivate = false assert.False(t, repo.IsPrivate)
watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repo.ID) watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, watchers) require.NotEmpty(t, watchers)
assert.NoError(t, MakeRepoPrivate(t.Context(), repo)) assert.NoError(t, MakeRepoPrivate(t.Context(), repo, true))
watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repo.ID) watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
assert.NoError(t, err) assert.NoError(t, err)
+3 -3
View File
@@ -67,13 +67,13 @@
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}} {{ctx.Locale.Tr "repo.settings.enter_repo_name_to_confirm"}}
<span class="tw-text-red">{{.Repository.Name}}</span> <span class="tw-text-red">{{.Repository.Name}}</span>
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label> <label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required> <input name="repo_name" required>
</div> </div>
<div class="actions"> <div class="actions">
+47 -90
View File
@@ -887,21 +887,8 @@
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="convert"> <input type="hidden" name="action" value="convert">
<div class="field"> {{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
<label> {{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_confirm"))}}
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="tw-text-red">{{.Repository.Name}}</span>
</label>
</div>
<div class="required field">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input name="repo_name" required maxlength="100">
</div>
<div class="actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.convert_confirm"}}</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -917,21 +904,8 @@
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="convert_fork"> <input type="hidden" name="action" value="convert_fork">
<div class="field"> {{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
<label> {{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_fork_confirm"))}}
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="tw-text-red">{{.Repository.Name}}</span>
</label>
</div>
<div class="required field">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input name="repo_name" required>
</div>
<div class="actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.convert_fork_confirm"}}</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -949,25 +923,13 @@
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="transfer"> <input type="hidden" name="action" value="transfer">
<div class="field"> {{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
<label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="tw-text-red">{{.Repository.Name}}</span>
</label>
</div>
<div class="required field">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input name="repo_name" required>
</div>
<div class="required field"> <div class="required field">
<label for="new_owner_name">{{ctx.Locale.Tr "repo.settings.transfer_owner"}}</label> <label for="new_owner_name">{{ctx.Locale.Tr "repo.settings.transfer_owner"}}</label>
<input id="new_owner_name" name="new_owner_name" required> <input id="new_owner_name" name="new_owner_name" required>
</div> </div>
<div class="actions"> {{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.transfer_perform"))}}
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.transfer_perform"}}</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -986,49 +948,57 @@
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<div class="field"> {{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
<label> {{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_delete"))}}
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="tw-text-red">{{.Repository.Name}}</span>
</label>
</div>
<div class="required field">
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required>
</div>
<div class="actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.confirm_delete"}}</button>
</div>
</form> </form>
</div> </div>
</div> </div>
{{if not .Repository.IsFork}} {{if not .Repository.IsFork}}
<div class="ui g-modal-confirm modal" id="visibility-repo-modal"> <div class="ui small modal" id="visibility-repo-modal">
<div class="header"> <div class="header">
{{ctx.Locale.Tr "repo.visibility"}} {{ctx.Locale.Tr "repo.visibility"}}
</div> </div>
<div class="content"> <div class="content">
{{if .Repository.IsPrivate}} {{if .Repository.IsPrivate}}
<p>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_title"}}</p> <p>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_title"}}</p>
<ul> <ul>
<li>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_one"}}</li> <li>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_one"}}</li>
</ul> </ul>
{{else}} {{else}}
<p>{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_title"}}</p> <p>{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_title"}}</p>
<ul> <ul>
<li>{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_one"}}</li> <li>{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_one"}}</li>
<li>{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_two"}}{{if .Repository.NumForks}}<span class="tw-text-red">{{ctx.Locale.Tr "repo.visibility_fork_helper"}}</span>{{end}}</li> <li>
{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_two"}}
</li>
{{if or .Repository.NumStars .Repository.NumWatches .Repository.NumForks}}
<ul class="tw-my-0 tw-pl-4">
{{if .Repository.NumStars}}<li>{{ctx.Locale.Tr "repo.settings.visibility.private.stats_stars" .Repository.NumStars}}</li>{{end}}
{{if .Repository.NumWatches}}<li>{{ctx.Locale.Tr "repo.settings.visibility.private.stats_watchers" .Repository.NumWatches}}</li>{{end}}
{{if .Repository.NumForks}}<li>{{ctx.Locale.Tr "repo.settings.visibility.private.stats_forks" .Repository.NumForks}}</li>{{end}}
</ul> </ul>
{{end}}
</ul>
{{end}}
<form class="ui form tw-mt-5 form-fetch-action" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="visibility">
<input type="hidden" name="private" value="{{not .Repository.IsPrivate}}">
{{if not .Repository.IsPrivate}}
<div class="field">
<label>
{{ctx.Locale.Tr "repo.settings.enter_repo_full_name_to_confirm"}}
<span class="tw-text-red">{{.Repository.FullName}}</span>
</label>
</div>
<div class="required field">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input name="confirm_repo_name" required maxlength="200">
</div>
{{end}} {{end}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (Iif .Repository.IsPrivate (ctx.Locale.Tr "repo.settings.visibility.public.button") (ctx.Locale.Tr "repo.settings.visibility.private.button")))}}
</form>
</div> </div>
<form action="{{.Link}}" method="post">
<input type="hidden" name="action" value="visibility">
<input type="hidden" name="repo_id" value="{{.Repository.ID}}">
{{template "base/modal_actions_confirm" .}}
</form>
</div> </div>
{{end}} {{end}}
@@ -1044,21 +1014,8 @@
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="delete-wiki"> <input type="hidden" name="action" value="delete-wiki">
<div class="field"> {{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
<label> {{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_wiki_delete"))}}
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="tw-text-red">{{.Repository.Name}}</span>
</label>
</div>
<div class="required field">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input name="repo_name" required>
</div>
<div class="actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.confirm_wiki_delete"}}</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -0,0 +1,10 @@
<div class="field">
<label>
{{ctx.Locale.Tr "repo.settings.enter_repo_name_to_confirm"}}
<span class="tw-text-red">{{.RepoName}}</span>
</label>
</div>
<div class="required field">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input name="repo_name" required maxlength="100">
</div>
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestRepositoryVisibilityChange(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
t.Run("MakePrivateRequiresCorrectName", func(t *testing.T) {
// Wrong name should be rejected with a JSON error
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"action": "visibility",
"private": "true",
"confirm_repo_name": "wrong-name",
})
resp := session.MakeRequest(t, req, http.StatusBadRequest)
assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
assert.False(t, repo1.IsPrivate)
// Correct full name (owner/repo) should succeed with a JSON redirect
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"action": "visibility",
"private": "true",
"confirm_repo_name": "user2/repo1",
})
resp = session.MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
assert.True(t, repo1.IsPrivate)
})
t.Run("MakePublicDoesNotRequireName", func(t *testing.T) {
req := NewRequestWithValues(t, "POST", "/user2/repo2/settings", map[string]string{
"action": "visibility",
"private": "false",
})
resp := session.MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.False(t, repo2.IsPrivate)
})
}