From e637008fe3908a0027606846eab367e697ccc71d Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Sun, 5 Jan 2025 23:18:02 +0800
Subject: [PATCH] Fix empty git repo handling logic and fix mobile view
 (#33101) (#33102)

Backport #33101 and UI fix from main (including #33108)
---
 models/repo/repo.go                  |  2 ++
 options/locale/locale_en-US.ini      |  1 +
 routers/web/repo/view_home.go        | 46 ++++++++++++++++++----------
 services/context/repo.go             |  3 --
 templates/base/head_navbar.tmpl      |  6 ++--
 templates/repo/commit_page.tmpl      |  2 +-
 templates/repo/empty.tmpl            | 21 ++++++-------
 templates/repo/header.tmpl           |  2 +-
 templates/user/dashboard/navbar.tmpl |  4 +--
 web_src/css/base.css                 | 10 +++---
 web_src/css/modules/navbar.css       |  3 +-
 11 files changed, 56 insertions(+), 44 deletions(-)

diff --git a/models/repo/repo.go b/models/repo/repo.go
index 8d211caf40..c5060a419f 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -276,6 +276,8 @@ func (repo *Repository) IsBroken() bool {
 }
 
 // MarkAsBrokenEmpty marks the repo as broken and empty
+// FIXME: the status "broken" and "is_empty" were abused,
+// The code always set them together, no way to distinguish whether a repo is really "empty" or "broken"
 func (repo *Repository) MarkAsBrokenEmpty() {
 	repo.Status = RepositoryBroken
 	repo.IsEmpty = true
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 16bd0fe8b6..2c10a4dbe1 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1231,6 +1231,7 @@ create_new_repo_command = Creating a new repository on the command line
 push_exist_repo = Pushing an existing repository from the command line
 empty_message = This repository does not contain any content.
 broken_message = The Git data underlying this repository cannot be read. Contact the administrator of this instance or delete this repository.
+no_branch = This repository doesn’t have any branches.
 
 code = Code
 code.desc = Access source code, files, commits and branches.
diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go
index b318c4a621..1d886a5ffb 100644
--- a/routers/web/repo/view_home.go
+++ b/routers/web/repo/view_home.go
@@ -223,16 +223,37 @@ func prepareRecentlyPushedNewBranches(ctx *context.Context) {
 	}
 }
 
+func updateContextRepoEmptyAndStatus(ctx *context.Context, empty bool, status repo_model.RepositoryStatus) {
+	ctx.Repo.Repository.IsEmpty = empty
+	if ctx.Repo.Repository.Status == repo_model.RepositoryReady || ctx.Repo.Repository.Status == repo_model.RepositoryBroken {
+		ctx.Repo.Repository.Status = status // only handle ready and broken status, leave other status as-is
+	}
+	if err := repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty", "status"); err != nil {
+		ctx.ServerError("updateContextRepoEmptyAndStatus: UpdateRepositoryCols", err)
+		return
+	}
+}
+
 func handleRepoEmptyOrBroken(ctx *context.Context) {
 	showEmpty := true
-	var err error
 	if ctx.Repo.GitRepo != nil {
-		showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
+		reallyEmpty, err := ctx.Repo.GitRepo.IsEmpty()
 		if err != nil {
+			showEmpty = true // the repo is broken
+			updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryBroken)
 			log.Error("GitRepo.IsEmpty: %v", err)
-			ctx.Repo.Repository.Status = repo_model.RepositoryBroken
-			showEmpty = true
 			ctx.Flash.Error(ctx.Tr("error.occurred"), true)
+		} else if reallyEmpty {
+			showEmpty = true // the repo is really empty
+			updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady)
+		} else if ctx.Repo.Commit == nil {
+			showEmpty = true // it is not really empty, but there is no branch
+			// at the moment, other repo units like "actions" are not able to handle such case,
+			// so we just mark the repo as empty to prevent from displaying these units.
+			updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady)
+		} else {
+			// the repo is actually not empty and has branches, need to update the database later
+			showEmpty = false
 		}
 	}
 	if showEmpty {
@@ -240,18 +261,11 @@ func handleRepoEmptyOrBroken(ctx *context.Context) {
 		return
 	}
 
-	// the repo is not really empty, so we should update the modal in database
-	// such problem may be caused by:
-	// 1) an error occurs during pushing/receiving.  2) the user replaces an empty git repo manually
-	// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
-	// it's possible for a repository to be non-empty by that flag but still 500
-	// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
-	ctx.Repo.Repository.IsEmpty = false
-	if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
-		ctx.ServerError("UpdateRepositoryCols", err)
-		return
-	}
-	if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
+	// The repo is not really empty, so we should update the model in database, such problem may be caused by:
+	// 1) an error occurs during pushing/receiving.
+	// 2) the user replaces an empty git repo manually.
+	updateContextRepoEmptyAndStatus(ctx, false, repo_model.RepositoryReady)
+	if err := repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
 		ctx.ServerError("UpdateRepoSize", err)
 		return
 	}
