From 3d544a3ad35f221d48591757ae3beaab0820e4ff Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Sat, 4 Jan 2025 18:47:24 +0800
Subject: [PATCH] Fix empty git repo handling logic (#33101)

Fix #33092
---
 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/repo/empty.tmpl       | 21 +++++++--------
 templates/repo/header.tmpl      |  2 +-
 6 files changed, 43 insertions(+), 32 deletions(-)

diff --git a/models/repo/repo.go b/models/repo/repo.go
index 5ef4d470c3..af4a1f7fb5 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -279,6 +279,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 6029d49ade..07c9ffa9fc 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1235,6 +1235,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 70ba07f9a8..3fcd7bba8e 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 2a473f4a54..63529e1d81 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -897,10 +897,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
@@ -911,7 +909,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
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}}