Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions options/locale/locale_en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2174,6 +2174,8 @@
"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_desc": "Transfer this repository to a user or to an organization for which you have administrator rights.",
"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_form_title": "Enter the repository 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_notices_1": "- You will lose access to the repository if you transfer it to an individual user.",
Expand Down Expand Up @@ -2478,6 +2480,9 @@
"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_two": "May remove the relationship between it and <strong>forks</strong>, <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.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>",
Expand Down
19 changes: 9 additions & 10 deletions routers/web/repo/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -999,22 +999,23 @@ func handleSettingsPostUnarchive(ctx *context.Context) {
}

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")
ctx.JSONError(ctx.Tr("repo.settings.visibility.fork_error"))
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.RenderWithErrDeprecated(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form)
ctx.JSONError(ctx.Tr("form.repository_force_private"))
return
}
if !repo.IsPrivate && repo.FullName() != ctx.FormString("confirm_repo_name") {
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
return
}

var err error
if repo.IsPrivate {
err = repo_service.MakeRepoPublic(ctx, repo)
} else {
Expand All @@ -1023,15 +1024,13 @@ func handleSettingsPostVisibility(ctx *context.Context) {

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")
ctx.JSONError(ctx.Tr("repo.settings.visibility.error"))
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")
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings")
}

func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) {
Expand Down
136 changes: 46 additions & 90 deletions templates/repo/settings/options.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -887,21 +887,8 @@
</div>
<form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="convert">
<div class="field">
<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 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>
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_confirm"))}}
</form>
</div>
</div>
Expand All @@ -917,21 +904,8 @@
</div>
<form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="convert_fork">
<div class="field">
<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="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>
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_fork_confirm"))}}
</form>
</div>
</div>
Expand All @@ -949,25 +923,13 @@
</div>
<form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="transfer">
<div class="field">
<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>
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
<div class="required field">
<label for="new_owner_name">{{ctx.Locale.Tr "repo.settings.transfer_owner"}}</label>
<input id="new_owner_name" name="new_owner_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.transfer_perform"}}</button>
</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.transfer_perform"))}}
</form>
</div>
</div>
Expand All @@ -986,49 +948,56 @@
</div>
<form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="delete">
<div class="field">
<label>
{{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>
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_delete"))}}
</form>
</div>
</div>

{{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">
{{ctx.Locale.Tr "repo.visibility"}}
</div>
<div class="content">
{{if .Repository.IsPrivate}}
<p>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_title"}}</p>
<ul>
<li>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_one"}}</li>
</ul>
{{else}}
<p>{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_title"}}</p>
<ul>
<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>
{{if .Repository.IsPrivate}}
<p>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_title"}}</p>
<ul>
<li>{{ctx.Locale.Tr "repo.settings.visibility.public.bullet_one"}}</li>
</ul>
{{else}}
<p>{{ctx.Locale.Tr "repo.settings.visibility.private.bullet_title"}}</p>
<ul>
<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>
</ul>
{{if or .Repository.NumStars .Repository.NumWatches .Repository.NumForks}}
<div class="ui small message">
<ul class="tw-m-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>
</div>
{{end}}
{{end}}
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="visibility">
{{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}}
{{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>
<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>
{{end}}

Expand All @@ -1044,21 +1013,8 @@
</div>
<form class="ui form" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="delete-wiki">
<div class="field">
<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="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>
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_wiki_delete"))}}
</form>
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions templates/repo/settings/repo_name_confirm_fields.tmpl
Original file line number Diff line number Diff line change
@@ -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>
Comment thread
techknowlogick marked this conversation as resolved.
62 changes: 62 additions & 0 deletions tests/integration/repo_visibility_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package integration

import (
"net/http"
"net/url"
"testing"

repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/tests"

"github.com/stretchr/testify/assert"
)

func TestRepositoryVisibilityChange(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
defer tests.PrepareTestEnv(t)()
Comment thread
wxiaoguang marked this conversation as resolved.
Outdated

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",
"repo_id": "1",
"confirm_repo_name": "wrong-name",
})
resp := session.MakeRequest(t, req, http.StatusBadRequest)
assert.Contains(t, resp.Body.String(), "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",
"repo_id": "1",
"confirm_repo_name": "user2/repo1",
})
resp = session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "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",
"repo_id": "2",
})
resp := session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "redirect")

repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.False(t, repo2.IsPrivate)
})
})
}