diff --git a/services/context/repo.go b/services/context/repo.go
index 9b54439110..1e7c430347 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -908,10 +908,8 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
 					refName = brs[0].Name
 				} else if len(brs) == 0 {
 					log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
-					ctx.Repo.Repository.MarkAsBrokenEmpty()
 				} else {
 					log.Error("GetBranches error: %v", err)
-					ctx.Repo.Repository.MarkAsBrokenEmpty()
 				}
 			}
 			ctx.Repo.RefName = refName
@@ -922,7 +920,6 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
 			} else if strings.Contains(err.Error(), "fatal: not a git repository") || strings.Contains(err.Error(), "object does not exist") {
 				// if the repository is broken, we can continue to the handler code, to show "Settings -> Delete Repository" for end users
 				log.Error("GetBranchCommit: %v", err)
-				ctx.Repo.Repository.MarkAsBrokenEmpty()
 			} else {
 				ctx.ServerError("GetBranchCommit", err)
 				return cancel
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl
index 951ee590d1..bbb2a7ef01 100644
--- a/templates/base/head_navbar.tmpl
+++ b/templates/base/head_navbar.tmpl
@@ -11,7 +11,7 @@
 		</a>
 
 		<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
-		<div class="ui secondary menu item navbar-mobile-right only-mobile">
+		<div class="ui secondary menu navbar-mobile-right only-mobile">
 			{{if and .IsSigned EnableTimetracking .ActiveStopwatch}}
 			<a id="mobile-stopwatch-icon" class="active-stopwatch item tw-mx-0" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}" data-seconds="{{.ActiveStopwatch.Seconds}}">
 				<div class="tw-relative">
@@ -70,7 +70,7 @@
 					<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
 				</span>
 				<div class="menu user-menu">
