Skip to content
Merged
3 changes: 3 additions & 0 deletions options/locale/locale_en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2474,6 +2474,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
4 changes: 3 additions & 1 deletion templates/base/modal_actions_confirm.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ The ".ok.button" and ".cancel.button" selectors are also used by Fomantic Modal
{{else}}
{{$textNegative := ctx.Locale.Tr "modal.no"}}
{{$textPositive := ctx.Locale.Tr "modal.yes"}}
{{$buttonPositiveClass := "primary"}}
Comment thread
techknowlogick marked this conversation as resolved.
Outdated
{{if eq .ModalButtonTypes "confirm"}}
{{$textNegative = ctx.Locale.Tr "modal.cancel"}}
{{$textPositive = ctx.Locale.Tr "modal.confirm"}}
{{end}}
{{if .ModalButtonCancelText}}{{$textNegative = .ModalButtonCancelText}}{{end}}
{{if .ModalButtonOkText}}{{$textPositive = .ModalButtonOkText}}{{end}}
{{if .ModalButtonOkClass}}{{$buttonPositiveClass = .ModalButtonOkClass}}{{end}}
<button class="ui cancel button">{{svg "octicon-x"}} {{$textNegative}}</button>
<button class="ui primary ok button">{{svg "octicon-check"}} {{$textPositive}}</button>
<button class="ui {{$buttonPositiveClass}} ok button">{{svg "octicon-check"}} {{$textPositive}}</button>
{{end}}
</div>
131 changes: 44 additions & 87 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 "InputID" "repo_name_to_convert")}}
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm" "ModalButtonOkText" (ctx.Locale.Tr "repo.settings.convert_confirm") "ModalButtonOkClass" "red")}}
Comment thread
techknowlogick marked this conversation as resolved.
Outdated
</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 "InputID" "repo_name_to_convert_fork")}}
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm" "ModalButtonOkText" (ctx.Locale.Tr "repo.settings.convert_fork_confirm") "ModalButtonOkClass" "red")}}
</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 "InputID" "repo_name_to_transfer")}}
<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 "ModalButtonTypes" "confirm" "ModalButtonOkText" (ctx.Locale.Tr "repo.settings.transfer_perform") "ModalButtonOkClass" "red")}}
</form>
</div>
</div>
Expand All @@ -986,48 +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 "InputID" "repo_name_to_delete")}}
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm" "ModalButtonOkText" (ctx.Locale.Tr "repo.settings.confirm_delete") "ModalButtonOkClass" "red")}}
</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}}
</div>
<form action="{{.Link}}" method="post">
<form class="ui form form-fetch-action" 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" .}}
{{if not .Repository.IsPrivate}}
<div class="field">
<label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="tw-text-red">{{.Repository.FullName}}</span>
</label>
</div>
<div class="required field">
<label for="confirm_repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="confirm_repo_name" name="confirm_repo_name" required maxlength="200">
</div>
{{end}}
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm" "ModalButtonOkText" (Iif .Repository.IsPrivate (ctx.Locale.Tr "repo.settings.visibility.public.button") (ctx.Locale.Tr "repo.settings.visibility.private.button")) "ModalButtonOkClass" "red")}}
</form>
</div>
{{end}}
Expand All @@ -1044,21 +1014,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 "InputID" "repo_name_to_delete_wiki")}}
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm" "ModalButtonOkText" (ctx.Locale.Tr "repo.settings.confirm_wiki_delete") "ModalButtonOkClass" "red")}}
</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.transfer_form_title"}}
Comment thread
techknowlogick marked this conversation as resolved.
Outdated
<span class="tw-text-red">{{.RepoName}}</span>
</label>
</div>
<div class="required field">
<label for="{{.InputID}}">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="{{.InputID}}" name="repo_name" required maxlength="100">
</div>
Comment thread
techknowlogick marked this conversation as resolved.
66 changes: 66 additions & 0 deletions tests/integration/repo_visibility_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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 TestRepositoryVisibilityChangeRequiresNameWhenMakingPrivate(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")

// 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)
})
}

func TestRepositoryVisibilityChangeToPublicDoesNotRequireName(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it needs onGiteaRun? Why these 2 tests can't be done in one test (one preparation)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was marked as resolved, but not addressed.

I removed the onGiteaRun in new commits and the tests still run correctly.

defer tests.PrepareTestEnv(t)()

session := loginUser(t, "user2")

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)
})
}
Loading