More pleasantly handle broken or missing git repositories (#17747)

* More pleasantly handle broken or missing git repositories

In #17742 it was noted that there a completely invalid git repository underlying a
repo on gitea.com. This happened due to a problem during a migration however, it
is not beyond the realms of possibility that a corruption could occur to another
user.

This PR adds a check to RepoAssignment that will detect if a repository loading has
failed due to an absent git repository. It will then show a page suggesting the user
contacts the administrator or deletes the repository.

Fix #17742

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Update options/locale/locale_en-US.ini

Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
tokarchuk/v1.17
zeripath 3 years ago committed by GitHub
parent baed01f247
commit 1dbc58f742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      models/repo.go
  2. 34
      modules/context/repo.go
  3. 1
      options/locale/locale_en-US.ini
  4. 8
      routers/private/serv.go
  5. 6
      templates/repo/empty.tmpl
  6. 12
      templates/repo/header.tmpl

@ -146,6 +146,7 @@ const (
RepositoryReady RepositoryStatus = iota // a normal repository RepositoryReady RepositoryStatus = iota // a normal repository
RepositoryBeingMigrated // repository is migrating RepositoryBeingMigrated // repository is migrating
RepositoryPendingTransfer // repository pending in ownership transfer state RepositoryPendingTransfer // repository pending in ownership transfer state
RepositoryBroken // repository is in a permanently broken state
) )
// TrustModelType defines the types of trust model for this repository // TrustModelType defines the types of trust model for this repository
@ -289,6 +290,11 @@ func (repo *Repository) IsBeingCreated() bool {
return repo.IsBeingMigrated() return repo.IsBeingMigrated()
} }
// IsBroken indicates that repository is broken
func (repo *Repository) IsBroken() bool {
return repo.Status == RepositoryBroken
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (repo *Repository) AfterLoad() { func (repo *Repository) AfterLoad() {
// FIXME: use models migration to solve all at once. // FIXME: use models migration to solve all at once.

@ -522,14 +522,30 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
} }
} }
isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink || ctx.Link == ctx.Repo.RepoLink+"/settings" || strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/")
// Disable everything when the repo is being created // Disable everything when the repo is being created
if ctx.Repo.Repository.IsBeingCreated() { if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
return return
} }
gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName)) gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
if err != nil { if err != nil {
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
ctx.Repo.Repository.Status = models.RepositoryBroken
ctx.Repo.Repository.IsEmpty = true
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
// Only allow access to base of repo or settings
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
return
}
ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err) ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
return return
} }
@ -551,6 +567,17 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
tags, err := ctx.Repo.GitRepo.GetTags(0, 0) tags, err := ctx.Repo.GitRepo.GetTags(0, 0)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "fatal: not a git repository ") {
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
ctx.Repo.Repository.Status = models.RepositoryBroken
ctx.Repo.Repository.IsEmpty = true
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
// Only allow access to base of repo or settings
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
return
}
ctx.ServerError("GetTags", err) ctx.ServerError("GetTags", err)
return return
} }
@ -919,6 +946,11 @@ func UnitTypes() func(ctx *Context) {
// IssueTemplatesFromDefaultBranch checks for issue templates in the repo's default branch // IssueTemplatesFromDefaultBranch checks for issue templates in the repo's default branch
func (ctx *Context) IssueTemplatesFromDefaultBranch() []api.IssueTemplate { func (ctx *Context) IssueTemplatesFromDefaultBranch() []api.IssueTemplate {
var issueTemplates []api.IssueTemplate var issueTemplates []api.IssueTemplate
if ctx.Repo.Repository.IsEmpty {
return issueTemplates
}
if ctx.Repo.Commit == nil { if ctx.Repo.Commit == nil {
var err error var err error
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)

@ -946,6 +946,7 @@ clone_this_repo = Clone this repository
create_new_repo_command = Creating a new repository on the command line create_new_repo_command = Creating a new repository on the command line
push_exist_repo = Pushing an existing repository from the command line push_exist_repo = Pushing an existing repository from the command line
empty_message = This repository does not contain any content. 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.
code = Code code = Code
code.desc = Access source code, files, commits and branches. code.desc = Access source code, files, commits and branches.

@ -162,6 +162,14 @@ func ServCommand(ctx *context.PrivateContext) {
return return
} }
if repo.IsBroken() {
ctx.JSON(http.StatusInternalServerError, private.ErrServCommand{
Results: results,
Err: "Repository is in a broken state",
})
return
}
// We can shortcut at this point if the repo is a mirror // We can shortcut at this point if the repo is a mirror
if mode > models.AccessModeRead && repo.IsMirror { if mode > models.AccessModeRead && repo.IsMirror {
ctx.JSON(http.StatusForbidden, private.ErrServCommand{ ctx.JSON(http.StatusForbidden, private.ErrServCommand{

@ -10,7 +10,11 @@
{{.i18n.Tr "repo.archive.title"}} {{.i18n.Tr "repo.archive.title"}}
</div> </div>
{{end}} {{end}}
{{if .CanWriteCode}} {{if .Repository.IsBroken}}
<div class="ui segment center">
{{.i18n.Tr "repo.broken_message"}}
</div>
{{else if .CanWriteCode}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "repo.quick_guide"}} {{.i18n.Tr "repo.quick_guide"}}
</h4> </h4>

@ -40,7 +40,7 @@
{{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullName}}</a></div>{{end}} {{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullName}}</a></div>{{end}}
{{if .IsGenerated}}<div class="fork-flag">{{$.i18n.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{.TemplateRepo.FullName}}</a></div>{{end}} {{if .IsGenerated}}<div class="fork-flag">{{$.i18n.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{.TemplateRepo.FullName}}</a></div>{{end}}
</div> </div>
{{if not .IsBeingCreated}} {{if not (or .IsBeingCreated .IsBroken)}}
<div class="repo-buttons"> <div class="repo-buttons">
{{if $.RepoTransfer}} {{if $.RepoTransfer}}
<form method="post" action="{{$.RepoLink}}/action/accept_transfer?redirect_to={{$.RepoLink}}"> <form method="post" action="{{$.RepoLink}}/action/accept_transfer?redirect_to={{$.RepoLink}}">
@ -100,7 +100,7 @@
</div><!-- end container --> </div><!-- end container -->
{{end}} {{end}}
<div class="ui tabs container"> <div class="ui tabs container">
{{if not .Repository.IsBeingCreated}} {{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}}
<div class="ui tabular stackable menu navbar"> <div class="ui tabular stackable menu navbar">
{{if .Permission.CanRead $.UnitTypeCode}} {{if .Permission.CanRead $.UnitTypeCode}}
<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL}}{{end}}"> <a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL}}{{end}}">
@ -172,6 +172,14 @@
</div> </div>
{{end}} {{end}}
</div> </div>
{{else if .Permission.IsAdmin}}
<div class="ui tabular stackable menu navbar">
<div class="right menu">
<a class="{{if .PageIsSettings}}active{{end}} item" href="{{.RepoLink}}/settings">
{{svg "octicon-tools"}} {{.i18n.Tr "repo.settings"}}
</a>
</div>
</div>
{{end}} {{end}}
</div> </div>
<div class="ui tabs divider"></div> <div class="ui tabs divider"></div>

Loading…
Cancel
Save