From f3ba3e922dde7d12999a90d6cee15805a56cc7ff Mon Sep 17 00:00:00 2001
From: JakobDev <jakobdev@gmx.de>
Date: Wed, 24 Jan 2024 03:32:57 +0100
Subject: [PATCH] Don't run push mirrors for archived repos (#27140)

Fixes https://codeberg.org/forgejo/forgejo/issues/612

At the moment push mirrors are still run if a repo is archived. This PR
fixes this.
---
 models/repo/pushmirror.go            |   7 +-
 options/locale/locale_en-US.ini      |   1 +
 routers/api/v1/api.go                |   8 +-
 routers/web/repo/setting/setting.go  |  10 +-
 templates/repo/settings/options.tmpl | 346 ++++++++++++++-------------
 5 files changed, 191 insertions(+), 181 deletions(-)

diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go
index 24c58faf84..bf134abfb1 100644
--- a/models/repo/pushmirror.go
+++ b/models/repo/pushmirror.go
@@ -121,8 +121,11 @@ func GetPushMirrorsSyncedOnCommit(ctx context.Context, repoID int64) ([]*PushMir
 // PushMirrorsIterate iterates all push-mirror repositories.
 func PushMirrorsIterate(ctx context.Context, limit int, f func(idx int, bean any) error) error {
 	sess := db.GetEngine(ctx).
-		Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()).
-		And("`interval` != 0").
+		Table("push_mirror").
+		Join("INNER", "`repository`", "`repository`.id = `push_mirror`.repo_id").
+		Where("`push_mirror`.last_update + (`push_mirror`.`interval` / ?) <= ?", time.Second, time.Now().Unix()).
+		And("`push_mirror`.`interval` != 0").
+		And("`repository`.is_archived = ?", false).
 		OrderBy("last_update ASC")
 	if limit > 0 {
 		sess = sess.Limit(limit)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9a06bb0952..3bf8aa3845 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2377,6 +2377,7 @@ settings.archive.error = An error occurred while trying to archive the repo. See
 settings.archive.error_ismirror = You cannot archive a mirrored repo.
 settings.archive.branchsettings_unavailable = Branch settings are not available if the repo is archived.
 settings.archive.tagsettings_unavailable = Tag settings are not available if the repo is archived.
+settings.archive.mirrors_unavailable = Mirrors are not available if the repo is archived.
 settings.unarchive.button = Unarchive repo
 settings.unarchive.header = Unarchive this repo
 settings.unarchive.text = Unarchiving the repo will restore its ability to receive commits and pushes, as well as new issues and pull-requests.
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 8d7669762b..cb1803f7c6 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1181,13 +1181,13 @@ func Routes() *web.Route {
 							Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
 					})
 				}, reqRepoReader(unit.TypeReleases))