-					<div class="ui header">
+					<div class="header">
 						{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
 					</div>
 
@@ -128,7 +128,7 @@
 					<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
 				</span>
 				<div class="menu user-menu">
-					<div class="ui header">
+					<div class="header">
 						{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
 					</div>
 
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
index 71f77154fb..38e3df8e70 100644
--- a/templates/repo/commit_page.tmpl
+++ b/templates/repo/commit_page.tmpl
@@ -30,7 +30,7 @@
 								{{ctx.Locale.Tr "repo.commit.operations"}}
 								{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 								<div class="menu">
-									<div class="ui header">{{ctx.Locale.Tr "repo.commit.operations"}}</div>
+									<div class="header">{{ctx.Locale.Tr "repo.commit.operations"}}</div>
 									<div class="divider"></div>
 									<div class="item show-create-branch-modal"
 										data-content="{{ctx.Locale.Tr "repo.branch.new_branch_from" (.CommitID)}}" {{/* used by the form */}}
diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl
index 7170fe3602..dfda5b7b2b 100644
--- a/templates/repo/empty.tmpl
+++ b/templates/repo/empty.tmpl
@@ -14,14 +14,13 @@
 						{{end}}
 					</div>
 				{{end}}
+
 				{{if .Repository.IsBroken}}
-						<div class="ui segment center">
-							{{ctx.Locale.Tr "repo.broken_message"}}
-						</div>
+					<div class="ui segment center">{{ctx.Locale.Tr "repo.broken_message"}}</div>
+				{{else if .Repository.IsEmpty}}
+					<div class="ui segment center">{{ctx.Locale.Tr "repo.no_branch"}}</div>
 				{{else if .CanWriteCode}}
-					<h4 class="ui top attached header">
-						{{ctx.Locale.Tr "repo.quick_guide"}}
-					</h4>
+					<h4 class="ui top attached header">{{ctx.Locale.Tr "repo.quick_guide"}}</h4>
 					<div class="ui attached guide table segment empty-repo-guide">
 						<div class="item">
 							<h3>{{ctx.Locale.Tr "repo.clone_this_repo"}} <small>{{ctx.Locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository"}}</small></h3>
@@ -66,12 +65,10 @@ git push -u origin {{.Repository.DefaultBranch}}</code></pre>
 								</div>
 							</div>
 						{{end}}
-					{{else}}
-						<div class="ui segment center">
-							{{ctx.Locale.Tr "repo.empty_message"}}
-						</div>
-					{{end}}
-				</div>
+					</div>
+				{{else}}
+					<div class="ui segment center">{{ctx.Locale.Tr "repo.empty_message"}}</div>
+				{{end}}
 			</div>
 		</div>
 	</div>
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index c3ae697f31..e187ef1a87 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -162,7 +162,7 @@
 						</a>
 					{{end}}
 
-					{{if and .EnableActions (.Permission.CanRead ctx.Consts.RepoUnitTypeActions)}}
+					{{if and .EnableActions (.Permission.CanRead ctx.Consts.RepoUnitTypeActions) (not .IsEmptyRepo)}}
 						<a class="{{if .PageIsActions}}active {{end}}item" href="{{.RepoLink}}/actions">
 							{{svg "octicon-play"}} {{ctx.Locale.Tr "actions.actions"}}
 							{{if .Repository.NumOpenActionRuns}}
diff --git a/templates/user/dashboard/navbar.tmpl b/templates/user/dashboard/navbar.tmpl
index 7982cbd950..a828bc90e4 100644
--- a/templates/user/dashboard/navbar.tmpl
+++ b/templates/user/dashboard/navbar.tmpl
@@ -12,7 +12,7 @@
 					{{svg "octicon-triangle-down" 14 "dropdown icon tw-ml-1"}}
 				</span>
 				<div class="context user overflow menu">
-					<div class="ui header">
+					<div class="header">
 						{{ctx.Locale.Tr "home.switch_dashboard_context"}}
 					</div>
 					<div class="scrolling menu items">
@@ -56,7 +56,7 @@
 					</span>
 					{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 					<div class="context user overflow menu">
-						<div class="ui header">
+						<div class="header">
 							{{ctx.Locale.Tr "home.filter_by_team_repositories"}}
 						</div>
 						<div class="scrolling menu items">
diff --git a/web_src/css/base.css b/web_src/css/base.css
index a0bfefebf5..1a354c9663 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -336,8 +336,13 @@ a.label,
   border-color: var(--color-secondary);
 }
 
+.ui.dropdown .menu > .header {
+  text-transform: none; /* reset fomantic's "uppercase" */
+}
+
 .ui.dropdown .menu > .header:not(.ui) {
   color: var(--color-text);
+  font-size: 0.95em; /* reset fomantic's small font-size */
 }
 
 .ui.dropdown .menu > .item {
@@ -691,11 +696,6 @@ input:-webkit-autofill:active,
   box-shadow: 0 6px 18px var(--color-shadow) !important;
 }
 
-.ui.dropdown .menu > .header {
-  font-size: 0.8em;
-  text-transform: none; /* reset fomantic's "uppercase" */
-}
-
 .ui .text.left {
   text-align: left !important;
 }
diff --git a/web_src/css/modules/navbar.css b/web_src/css/modules/navbar.css
index 556da2df3b..73914d9a21 100644
--- a/web_src/css/modules/navbar.css
+++ b/web_src/css/modules/navbar.css
@@ -66,7 +66,8 @@
     align-items: stretch;
   }
   /* hide all items */
-  #navbar .item {
+  #navbar .navbar-left > .item,
+  #navbar .navbar-right > .item {
     display: none;
   }
   #navbar #navbar-logo {