浏览代码

添加等待页面并优化错误处理

新增了等待页面,并在多个地方将错误处理方式从直接返回错误改为提示用户稍后再试。同时,改进了仓库迁移和 fork 的逻辑,使其异步执行并在超时后重定向。
SongZihuan 1 月之前
父节点
当前提交
ed74220689

+ 19 - 0
internal/context/context.go

@@ -169,6 +169,22 @@ func (c *Context) RenderWithErr(msg, tpl string, f any) {
 	c.HTML(http.StatusOK, tpl)
 }
 
+func (c *Context) Wait(err error, msg string, refresh bool) {
+	c.Flash.ErrorMsg = msg
+	c.Data["Refresh"] = refresh
+	c.Data["Flash"] = c.Flash.ErrorMsg
+
+	if err != nil {
+		log.ErrorDepth(4, "%s: %v", msg, err)
+		c.Data["ErrorMsg"] = err.Error()
+	} else {
+		c.Data["ErrorMsg"] = ""
+	}
+
+	c.Title("status.page_not_found")
+	c.HTML(http.StatusOK, "status/wait")
+}
+
 // NotFound renders the 404 page.
 func (c *Context) NotFound() {
 	c.Title("status.page_not_found")
@@ -269,6 +285,9 @@ func Contexter(store Store) macaron.Handler {
 			c.Data["LoggedUserID"] = c.User.ID
 			c.Data["LoggedUserName"] = c.User.Name
 			c.Data["IsAdmin"] = c.User.IsAdmin
+			c.Data["IsCanCreate"] = c.User.CanCreateRepo() || c.User.CanCreateOrganization()
+			c.Data["IsCanCreateRepo"] = c.User.CanCreateRepo()
+			c.Data["IsCanCreateOrg"] = c.User.CanCreateOrganization()
 		} else {
 			c.Data["LoggedUserID"] = 0
 			c.Data["LoggedUserName"] = ""

+ 9 - 9
internal/context/repo.go

@@ -229,7 +229,7 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 		if repo.IsMirror {
 			c.Repo.Mirror, err = database.GetMirrorByRepoID(repo.ID)
 			if err != nil {
-				c.Error(err, "get mirror by repository ID")
+				c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 				return
 			}
 			c.Data["MirrorEnablePrune"] = c.Repo.Mirror.EnablePrune
@@ -239,14 +239,14 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 
 		gitRepo, err := git.Open(database.RepoPath(ownerName, repoName))
 		if err != nil {
-			c.Error(err, "open repository")
+			c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 			return
 		}
 		c.Repo.GitRepo = gitRepo
 
 		tags, err := c.Repo.GitRepo.Tags()
 		if err != nil {
-			c.Error(err, "get tags")
+			c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 			return
 		}
 		c.Data["Tags"] = tags
@@ -277,7 +277,7 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 		c.Data["TagName"] = c.Repo.TagName
 		branches, err := c.Repo.GitRepo.Branches()
 		if err != nil {
-			c.Error(err, "get branches")
+			c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 			return
 		}
 		c.Data["Branches"] = branches
@@ -317,7 +317,7 @@ func RepoRef() macaron.Handler {
 			repoPath := database.RepoPath(c.Repo.Owner.Name, c.Repo.Repository.Name)
 			c.Repo.GitRepo, err = git.Open(repoPath)
 			if err != nil {
-				c.Error(err, "open repository")
+				c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 				return
 			}
 		}
@@ -328,14 +328,14 @@ func RepoRef() macaron.Handler {
 			if !c.Repo.GitRepo.HasBranch(refName) {
 				branches, err := c.Repo.GitRepo.Branches()
 				if err != nil {
-					c.Error(err, "get branches")
+					c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 					return
 				}
 				refName = branches[0]
 			}
 			c.Repo.Commit, err = c.Repo.GitRepo.BranchCommit(refName)
 			if err != nil {
-				c.Error(err, "get branch commit")
+				c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 				return
 			}
 			c.Repo.CommitID = c.Repo.Commit.ID.String()
@@ -366,7 +366,7 @@ func RepoRef() macaron.Handler {
 
 				c.Repo.Commit, err = c.Repo.GitRepo.BranchCommit(refName)
 				if err != nil {
-					c.Error(err, "get branch commit")
+					c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 					return
 				}
 				c.Repo.CommitID = c.Repo.Commit.ID.String()
@@ -375,7 +375,7 @@ func RepoRef() macaron.Handler {
 				c.Repo.IsViewTag = true
 				c.Repo.Commit, err = c.Repo.GitRepo.TagCommit(refName)
 				if err != nil {
-					c.Error(err, "get tag commit")
+					c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 					return
 				}
 				c.Repo.CommitID = c.Repo.Commit.ID.String()

+ 15 - 14
internal/database/repo.go

@@ -766,13 +766,13 @@ type MigrateRepoOptions struct {
 - GitHub, GitLab, Gogs: *.wiki.git
 - BitBucket: *.git/wiki
 */
-var commonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"}
+var CommonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"}
 
 // wikiRemoteURL returns accessible repository URL for wiki if exists.
 // Otherwise, it returns an empty string.
 func wikiRemoteURL(remote string) string {
 	remote = strings.TrimSuffix(remote, ".git")
-	for _, suffix := range commonWikiURLSuffixes {
+	for _, suffix := range CommonWikiURLSuffixes {
 		wikiURL := remote + suffix
 		if git.IsURLAccessible(time.Minute, wikiURL) {
 			return wikiURL
@@ -782,8 +782,8 @@ func wikiRemoteURL(remote string) string {
 }
 
 // MigrateRepository migrates a existing repository from other project hosting.
-func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository, error) {
-	repo, err := CreateRepository(doer, owner, CreateRepoOptionsLegacy{
+func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (repo *Repository, err error) {
+	repo, err = CreateRepository(doer, owner, CreateRepoOptionsLegacy{
 		Name:        opts.Name,
 		Description: opts.Description,
 		IsPrivate:   opts.IsPrivate,
@@ -792,11 +792,10 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 	})
 	if err != nil {
 		return nil, err
+	} else if repo == nil {
+		return nil, fmt.Errorf("create repository error: repo is nil")
 	}
 
-	repoPath := RepoPath(owner.Name, opts.Name)
-	wikiPath := WikiPath(owner.Name, opts.Name)
-
 	if owner.IsOrganization() {
 		t, err := owner.GetOwnerTeam()
 		if err != nil {
@@ -808,6 +807,8 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 	}
 
 	migrateTimeout := time.Duration(conf.Git.Timeout.Migrate) * time.Second
+	repoPath := repo.RepoPath()
+	wikiPath := repo.WikiPath()
 
 	RemoveAllWithNotice("Repository path erase before creation", repoPath)
 	if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneOptions{
@@ -815,13 +816,12 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 		Quiet:   true,
 		Timeout: migrateTimeout,
 	}); err != nil {
-		return repo, fmt.Errorf("clone: %v", err)
+		return nil, fmt.Errorf("clone: %v", err)
 	}
 
-	wikiRemotePath := wikiRemoteURL(opts.RemoteAddr)
-	if len(wikiRemotePath) > 0 {
+	if len(wikiPath) > 0 {
 		RemoveAllWithNotice("Repository wiki path erase before creation", wikiPath)
-		if err = git.Clone(wikiRemotePath, wikiPath, git.CloneOptions{
+		if err = git.Clone(wikiPath, wikiPath, git.CloneOptions{
 			Mirror:  true,
 			Quiet:   true,
 			Timeout: migrateTimeout,
@@ -1208,7 +1208,7 @@ func (err ErrReachLimitOfRepo) Error() string {
 
 // CreateRepository creates a repository for given user or organization.
 func CreateRepository(doer, owner *User, opts CreateRepoOptionsLegacy) (_ *Repository, err error) {
-	if !owner.canCreateRepo() {
+	if !owner.CanCreateRepo() {
 		return nil, ErrReachLimitOfRepo{Limit: owner.maxNumRepos()}
 	}
 
@@ -2522,7 +2522,7 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool, error) {
 
 // ForkRepository creates a fork of target repository under another user domain.
 func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string) (_ *Repository, err error) {
-	if !owner.canCreateRepo() {
+	if !owner.CanCreateRepo() {
 		return nil, ErrReachLimitOfRepo{Limit: owner.maxNumRepos()}
 	}
 
@@ -2551,7 +2551,8 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string)
 		return nil, err
 	}
 
-	repoPath := repo.repoPath(sess)
+	repoPath := repo.RepoPath()
+
 	RemoveAllWithNotice("Repository path erase before creation", repoPath)
 
 	_, stderr, err := process.ExecTimeout(10*time.Minute,

+ 2 - 2
internal/database/users.go

@@ -1637,8 +1637,8 @@ func (u *User) maxNumRepos() int {
 	return u.MaxRepoCreation
 }
 
-// canCreateRepo returns true if the user can create a repository.
-func (u *User) canCreateRepo() bool {
+// CanCreateRepo returns true if the user can create a repository.
+func (u *User) CanCreateRepo() bool {
 	if !u.IsActive {
 		return false
 	}

+ 23 - 6
internal/route/repo/pull.go

@@ -137,8 +137,24 @@ func ForkPost(c *context.Context, f form.CreateRepo) {
 		return
 	}
 
-	repo, err = database.ForkRepository(c.User, ctxUser, baseRepo, f.RepoName, f.Description)
-	if err != nil {
+	var errChannel = make(chan error, 1)
+	var repoChannel = make(chan *database.Repository, 1)
+
+	go func() {
+		repo, err = database.ForkRepository(c.User, ctxUser, baseRepo, f.RepoName, f.Description)
+		if err != nil {
+			errChannel <- err
+			close(repoChannel)
+			close(errChannel)
+		} else {
+			repoChannel <- repo
+			close(repoChannel)
+			close(errChannel)
+		}
+	}()
+
+	select {
+	case err := <-errChannel:
 		c.Data["Err_RepoName"] = true
 		switch {
 		case database.IsErrReachLimitOfRepo(err):
@@ -150,11 +166,12 @@ func ForkPost(c *context.Context, f form.CreateRepo) {
 		default:
 			c.Error(err, "fork repository")
 		}
-		return
+	case repo := <-repoChannel:
+		log.Trace("Repository forked from '%s' -> '%s'", baseRepo.FullName(), repo.FullName())
+		c.Redirect(repo.Link())
+	case <-time.After(5 * time.Second):
+		c.Redirect(conf.Server.Subpath + "/" + ctxUser.Name + "/" + f.RepoName)
 	}
-
-	log.Trace("Repository forked from '%s' -> '%s'", baseRepo.FullName(), repo.FullName())
-	c.Redirect(repo.Link())
 }
 
 func checkPullInfo(c *context.Context) *database.Issue {

+ 46 - 28
internal/route/repo/repo.go

@@ -10,6 +10,7 @@ import (
 	"path"
 	"path/filepath"
 	"strings"
+	"time"
 
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
@@ -196,38 +197,55 @@ func MigratePost(c *context.Context, f form.MigrateRepo) {
 		return
 	}
 
-	repo, err := database.MigrateRepository(c.User, ctxUser, database.MigrateRepoOptions{
-		Name:        f.RepoName,
-		Description: f.Description,
-		IsPrivate:   f.Private || conf.Repository.ForcePrivate,
-		IsUnlisted:  f.Unlisted,
-		IsMirror:    f.Mirror,
-		RemoteAddr:  remoteAddr,
-	})
-	if err == nil {
-		log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, f.RepoName)
-		c.Redirect(conf.Server.Subpath + "/" + ctxUser.Name + "/" + f.RepoName)
-		return
-	}
+	var errChannel = make(chan error, 1)
+	var repoChannel = make(chan *database.Repository, 1)
+
+	go func() {
+		repo, err := database.MigrateRepository(c.User, ctxUser, database.MigrateRepoOptions{
+			Name:        f.RepoName,
+			Description: f.Description,
+			IsPrivate:   f.Private || conf.Repository.ForcePrivate,
+			IsUnlisted:  f.Unlisted,
+			IsMirror:    f.Mirror,
+			RemoteAddr:  remoteAddr,
+		})
+		if err != nil {
+			if repo != nil {
+				if errDelete := database.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
+					log.Error("DeleteRepository: %v", errDelete)
+				}
+			}
 
-	if repo != nil {
-		if errDelete := database.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
-			log.Error("DeleteRepository: %v", errDelete)
+			log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, f.RepoName)
+
+			errChannel <- err
+			close(repoChannel)
+			close(errChannel)
+		} else {
+			repoChannel <- repo
+			close(repoChannel)
+			close(errChannel)
 		}
-	}
+	}()
+
+	select {
+	case err := <-errChannel:
+		if strings.Contains(err.Error(), "Authentication failed") ||
+			strings.Contains(err.Error(), "could not read Username") {
+			c.Data["Err_Auth"] = true
+			c.RenderWithErr(c.Tr("form.auth_failed", database.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f)
+		} else if strings.Contains(err.Error(), "fatal:") {
+			c.Data["Err_CloneAddr"] = true
+			c.RenderWithErr(c.Tr("repo.migrate.failed", database.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f)
+		} else {
+			handleCreateError(c, err, "MigratePost", MIGRATE, &f)
+		}
+	case repo := <-repoChannel:
+		c.Redirect(conf.Server.Subpath + "/" + repo.Owner.Name + "/" + repo.Name)
+	case <-time.After(5 * time.Second):
+		c.Redirect(conf.Server.Subpath + "/" + ctxUser.Name + "/" + f.RepoName)
 
-	if strings.Contains(err.Error(), "Authentication failed") ||
-		strings.Contains(err.Error(), "could not read Username") {
-		c.Data["Err_Auth"] = true
-		c.RenderWithErr(c.Tr("form.auth_failed", database.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f)
-		return
-	} else if strings.Contains(err.Error(), "fatal:") {
-		c.Data["Err_CloneAddr"] = true
-		c.RenderWithErr(c.Tr("repo.migrate.failed", database.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f)
-		return
 	}
-
-	handleCreateError(c, err, "MigratePost", MIGRATE, &f)
 }
 
 func Action(c *context.Context) {

+ 1 - 1
internal/route/repo/view.go

@@ -259,7 +259,7 @@ func Home(c *context.Context) {
 		var err error
 		c.Repo.CommitsCount, err = c.Repo.Commit.CommitsCount()
 		if err != nil {
-			c.Error(err, "count commits")
+			c.Wait(err, "The Git repository has not been loaded yet, please refresh and try again later!", true)
 			return
 		}
 		c.Data["CommitsCount"] = c.Repo.CommitsCount

+ 4 - 2
templates/base/head.tmpl

@@ -110,20 +110,22 @@
 
 								{{if .IsLogged}}
 									<div class="right menu">
-										{{if .IsAdmin}}
+										{{if .IsCanCreate}}
 											<div class="ui dropdown head link jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted">
 												<span class="text">
 													<i class="octicon octicon-plus"><span class="sr-only">{{.i18n.Tr "create_new"}}</span></i>
 													<i class="octicon octicon-triangle-down"></i>
 												</span>
 												<div class="menu">
+													{{if .IsCanCreateRepo}}
 													<a class="item" href="{{AppSubURL}}/repo/create">
 														<i class="octicon octicon-plus"></i> {{.i18n.Tr "new_repo"}}
 													</a>
 													<a class="item" href="{{AppSubURL}}/repo/migrate">
 														<i class="octicon octicon-repo-clone"></i> {{.i18n.Tr "new_migrate"}}
 													</a>
-													{{if .LoggedUser.CanCreateOrganization}}
+													{{end}}
+													{{if .IsCanCreateOrg}}
 														<a class="item" href="{{AppSubURL}}/org/create">
 															<i class="octicon octicon-organization"></i> {{.i18n.Tr "new_org"}}
 														</a>

+ 7 - 3
templates/status/404.tmpl

@@ -1,8 +1,12 @@
 {{template "base/head" .}}
-<div class="ui container center">
-	<p style="margin-top: 100px"><img src="{{AppSubURL}}/img/404.png" alt="404"/></p>
+<div class="ui container center" style="padding-top: 30px">
+	<h1 style="font-size: 7em; margin-bottom: 0.5em; color: #cc0000;">404</h1>
+	<h2 style="font-size: 2em; color: #666">Oops! The page you're looking for isn't here.</h2>
+
 	<div class="ui divider"></div>
 	<br>
-	<p>If you think this is an error, please contact with me <a href="https://github.com/SongZihuan/HuanGogs/issues"> /HuanGogs/issues </a>.</p>
+
+	{{if .IsAdmin}}<p>Application Version: {{AppVer}}</p>{{end}}
+	<p>If you think this is an error, please <a href="https://github.com/SongZihuan/huan-gogs/issues">  contact with me </a>.</p>
 </div>
 {{template "base/footer" .}}

+ 6 - 4
templates/status/500.tmpl

@@ -1,8 +1,10 @@
 {{template "base/head" .}}
-<div class="ui container center">
-	<p style="margin-top: 100px"><img src="{{AppSubURL}}/img/500.png" alt="500"/></p>
-	<div class="ui divider"></div>
-	<br>
+<div class="ui container center" style="padding-top: 30px">
+	<h1 style="font-size: 7em; margin-bottom: 0.5em; color: #cc0000;">500</h1>
+	<h2 style="font-size: 2em; color: #666">Oops! Something went wrong on our end.</h2>
+
+	<hr>
+
 	{{if .ErrorMsg}}<p>An error has occurred : {{.ErrorMsg}}</p>{{end}}
 	{{if .IsAdmin}}<p>Application Version: {{AppVer}}</p>{{end}}
 </div>

+ 17 - 0
templates/status/wait.tmpl

@@ -0,0 +1,17 @@
+{{template "base/head" .}}
+<div class="ui container center" style="padding-top: 30px">
+	<h1 style="font-size: 7em; margin-bottom: 0.5em; color: #cc0000;">Wait a moment!</h1>
+	<h2 style="font-size: 2em; color: #666">{{if .Flash}}{{.Flash}}{{end}}</h2>
+
+	<div class="ui divider"></div>
+	<br>
+
+	{{if .IsAdmin}}<p>Application Version: {{AppVer}}</p>{{end}}
+	<p>If you think this is an error, please <a href="https://github.com/SongZihuan/huan-gogs/issues">  contact with me </a>.</p>
+</div>
+{{if .Refresh}}
+<script type="module">
+	setInterval(() => location.reload(), 5000); // 5秒 = 300000毫秒
+</script>
+{{end}}
+{{template "base/footer" .}}