-				m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), repo.MirrorSync)
-				m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), repo.PushMirrorSync)
+				m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.MirrorSync)
+				m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), mustNotBeArchived, repo.PushMirrorSync)
 				m.Group("/push_mirrors", func() {
 					m.Combo("").Get(repo.ListPushMirrors).
-						Post(bind(api.CreatePushMirrorOption{}), repo.AddPushMirror)
+						Post(mustNotBeArchived, bind(api.CreatePushMirrorOption{}), repo.AddPushMirror)
 					m.Combo("/{name}").
-						Delete(repo.DeletePushMirrorByRemoteName).
+						Delete(mustNotBeArchived, repo.DeletePushMirrorByRemoteName).
 						Get(repo.GetPushMirrorByName)
 				}, reqAdmin(), reqToken())
 
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index e721b7c739..fc1403b3cc 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -185,7 +185,7 @@ func SettingsPost(ctx *context.Context) {
 		ctx.Redirect(repo.Link() + "/settings")
 
 	case "mirror":
-		if !setting.Mirror.Enabled || !repo.IsMirror {
+		if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived {
 			ctx.NotFound("", nil)
 			return
 		}
@@ -278,7 +278,7 @@ func SettingsPost(ctx *context.Context) {
 		ctx.Redirect(repo.Link() + "/settings")
 
 	case "mirror-sync":
-		if !setting.Mirror.Enabled || !repo.IsMirror {
+		if !setting.Mirror.Enabled || !repo.IsMirror || repo.IsArchived {
 			ctx.NotFound("", nil)
 			return
 		}
@@ -306,7 +306,7 @@ func SettingsPost(ctx *context.Context) {
 		ctx.Redirect(repo.Link() + "/settings")
 
 	case "push-mirror-update":
-		if !setting.Mirror.Enabled {
+		if !setting.Mirror.Enabled || repo.IsArchived {
 			ctx.NotFound("", nil)
 			return
 		}
@@ -343,7 +343,7 @@ func SettingsPost(ctx *context.Context) {
 		ctx.Redirect(repo.Link() + "/settings")
 
 	case "push-mirror-remove":
-		if !setting.Mirror.Enabled {
+		if !setting.Mirror.Enabled || repo.IsArchived {
 			ctx.NotFound("", nil)
 			return
 		}
@@ -372,7 +372,7 @@ func SettingsPost(ctx *context.Context) {
 		ctx.Redirect(repo.Link() + "/settings")
 
 	case "push-mirror-add":
-		if setting.Mirror.DisableNewPush {
+		if setting.Mirror.DisableNewPush || repo.IsArchived {
 			ctx.NotFound("", nil)
 			return
 		}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 8456bb409b..07b2f58d53 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -79,206 +79,212 @@
 				{{ctx.Locale.Tr "repo.settings.mirror_settings"}}
 			</h4>
 			<div class="ui attached segment">
-				{{if $newMirrorsEntirelyEnabled}}
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs"}}
-					<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pushing-to-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.pull_mirror_instructions"}}
-					<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_pull_section"}}</a><br>
-				{{else if $onlyNewPushMirrorsEnabled}}
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_pull_mirror.instructions"}}
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.more_information_if_disabled"}}
-					<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br>
-				{{else if $onlyNewPullMirrorsEnabled}}
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.instructions"}}
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning"}}
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.more_information_if_disabled"}}
-					<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.info"}}
-					{{if $existingPushMirror}}
-						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.can_still_use"}}
-					{{end}}
+				{{if .Repository.IsArchived}}
+					<div class="ui warning message gt-text-center">
+						{{ctx.Locale.Tr "repo.settings.archive.mirrors_unavailable"}}
+					</div>
 				{{else}}
-					{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.no_new_mirrors"}} {{ctx.Locale.Tr "repo.settings.mirror_settings.docs.can_still_use"}}<br>
-				{{end}}
-				<table class="ui table">
-					{{if $existingPushMirror}}
-					<thead>
-						<tr>
-							<th style="width:40%">{{ctx.Locale.Tr "repo.settings.mirror_settings.mirrored_repository"}}</th>
-							<th>{{ctx.Locale.Tr "repo.settings.mirror_settings.direction"}}</th>
-							<th>{{ctx.Locale.Tr "repo.settings.mirror_settings.last_update"}}</th>
-							<th></th>
-						</tr>
-					</thead>
+					{{if $newMirrorsEntirelyEnabled}}
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs"}}
+						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pushing-to-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.pull_mirror_instructions"}}
+						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_pull_section"}}</a><br>
+					{{else if $onlyNewPushMirrorsEnabled}}
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_pull_mirror.instructions"}}
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.more_information_if_disabled"}}
+						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br>
+					{{else if $onlyNewPullMirrorsEnabled}}
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.instructions"}}
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning"}}
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.more_information_if_disabled"}}
+						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.info"}}
+						{{if $existingPushMirror}}
+							{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.can_still_use"}}
+						{{end}}
+					{{else}}
+						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.no_new_mirrors"}} {{ctx.Locale.Tr "repo.settings.mirror_settings.docs.can_still_use"}}<br>
 					{{end}}
-					{{if $modifyBrokenPullMirror}}
-						{{/* even if a repo is a pull mirror (IsMirror=true), the PullMirror might still be nil if the mirror migration is broken */}}
+					<table class="ui table">
+						{{if $existingPushMirror}}
+						<thead>
+							<tr>
+								<th style="width:40%">{{ctx.Locale.Tr "repo.settings.mirror_settings.mirrored_repository"}}</th>
+								<th>{{ctx.Locale.Tr "repo.settings.mirror_settings.direction"}}</th>
+								<th>{{ctx.Locale.Tr "repo.settings.mirror_settings.last_update"}}</th>
+								<th></th>
+							</tr>
+						</thead>
+						{{end}}
+						{{if $modifyBrokenPullMirror}}
+							{{/* even if a repo is a pull mirror (IsMirror=true), the PullMirror might still be nil if the mirror migration is broken */}}
+							<tbody>
+								<tr>
+									<td colspan="4">
+										<div class="text red gt-py-4 gt-border-secondary-bottom">{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.pull"}}: {{ctx.Locale.Tr "error.occurred"}}</div>
+									</td>
+								</tr>
+							</tbody>
+						{{else if $isWorkingPullMirror}}
 						<tbody>
 							<tr>
-								<td colspan="4">
-									<div class="text red gt-py-4 gt-border-secondary-bottom">{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.pull"}}: {{ctx.Locale.Tr "error.occurred"}}</div>
+								<td>{{.PullMirror.RemoteAddress}}</td>
+								<td>{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.pull"}}</td>
+								<td>{{DateTime "full" .PullMirror.UpdatedUnix}}</td>
+								<td class="right aligned">
+									<form method="post" class="gt-dib">
+										{{.CsrfTokenHtml}}
+										<input type="hidden" name="action" value="mirror-sync">
+										<button class="ui primary tiny button inline text-thin">{{ctx.Locale.Tr "repo.settings.sync_mirror"}}</button>
+									</form>
 								</td>
 							</tr>
-						</tbody>
-					{{else if $isWorkingPullMirror}}
-					<tbody>
-						<tr>
-							<td>{{.PullMirror.RemoteAddress}}</td>
-							<td>{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.pull"}}</td>
-							<td>{{DateTime "full" .PullMirror.UpdatedUnix}}</td>
-							<td class="right aligned">
-								<form method="post" class="gt-dib">
-									{{.CsrfTokenHtml}}
-									<input type="hidden" name="action" value="mirror-sync">
-									<button class="ui primary tiny button inline text-thin">{{ctx.Locale.Tr "repo.settings.sync_mirror"}}</button>
-								</form>
-							</td>
-						</tr>
-						<tr>
-							<td colspan="4">
-								<form class="ui form" method="post">
-									{{template "base/disable_form_autofill"}}
-									{{.CsrfTokenHtml}}
-									<input type="hidden" name="action" value="mirror">
-									<div class="inline field {{if .Err_EnablePrune}}error{{end}}">
-										<label>{{ctx.Locale.Tr "repo.mirror_prune"}}</label>
-										<div class="ui checkbox">
-									<input id="enable_prune" name="enable_prune" type="checkbox" {{if .PullMirror.EnablePrune}}checked{{end}}>
-									<label>{{ctx.Locale.Tr "repo.mirror_prune_desc"}}</label>
-										</div>
-									</div>
-									<div class="inline field {{if .Err_Interval}}error{{end}}">
-										<label for="interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
-										<input id="interval" name="interval" value="{{.PullMirror.Interval}}">
-									</div>
-									{{$address := MirrorRemoteAddress $.Context .Repository .PullMirror.GetRemoteName false}}
-									<div class="field {{if .Err_MirrorAddress}}error{{end}}">
-										<label for="mirror_address">{{ctx.Locale.Tr "repo.mirror_address"}}</label>
-										<input id="mirror_address" name="mirror_address" value="{{$address.Address}}" required>
-										<p class="help">{{ctx.Locale.Tr "repo.mirror_address_desc"}}</p>
-									</div>
-									<details class="ui optional field" {{if or .Err_Auth $address.Username}}open{{end}}>
-										<summary class="gt-p-2">
-											{{ctx.Locale.Tr "repo.need_auth"}}
-										</summary>
-										<div class="gt-p-2">
-											<div class="inline field {{if .Err_Auth}}error{{end}}">
-												<label for="mirror_username">{{ctx.Locale.Tr "username"}}</label>
-												<input id="mirror_username" name="mirror_username" value="{{$address.Username}}" {{if not .mirror_username}}data-need-clear="true"{{end}}>
-											</div>
-											<div class="inline field {{if .Err_Auth}}error{{end}}">
-												<label for="mirror_password">{{ctx.Locale.Tr "password"}}</label>
-												<input id="mirror_password" name="mirror_password" type="password" placeholder="{{if $address.Password}}{{ctx.Locale.Tr "repo.mirror_password_placeholder"}}{{else}}{{ctx.Locale.Tr "repo.mirror_password_blank_placeholder"}}{{end}}" value="" {{if not .mirror_password}}data-need-clear="true"{{end}} autocomplete="off">
-											</div>
-											<p class="help">{{ctx.Locale.Tr "repo.mirror_password_help"}}</p>
-										</div>
-									</details>
-
-									{{if .LFSStartServer}}
-									<div class="inline field">
-										<label>{{ctx.Locale.Tr "repo.mirror_lfs"}}</label>
-										<div class="ui checkbox">
-											<input id="mirror_lfs" name="mirror_lfs" type="checkbox" {{if .PullMirror.LFS}}checked{{end}}>
-											<label>{{ctx.Locale.Tr "repo.mirror_lfs_desc"}}</label>
-										</div>
-									</div>
-									<div class="field {{if .Err_LFSEndpoint}}error{{end}}">
-										<label for="mirror_lfs_endpoint">{{ctx.Locale.Tr "repo.mirror_lfs_endpoint"}}</label>
-										<input id="mirror_lfs_endpoint" name="mirror_lfs_endpoint" value="{{.PullMirror.LFSEndpoint}}" placeholder="{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.placeholder"}}">
-										<p class="help">{{ctx.Locale.Tr "repo.mirror_lfs_endpoint_desc" "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md#server-discovery" | Str2html}}</p>
-									</div>
-									{{end}}
-									<div class="field">
-										<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.update_mirror_settings"}}</button>
-									</div>
-								</form>
-							</td>
-						</tr>
-					</tbody>
-					<thead><tr><th colspan="4"></th></tr></thead>
-					{{end}}{{/* end if: IsMirror */}}
-					<tbody>
-						{{range .PushMirrors}}
-						<tr>
-							<td class="gt-word-break">{{.RemoteAddress}}</td>
-							<td>{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.push"}}</td>
-							<td>{{if .LastUpdateUnix}}{{DateTime "full" .LastUpdateUnix}}{{else}}{{ctx.Locale.Tr "never"}}{{end}} {{if .LastError}}<div class="ui red label" data-tooltip-content="{{.LastError}}">{{ctx.Locale.Tr "error"}}</div>{{end}}</td>
-							<td class="right aligned">
-								<button
-									class="ui tiny button show-modal"
-									data-modal="#push-mirror-edit-modal"
-									data-tooltip-content="{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.edit_sync_time"}}"
-									data-modal-push-mirror-edit-id="{{.ID}}"
-									data-modal-push-mirror-edit-interval="{{.Interval}}"
-									data-modal-push-mirror-edit-address="{{.RemoteAddress}}"
-								>
-									{{svg "octicon-pencil" 14}}
-								</button>
-								<form method="post" class="gt-dib">
-									{{$.CsrfTokenHtml}}
-									<input type="hidden" name="action" value="push-mirror-sync">
-									<input type="hidden" name="push_mirror_id" value="{{.ID}}">
-									<button class="ui primary tiny button" data-tooltip-content="{{ctx.Locale.Tr "repo.settings.sync_mirror"}}">{{svg "octicon-sync" 14}}</button>
-								</form>
-								<form method="post" class="gt-dib">
-									{{$.CsrfTokenHtml}}
-									<input type="hidden" name="action" value="push-mirror-remove">
-									<input type="hidden" name="push_mirror_id" value="{{.ID}}">
-									<button class="ui basic red tiny button" data-tooltip-content="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button>
-								</form>
-							</td>
-						</tr>
-						{{else}}
-						<tr>
-							<td>{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.none"}}</td>
-						</tr>
-						{{end}}
-						{{if (not .DisableNewPushMirrors)}}
 							<tr>
 								<td colspan="4">
 									<form class="ui form" method="post">
 										{{template "base/disable_form_autofill"}}
 										{{.CsrfTokenHtml}}
-										<input type="hidden" name="action" value="push-mirror-add">
-										<div class="field {{if .Err_PushMirrorAddress}}error{{end}}">
-											<label for="push_mirror_address">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.remote_url"}}</label>
-											<input id="push_mirror_address" name="push_mirror_address" value="{{.push_mirror_address}}" required>
+										<input type="hidden" name="action" value="mirror">
+										<div class="inline field {{if .Err_EnablePrune}}error{{end}}">
+											<label>{{ctx.Locale.Tr "repo.mirror_prune"}}</label>
+											<div class="ui checkbox">
+										<input id="enable_prune" name="enable_prune" type="checkbox" {{if .PullMirror.EnablePrune}}checked{{end}}>
+										<label>{{ctx.Locale.Tr "repo.mirror_prune_desc"}}</label>
+											</div>
+										</div>
+										<div class="inline field {{if .Err_Interval}}error{{end}}">
+											<label for="interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
+											<input id="interval" name="interval" value="{{.PullMirror.Interval}}">
+										</div>
+										{{$address := MirrorRemoteAddress $.Context .Repository .PullMirror.GetRemoteName false}}
+										<div class="field {{if .Err_MirrorAddress}}error{{end}}">
+											<label for="mirror_address">{{ctx.Locale.Tr "repo.mirror_address"}}</label>
+											<input id="mirror_address" name="mirror_address" value="{{$address.Address}}" required>
 											<p class="help">{{ctx.Locale.Tr "repo.mirror_address_desc"}}</p>
 										</div>
-										<details class="ui optional field" {{if or .Err_PushMirrorAuth .push_mirror_username}}open{{end}}>
+										<details class="ui optional field" {{if or .Err_Auth $address.Username}}open{{end}}>
 											<summary class="gt-p-2">
 												{{ctx.Locale.Tr "repo.need_auth"}}
 											</summary>
 											<div class="gt-p-2">
-												<div class="inline field {{if .Err_PushMirrorAuth}}error{{end}}">
-													<label for="push_mirror_username">{{ctx.Locale.Tr "username"}}</label>
-													<input id="push_mirror_username" name="push_mirror_username" value="{{.push_mirror_username}}">
+												<div class="inline field {{if .Err_Auth}}error{{end}}">
+													<label for="mirror_username">{{ctx.Locale.Tr "username"}}</label>
+													<input id="mirror_username" name="mirror_username" value="{{$address.Username}}" {{if not .mirror_username}}data-need-clear="true"{{end}}>
 												</div>
-												<div class="inline field {{if .Err_PushMirrorAuth}}error{{end}}">
-													<label for="push_mirror_password">{{ctx.Locale.Tr "password"}}</label>
-													<input id="push_mirror_password" name="push_mirror_password" type="password" value="{{.push_mirror_password}}" autocomplete="off">
+												<div class="inline field {{if .Err_Auth}}error{{end}}">
+													<label for="mirror_password">{{ctx.Locale.Tr "password"}}</label>
+													<input id="mirror_password" name="mirror_password" type="password" placeholder="{{if $address.Password}}{{ctx.Locale.Tr "repo.mirror_password_placeholder"}}{{else}}{{ctx.Locale.Tr "repo.mirror_password_blank_placeholder"}}{{end}}" value="" {{if not .mirror_password}}data-need-clear="true"{{end}} autocomplete="off">
 												</div>
+												<p class="help">{{ctx.Locale.Tr "repo.mirror_password_help"}}</p>
 											</div>
 										</details>
-										<div class="field">
+
+										{{if .LFSStartServer}}
+										<div class="inline field">
+											<label>{{ctx.Locale.Tr "repo.mirror_lfs"}}</label>
 											<div class="ui checkbox">
-												<input id="push_mirror_sync_on_commit" name="push_mirror_sync_on_commit" type="checkbox" {{if .push_mirror_sync_on_commit}}checked{{end}}>
-												<label for="push_mirror_sync_on_commit">{{ctx.Locale.Tr "repo.mirror_sync_on_commit"}}</label>
+												<input id="mirror_lfs" name="mirror_lfs" type="checkbox" {{if .PullMirror.LFS}}checked{{end}}>
+												<label>{{ctx.Locale.Tr "repo.mirror_lfs_desc"}}</label>
 											</div>
 										</div>
-										<div class="inline field {{if .Err_PushMirrorInterval}}error{{end}}">
-											<label for="push_mirror_interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
-											<input id="push_mirror_interval" name="push_mirror_interval" value="{{if .push_mirror_interval}}{{.push_mirror_interval}}{{else}}{{.DefaultMirrorInterval}}{{end}}">
+										<div class="field {{if .Err_LFSEndpoint}}error{{end}}">
+											<label for="mirror_lfs_endpoint">{{ctx.Locale.Tr "repo.mirror_lfs_endpoint"}}</label>
+											<input id="mirror_lfs_endpoint" name="mirror_lfs_endpoint" value="{{.PullMirror.LFSEndpoint}}" placeholder="{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.placeholder"}}">
+											<p class="help">{{ctx.Locale.Tr "repo.mirror_lfs_endpoint_desc" "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md#server-discovery" | Str2html}}</p>
 										</div>
+										{{end}}
 										<div class="field">
-											<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.add"}}</button>
+											<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.update_mirror_settings"}}</button>
 										</div>
 									</form>
 								</td>
 							</tr>
-						{{end}}
-					</tbody>
-				</table>
+						</tbody>
+						<thead><tr><th colspan="4"></th></tr></thead>
+						{{end}}{{/* end if: IsMirror */}}
+						<tbody>
+							{{range .PushMirrors}}
+							<tr>
+								<td class="gt-word-break">{{.RemoteAddress}}</td>
+								<td>{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.push"}}</td>
+								<td>{{if .LastUpdateUnix}}{{DateTime "full" .LastUpdateUnix}}{{else}}{{ctx.Locale.Tr "never"}}{{end}} {{if .LastError}}<div class="ui red label" data-tooltip-content="{{.LastError}}">{{ctx.Locale.Tr "error"}}</div>{{end}}</td>
+								<td class="right aligned">
+									<button
+										class="ui tiny button show-modal"
+										data-modal="#push-mirror-edit-modal"
+										data-tooltip-content="{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.edit_sync_time"}}"
+										data-modal-push-mirror-edit-id="{{.ID}}"
+										data-modal-push-mirror-edit-interval="{{.Interval}}"
+										data-modal-push-mirror-edit-address="{{.RemoteAddress}}"
+									>
+										{{svg "octicon-pencil" 14}}
+									</button>
+									<form method="post" class="gt-dib">
+										{{$.CsrfTokenHtml}}
+										<input type="hidden" name="action" value="push-mirror-sync">
+										<input type="hidden" name="push_mirror_id" value="{{.ID}}">
+										<button class="ui primary tiny button" data-tooltip-content="{{ctx.Locale.Tr "repo.settings.sync_mirror"}}">{{svg "octicon-sync" 14}}</button>
+									</form>
+									<form method="post" class="gt-dib">
+										{{$.CsrfTokenHtml}}
+										<input type="hidden" name="action" value="push-mirror-remove">
+										<input type="hidden" name="push_mirror_id" value="{{.ID}}">
+										<button class="ui basic red tiny button" data-tooltip-content="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button>
+									</form>
+								</td>
+							</tr>
+							{{else}}
+							<tr>
+								<td>{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.none"}}</td>
+							</tr>
+							{{end}}
+							{{if (not .DisableNewPushMirrors)}}
+								<tr>
+									<td colspan="4">
+										<form class="ui form" method="post">
+											{{template "base/disable_form_autofill"}}
+											{{.CsrfTokenHtml}}
+											<input type="hidden" name="action" value="push-mirror-add">
+											<div class="field {{if .Err_PushMirrorAddress}}error{{end}}">
+												<label for="push_mirror_address">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.remote_url"}}</label>
+												<input id="push_mirror_address" name="push_mirror_address" value="{{.push_mirror_address}}" required>
+												<p class="help">{{ctx.Locale.Tr "repo.mirror_address_desc"}}</p>
+											</div>
+											<details class="ui optional field" {{if or .Err_PushMirrorAuth .push_mirror_username}}open{{end}}>
+												<summary class="gt-p-2">
+													{{ctx.Locale.Tr "repo.need_auth"}}
+												</summary>
+												<div class="gt-p-2">
+													<div class="inline field {{if .Err_PushMirrorAuth}}error{{end}}">
+														<label for="push_mirror_username">{{ctx.Locale.Tr "username"}}</label>
+														<input id="push_mirror_username" name="push_mirror_username" value="{{.push_mirror_username}}">
+													</div>
+													<div class="inline field {{if .Err_PushMirrorAuth}}error{{end}}">
+														<label for="push_mirror_password">{{ctx.Locale.Tr "password"}}</label>
+														<input id="push_mirror_password" name="push_mirror_password" type="password" value="{{.push_mirror_password}}" autocomplete="off">
+													</div>
+												</div>
+											</details>
+											<div class="field">
+												<div class="ui checkbox">
+													<input id="push_mirror_sync_on_commit" name="push_mirror_sync_on_commit" type="checkbox" {{if .push_mirror_sync_on_commit}}checked{{end}}>
+													<label for="push_mirror_sync_on_commit">{{ctx.Locale.Tr "repo.mirror_sync_on_commit"}}</label>
+												</div>
+											</div>
+											<div class="inline field {{if .Err_PushMirrorInterval}}error{{end}}">
+												<label for="push_mirror_interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
+												<input id="push_mirror_interval" name="push_mirror_interval" value="{{if .push_mirror_interval}}{{.push_mirror_interval}}{{else}}{{.DefaultMirrorInterval}}{{end}}">
+											</div>
+											<div class="field">
+												<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.add"}}</button>
+											</div>
+										</form>
+									</td>
+								</tr>
+							{{end}}
+						</tbody>
+					</table>
+				{{end}}
 			</div>
 		{{end}}