* Store task errors following migrations and display them When migrate tasks fail store the error in the task table and ensure that they show on the status page. Fix #13242 Signed-off-by: Andrew Thornton <art27@cantab.net> * Update web_src/js/index.js * Hide the failed first Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: zeripath <art27@cantab.net>tags/v1.13.0-rc2
@@ -147,6 +147,27 @@ func GetMigratingTask(repoID int64) (*Task, error) { | |||
return &task, nil | |||
} | |||
// GetMigratingTaskByID returns the migrating task by repo's id | |||
func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, error) { | |||
var task = Task{ | |||
ID: id, | |||
DoerID: doerID, | |||
Type: structs.TaskTypeMigrateRepo, | |||
} | |||
has, err := x.Get(&task) | |||
if err != nil { | |||
return nil, nil, err | |||
} else if !has { | |||
return nil, nil, ErrTaskDoesNotExist{id, 0, task.Type} | |||
} | |||
var opts migration.MigrateOptions | |||
if err := json.Unmarshal([]byte(task.PayloadContent), &opts); err != nil { | |||
return nil, nil, err | |||
} | |||
return &task, &opts, nil | |||
} | |||
// FindTaskOptions find all tasks | |||
type FindTaskOptions struct { | |||
Status int |
@@ -20,7 +20,7 @@ import ( | |||
"code.gitea.io/gitea/modules/util" | |||
) | |||
func handleCreateError(owner *models.User, err error, name string) error { | |||
func handleCreateError(owner *models.User, err error) error { | |||
switch { | |||
case models.IsErrReachLimitOfRepo(err): | |||
return fmt.Errorf("You have already reached your limit of %d repositories", owner.MaxCreationLimit()) | |||
@@ -38,8 +38,8 @@ func handleCreateError(owner *models.User, err error, name string) error { | |||
func runMigrateTask(t *models.Task) (err error) { | |||
defer func() { | |||
if e := recover(); e != nil { | |||
err = fmt.Errorf("PANIC whilst trying to do migrate task: %v\nStacktrace: %v", err, log.Stack(2)) | |||
log.Critical("PANIC during runMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d]: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, err) | |||
err = fmt.Errorf("PANIC whilst trying to do migrate task: %v", e) | |||
log.Critical("PANIC during runMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d]: %v\nStacktrace: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, e, log.Stack(2)) | |||
} | |||
if err == nil { | |||
@@ -55,7 +55,8 @@ func runMigrateTask(t *models.Task) (err error) { | |||
t.EndTime = timeutil.TimeStampNow() | |||
t.Status = structs.TaskStatusFailed | |||
t.Errors = err.Error() | |||
if err := t.UpdateCols("status", "errors", "end_time"); err != nil { | |||
t.RepoID = 0 | |||
if err := t.UpdateCols("status", "errors", "repo_id", "end_time"); err != nil { | |||
log.Error("Task UpdateCols failed: %v", err) | |||
} | |||
@@ -66,8 +67,8 @@ func runMigrateTask(t *models.Task) (err error) { | |||
} | |||
}() | |||
if err := t.LoadRepo(); err != nil { | |||
return err | |||
if err = t.LoadRepo(); err != nil { | |||
return | |||
} | |||
// if repository is ready, then just finsih the task | |||
@@ -75,33 +76,35 @@ func runMigrateTask(t *models.Task) (err error) { | |||
return nil | |||
} | |||
if err := t.LoadDoer(); err != nil { | |||
return err | |||
if err = t.LoadDoer(); err != nil { | |||
return | |||
} | |||
if err := t.LoadOwner(); err != nil { | |||
return err | |||
if err = t.LoadOwner(); err != nil { | |||
return | |||
} | |||
t.StartTime = timeutil.TimeStampNow() | |||
t.Status = structs.TaskStatusRunning | |||
if err := t.UpdateCols("start_time", "status"); err != nil { | |||
return err | |||
if err = t.UpdateCols("start_time", "status"); err != nil { | |||
return | |||
} | |||
var opts *migration.MigrateOptions | |||
opts, err = t.MigrateConfig() | |||
if err != nil { | |||
return err | |||
return | |||
} | |||
opts.MigrateToRepoID = t.RepoID | |||
repo, err := migrations.MigrateRepository(graceful.GetManager().HammerContext(), t.Doer, t.Owner.Name, *opts) | |||
var repo *models.Repository | |||
repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), t.Doer, t.Owner.Name, *opts) | |||
if err == nil { | |||
log.Trace("Repository migrated [%d]: %s/%s", repo.ID, t.Owner.Name, repo.Name) | |||
return nil | |||
return | |||
} | |||
if models.IsErrRepoAlreadyExist(err) { | |||
return errors.New("The repository name is already used") | |||
err = errors.New("The repository name is already used") | |||
return | |||
} | |||
// remoteAddr may contain credentials, so we sanitize it | |||
@@ -113,5 +116,7 @@ func runMigrateTask(t *models.Task) (err error) { | |||
return fmt.Errorf("Migration failed: %v", err.Error()) | |||
} | |||
return handleCreateError(t.Owner, err, "MigratePost") | |||
// do not be tempted to coalesce this line with the return | |||
err = handleCreateError(t.Owner, err) | |||
return | |||
} |
@@ -402,19 +402,3 @@ func Download(ctx *context.Context) { | |||
ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext) | |||
} | |||
// Status returns repository's status | |||
func Status(ctx *context.Context) { | |||
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) | |||
if err != nil { | |||
ctx.JSON(500, map[string]interface{}{ | |||
"err": err, | |||
}) | |||
return | |||
} | |||
ctx.JSON(200, map[string]interface{}{ | |||
"status": ctx.Repo.Repository.Status, | |||
"err": task.Errors, | |||
}) | |||
} |
@@ -479,6 +479,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/forgot_password", user.ForgotPasswd) | |||
m.Post("/forgot_password", user.ForgotPasswdPost) | |||
m.Post("/logout", user.SignOut) | |||
m.Get("/task/:task", user.TaskStatus) | |||
}) | |||
// ***** END: User ***** | |||
@@ -986,8 +987,6 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/archive/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.Download) | |||
m.Get("/status", reqRepoCodeReader, repo.Status) | |||
m.Group("/branches", func() { | |||
m.Get("", repo.Branches) | |||
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) |
@@ -0,0 +1,30 @@ | |||
// Copyright 2020 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package user | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/context" | |||
) | |||
// TaskStatus returns task's status | |||
func TaskStatus(ctx *context.Context) { | |||
task, opts, err := models.GetMigratingTaskByID(ctx.ParamsInt64("task"), ctx.User.ID) | |||
if err != nil { | |||
ctx.JSON(500, map[string]interface{}{ | |||
"err": err, | |||
}) | |||
return | |||
} | |||
ctx.JSON(200, map[string]interface{}{ | |||
"status": task.Status, | |||
"err": task.Errors, | |||
"repo-id": task.RepoID, | |||
"repo-name": opts.RepoName, | |||
"start": task.StartTime, | |||
"end": task.EndTime, | |||
}) | |||
} |
@@ -7,11 +7,16 @@ | |||
{{template "base/alert" .}} | |||
<div class="home"> | |||
<div class="ui stackable middle very relaxed page grid"> | |||
<div id="repo_migrating" class="sixteen wide center aligned centered column" repo="{{.Repo.Repository.FullName}}"> | |||
<div id="repo_migrating" class="sixteen wide center aligned centered column" task="{{.MigrateTask.ID}}"> | |||
<div> | |||
<img src="{{StaticUrlPrefix}}/img/loading.png"/> | |||
</div> | |||
</div> | |||
<div id="repo_migrating_failed_image" class="sixteen wide center aligned centered column" style="display: none;"> | |||
<div> | |||
<img src="{{StaticUrlPrefix}}/img/failed.png"/> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui stackable middle very relaxed page grid"> | |||
<div class="sixteen wide center aligned centered column"> | |||
@@ -20,6 +25,7 @@ | |||
</div> | |||
<div id="repo_migrating_failed"> | |||
<p>{{.i18n.Tr "repo.migrate.migrating_failed" .CloneAddr | Safe}}</p> | |||
<p id="repo_migrating_failed_error"></p> | |||
</div> | |||
</div> | |||
</div> |
@@ -191,25 +191,32 @@ function updateIssuesMeta(url, action, issueIds, elementId) { | |||
function initRepoStatusChecker() { | |||
const migrating = $('#repo_migrating'); | |||
$('#repo_migrating_failed').hide(); | |||
$('#repo_migrating_failed_image').hide(); | |||
if (migrating) { | |||
const repo_name = migrating.attr('repo'); | |||
if (typeof repo_name === 'undefined') { | |||
const task = migrating.attr('task'); | |||
if (typeof task === 'undefined') { | |||
return; | |||
} | |||
$.ajax({ | |||
type: 'GET', | |||
url: `${AppSubUrl}/${repo_name}/status`, | |||
url: `${AppSubUrl}/user/task/${task}`, | |||
data: { | |||
_csrf: csrf, | |||
}, | |||
complete(xhr) { | |||
if (xhr.status === 200) { | |||
if (xhr.responseJSON) { | |||
if (xhr.responseJSON.status === 0) { | |||
if (xhr.responseJSON.status === 4) { | |||
window.location.reload(); | |||
return; | |||
} else if (xhr.responseJSON.status === 3) { | |||
$('#repo_migrating_progress').hide(); | |||
$('#repo_migrating').hide(); | |||
$('#repo_migrating_failed').show(); | |||
$('#repo_migrating_failed_image').show(); | |||
$('#repo_migrating_failed_error').text(xhr.responseJSON.err); | |||
return; | |||
} | |||
setTimeout(() => { | |||
initRepoStatusChecker(); | |||
}, 2000); | |||
@@ -217,7 +224,9 @@ function initRepoStatusChecker() { | |||
} | |||
} | |||
$('#repo_migrating_progress').hide(); | |||
$('#repo_migrating').hide(); | |||
$('#repo_migrating_failed').show(); | |||
$('#repo_migrating_failed_image').show(); | |||
} | |||
}); | |||
} |