Skip to content

Commit f6e029e

Browse files
wxiaoguangyardenshohamsilverwindGiteaBot
authoredMay 11, 2023
1 parent 58dfaf3 commit f6e029e

File tree

10 files changed

+128
-106
lines changed

10 files changed

+128
-106
lines changed
 

‎models/admin/task.go

+1-32
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import (
1717
"code.gitea.io/gitea/modules/structs"
1818
"code.gitea.io/gitea/modules/timeutil"
1919
"code.gitea.io/gitea/modules/util"
20-
21-
"xorm.io/builder"
2220
)
2321

2422
// Task represents a task
@@ -35,7 +33,7 @@ type Task struct {
3533
StartTime timeutil.TimeStamp
3634
EndTime timeutil.TimeStamp
3735
PayloadContent string `xorm:"TEXT"`
38-
Message string `xorm:"TEXT"` // if task failed, saved the error reason
36+
Message string `xorm:"TEXT"` // if task failed, saved the error reason, it could be a JSON string of TranslatableMessage or a plain message
3937
Created timeutil.TimeStamp `xorm:"created"`
4038
}
4139

@@ -185,14 +183,6 @@ func GetMigratingTask(repoID int64) (*Task, error) {
185183
return &task, nil
186184
}
187185

188-
// HasFinishedMigratingTask returns if a finished migration task exists for the repo.
189-
func HasFinishedMigratingTask(repoID int64) (bool, error) {
190-
return db.GetEngine(db.DefaultContext).
191-
Where("repo_id=? AND type=? AND status=?", repoID, structs.TaskTypeMigrateRepo, structs.TaskStatusFinished).
192-
Table("task").
193-
Exist()
194-
}
195-
196186
// GetMigratingTaskByID returns the migrating task by repo's id
197187
func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, error) {
198188
task := Task{
@@ -214,27 +204,6 @@ func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, e
214204
return &task, &opts, nil
215205
}
216206

217-
// FindTaskOptions find all tasks
218-
type FindTaskOptions struct {
219-
Status int
220-
}
221-
222-
// ToConds generates conditions for database operation.
223-
func (opts FindTaskOptions) ToConds() builder.Cond {
224-
cond := builder.NewCond()
225-
if opts.Status >= 0 {
226-
cond = cond.And(builder.Eq{"status": opts.Status})
227-
}
228-
return cond
229-
}
230-
231-
// FindTasks find all tasks
232-
func FindTasks(opts FindTaskOptions) ([]*Task, error) {
233-
tasks := make([]*Task, 0, 10)
234-
err := db.GetEngine(db.DefaultContext).Where(opts.ToConds()).Find(&tasks)
235-
return tasks, err
236-
}
237-
238207
// CreateTask creates a task on database
239208
func CreateTask(task *Task) error {
240209
return db.Insert(db.DefaultContext, task)

‎modules/structs/task.go

+3-6
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ package structs
66
// TaskType defines task type
77
type TaskType int
88

9-
// all kinds of task types
10-
const (
11-
TaskTypeMigrateRepo TaskType = iota // migrate repository from external or local disk
12-
)
9+
const TaskTypeMigrateRepo TaskType = iota // migrate repository from external or local disk
1310

1411
// Name returns the task type name
1512
func (taskType TaskType) Name() string {
@@ -25,9 +22,9 @@ type TaskStatus int
2522

2623
// enumerate all the kinds of task status
2724
const (
28-
TaskStatusQueue TaskStatus = iota // 0 task is queue
25+
TaskStatusQueued TaskStatus = iota // 0 task is queued
2926
TaskStatusRunning // 1 task is running
30-
TaskStatusStopped // 2 task is stopped
27+
TaskStatusStopped // 2 task is stopped (never used)
3128
TaskStatusFailed // 3 task is failed
3229
TaskStatusFinished // 4 task is finished
3330
)

‎options/locale/locale_en-US.ini

+3-1
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ migrated_from_fake = Migrated From %[1]s
10381038
migrate.migrate = Migrate From %s
10391039
migrate.migrating = Migrating from <b>%s</b> ...
10401040
migrate.migrating_failed = Migrating from <b>%s</b> failed.
1041-
migrate.migrating_failed.error = Error: %s
1041+
migrate.migrating_failed.error = Failed to migrate: %s
10421042
migrate.migrating_failed_no_addr = Migration failed.
10431043
migrate.github.description = Migrate data from github.com or other GitHub instances.
10441044
migrate.git.description = Migrate a repository only from any Git service.
@@ -1055,6 +1055,8 @@ migrate.migrating_labels = Migrating Labels
10551055
migrate.migrating_releases = Migrating Releases
10561056
migrate.migrating_issues = Migrating Issues
10571057
migrate.migrating_pulls = Migrating Pull Requests
1058+
migrate.cancel_migrating_title = Cancel Migration
1059+
migrate.cancel_migrating_confirm = Do you want to cancel this migration?
10581060

10591061
mirror_from = mirror of
10601062
forked_from = forked from

‎routers/web/repo/migrate.go

+18
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111

1212
"code.gitea.io/gitea/models"
13+
admin_model "code.gitea.io/gitea/models/admin"
1314
"code.gitea.io/gitea/models/db"
1415
repo_model "code.gitea.io/gitea/models/repo"
1516
user_model "code.gitea.io/gitea/models/user"
@@ -257,3 +258,20 @@ func setMigrationContextData(ctx *context.Context, serviceType structs.GitServic
257258
ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
258259
ctx.Data["service"] = serviceType
259260
}
261+
262+
func MigrateCancelPost(ctx *context.Context) {
263+
migratingTask, err := admin_model.GetMigratingTask(ctx.Repo.Repository.ID)
264+
if err != nil {
265+
log.Error("GetMigratingTask: %v", err)
266+
ctx.Redirect(ctx.Repo.Repository.Link())
267+
return
268+
}
269+
if migratingTask.Status == structs.TaskStatusRunning {
270+
taskUpdate := &admin_model.Task{ID: migratingTask.ID, Status: structs.TaskStatusFailed, Message: "canceled"}
271+
if err = taskUpdate.UpdateCols("status", "message"); err != nil {
272+
ctx.ServerError("task.UpdateCols", err)
273+
return
274+
}
275+
}
276+
ctx.Redirect(ctx.Repo.Repository.Link())
277+
}

‎routers/web/web.go

+1
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,7 @@ func registerRoutes(m *web.Route) {
939939
addSettingsRunnersRoutes()
940940
addSettingsSecretsRoutes()
941941
}, actions.MustEnableActions)
942+
m.Post("/migrate/cancel", repo.MigrateCancelPost) // this handler must be under "settings", otherwise this incomplete repo can't be accessed
942943
}, ctxDataSet("PageIsRepoSettings", true, "LFSStartServer", setting.LFS.StartServer))
943944
}, reqSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoAdmin, context.RepoRef())
944945

‎services/migrations/gitea_uploader.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -923,9 +923,8 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
923923
func (g *GiteaLocalUploader) Rollback() error {
924924
if g.repo != nil && g.repo.ID > 0 {
925925
g.gitRepo.Close()
926-
if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil {
927-
return err
928-
}
926+
927+
// do not delete the repository, otherwise the end users won't be able to see the last error message
929928
}
930929
return nil
931930
}

‎services/task/migrate.go

+31-18
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
"errors"
88
"fmt"
99
"strings"
10+
"time"
1011

11-
"code.gitea.io/gitea/models"
1212
admin_model "code.gitea.io/gitea/models/admin"
1313
"code.gitea.io/gitea/models/db"
1414
repo_model "code.gitea.io/gitea/models/repo"
@@ -28,13 +28,13 @@ import (
2828
func handleCreateError(owner *user_model.User, err error) error {
2929
switch {
3030
case repo_model.IsErrReachLimitOfRepo(err):
31-
return fmt.Errorf("You have already reached your limit of %d repositories", owner.MaxCreationLimit())
31+
return fmt.Errorf("you have already reached your limit of %d repositories", owner.MaxCreationLimit())
3232
case repo_model.IsErrRepoAlreadyExist(err):
33-
return errors.New("The repository name is already used")
33+
return errors.New("the repository name is already used")
3434
case db.IsErrNameReserved(err):
35-
return fmt.Errorf("The repository name '%s' is reserved", err.(db.ErrNameReserved).Name)
35+
return fmt.Errorf("the repository name '%s' is reserved", err.(db.ErrNameReserved).Name)
3636
case db.IsErrNamePatternNotAllowed(err):
37-
return fmt.Errorf("The pattern '%s' is not allowed in a repository name", err.(db.ErrNamePatternNotAllowed).Pattern)
37+
return fmt.Errorf("the pattern '%s' is not allowed in a repository name", err.(db.ErrNamePatternNotAllowed).Pattern)
3838
default:
3939
return err
4040
}
@@ -57,22 +57,17 @@ func runMigrateTask(t *admin_model.Task) (err error) {
5757
log.Error("FinishMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d] failed: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, err)
5858
}
5959

60+
log.Error("runMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d] failed: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, err)
61+
6062
t.EndTime = timeutil.TimeStampNow()
6163
t.Status = structs.TaskStatusFailed
6264
t.Message = err.Error()
63-
// Ensure that the repo loaded before we zero out the repo ID from the task - thus ensuring that we can delete it
64-
_ = t.LoadRepo()
6565

66-
t.RepoID = 0
67-
if err := t.UpdateCols("status", "errors", "repo_id", "end_time"); err != nil {
66+
if err := t.UpdateCols("status", "message", "end_time"); err != nil {
6867
log.Error("Task UpdateCols failed: %v", err)
6968
}
7069

71-
if t.Repo != nil {
72-
if errDelete := models.DeleteRepository(t.Doer, t.OwnerID, t.Repo.ID); errDelete != nil {
73-
log.Error("DeleteRepository: %v", errDelete)
74-
}
75-
}
70+
// then, do not delete the repository, otherwise the users won't be able to see the last error
7671
}()
7772

7873
if err = t.LoadRepo(); err != nil {
@@ -100,7 +95,7 @@ func runMigrateTask(t *admin_model.Task) (err error) {
10095
opts.MigrateToRepoID = t.RepoID
10196

10297
pm := process.GetManager()
103-
ctx, _, finished := pm.AddContext(graceful.GetManager().ShutdownContext(), fmt.Sprintf("MigrateTask: %s/%s", t.Owner.Name, opts.RepoName))
98+
ctx, cancel, finished := pm.AddContext(graceful.GetManager().ShutdownContext(), fmt.Sprintf("MigrateTask: %s/%s", t.Owner.Name, opts.RepoName))
10499
defer finished()
105100

106101
t.StartTime = timeutil.TimeStampNow()
@@ -109,6 +104,23 @@ func runMigrateTask(t *admin_model.Task) (err error) {
109104
return
110105
}
111106

107+
// check whether the task should be canceled, this goroutine is also managed by process manager
108+
go func() {
109+
for {
110+
select {
111+
case <-time.After(2 * time.Second):
112+
case <-ctx.Done():
113+
return
114+
}
115+
task, _ := admin_model.GetMigratingTask(t.RepoID)
116+
if task != nil && task.Status != structs.TaskStatusRunning {
117+
log.Debug("MigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d] is canceled due to status is not 'running'", t.ID, t.DoerID, t.RepoID, t.OwnerID)
118+
cancel()
119+
return
120+
}
121+
}
122+
}()
123+
112124
t.Repo, err = migrations.MigrateRepository(ctx, t.Doer, t.Owner.Name, *opts, func(format string, args ...interface{}) {
113125
message := admin_model.TranslatableMessage{
114126
Format: format,
@@ -118,23 +130,24 @@ func runMigrateTask(t *admin_model.Task) (err error) {
118130
t.Message = string(bs)
119131
_ = t.UpdateCols("message")
120132
})
133+
121134
if err == nil {
122135
log.Trace("Repository migrated [%d]: %s/%s", t.Repo.ID, t.Owner.Name, t.Repo.Name)
123136
return
124137
}
125138

126139
if repo_model.IsErrRepoAlreadyExist(err) {
127-
err = errors.New("The repository name is already used")
140+
err = errors.New("the repository name is already used")
128141
return
129142
}
130143

131144
// remoteAddr may contain credentials, so we sanitize it
132145
err = util.SanitizeErrorCredentialURLs(err)
133146
if strings.Contains(err.Error(), "Authentication failed") ||
134147
strings.Contains(err.Error(), "could not read Username") {
135-
return fmt.Errorf("Authentication failed: %w", err)
148+
return fmt.Errorf("authentication failed: %w", err)
136149
} else if strings.Contains(err.Error(), "fatal:") {
137-
return fmt.Errorf("Migration failed: %w", err)
150+
return fmt.Errorf("migration failed: %w", err)
138151
}
139152

140153
// do not be tempted to coalesce this line with the return

‎services/task/task.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*adm
9595
DoerID: doer.ID,
9696
OwnerID: u.ID,
9797
Type: structs.TaskTypeMigrateRepo,
98-
Status: structs.TaskStatusQueue,
98+
Status: structs.TaskStatusQueued,
9999
PayloadContent: string(bs),
100100
}
101101

‎templates/repo/migrate/migrating.tmpl

+21-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{{template "base/alert" .}}
88
<div class="home">
99
<div class="ui stackable middle very relaxed page grid">
10-
<div id="repo_migrating" class="sixteen wide center aligned centered column" task="{{.MigrateTask.ID}}">
10+
<div id="repo_migrating" class="sixteen wide center aligned centered column" data-migrating-task-id="{{.MigrateTask.ID}}">
1111
<div>
1212
<img src="{{AssetUrlPrefix}}/img/loading.png">
1313
</div>
@@ -32,10 +32,14 @@
3232
{{end}}
3333
<p id="repo_migrating_failed_error"></p>
3434
</div>
35-
{{if and .Failed .Permission.IsAdmin}}
35+
{{if .Permission.IsAdmin}}
3636
<div class="ui divider"></div>
3737
<div class="item">
38+
{{if .Failed}}
3839
<button class="ui basic red show-modal button" data-modal="#delete-repo-modal">{{.locale.Tr "repo.settings.delete"}}</button>
40+
{{else}}
41+
<button class="ui basic red show-modal button" data-modal="#cancel-repo-modal">{{.locale.Tr "cancel"}}</button>
42+
{{end}}
3943
</div>
4044
{{end}}
4145
</div>
@@ -45,6 +49,7 @@
4549
</div>
4650
</div>
4751
</div>
52+
4853
<div class="ui small modal" id="delete-repo-modal">
4954
<div class="header">
5055
{{.locale.Tr "repo.settings.delete"}}
@@ -78,4 +83,18 @@
7883
</form>
7984
</div>
8085
</div>
86+
87+
<div class="ui g-modal-confirm modal" id="cancel-repo-modal">
88+
<div class="header">
89+
{{.locale.Tr "repo.migrate.cancel_migrating_title"}}
90+
</div>
91+
<form action="{{.Link}}/settings/migrate/cancel" method="post">
92+
{{.CsrfTokenHtml}}
93+
<div class="content">
94+
{{.locale.Tr "repo.migrate.cancel_migrating_confirm"}}
95+
</div>
96+
{{template "base/modal_actions_confirm" .}}
97+
</form>
98+
</div>
99+
81100
{{template "base/footer" .}}

‎web_src/js/features/repo-migrate.js

+47-43
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,55 @@
11
import $ from 'jquery';
22
import {hideElem, showElem} from '../utils/dom.js';
33

4-
const {appSubUrl, csrfToken} = window.config;
4+
const {appSubUrl} = window.config;
55

66
export function initRepoMigrationStatusChecker() {
7-
const migrating = $('#repo_migrating');
8-
hideElem($('#repo_migrating_failed'));
9-
hideElem($('#repo_migrating_failed_image'));
10-
hideElem($('#repo_migrating_progress_message'));
11-
if (migrating) {
12-
const task = migrating.attr('task');
13-
if (task === undefined) {
14-
return;
7+
const $repoMigrating = $('#repo_migrating');
8+
if (!$repoMigrating.length) return;
9+
10+
const task = $repoMigrating.attr('data-migrating-task-id');
11+
12+
// returns true if the refresh still need to be called after a while
13+
const refresh = async () => {
14+
const res = await fetch(`${appSubUrl}/user/task/${task}`);
15+
if (res.status !== 200) return true; // continue to refresh if network error occurs
16+
17+
const data = await res.json();
18+
19+
// for all status
20+
if (data.message) {
21+
$('#repo_migrating_progress_message').text(data.message);
22+
}
23+
24+
// TaskStatusFinished
25+
if (data.status === 4) {
26+
window.location.reload();
27+
return false;
1528
}
16-
$.ajax({
17-
type: 'GET',
18-
url: `${appSubUrl}/user/task/${task}`,
19-
data: {
20-
_csrf: csrfToken,
21-
},
22-
complete(xhr) {
23-
if (xhr.status === 200 && xhr.responseJSON) {
24-
if (xhr.responseJSON.status === 4) {
25-
window.location.reload();
26-
return;
27-
} else if (xhr.responseJSON.status === 3) {
28-
hideElem($('#repo_migrating_progress'));
29-
hideElem($('#repo_migrating'));
30-
showElem($('#repo_migrating_failed'));
31-
showElem($('#repo_migrating_failed_image'));
32-
$('#repo_migrating_failed_error').text(xhr.responseJSON.message);
33-
return;
34-
}
35-
if (xhr.responseJSON.message) {
36-
showElem($('#repo_migrating_progress_message'));
37-
$('#repo_migrating_progress_message').text(xhr.responseJSON.message);
38-
}
39-
setTimeout(() => {
40-
initRepoMigrationStatusChecker();
41-
}, 2000);
42-
return;
43-
}
44-
hideElem($('#repo_migrating_progress'));
45-
hideElem($('#repo_migrating'));
46-
showElem($('#repo_migrating_failed'));
47-
showElem($('#repo_migrating_failed_image'));
29+
30+
// TaskStatusFailed
31+
if (data.status === 3) {
32+
hideElem('#repo_migrating_progress');
33+
hideElem('#repo_migrating');
34+
showElem('#repo_migrating_failed');
35+
showElem('#repo_migrating_failed_image');
36+
$('#repo_migrating_failed_error').text(data.message);
37+
return false;
38+
}
39+
40+
return true; // continue to refresh
41+
};
42+
43+
const syncTaskStatus = async () => {
44+
let doNextRefresh = true;
45+
try {
46+
doNextRefresh = await refresh();
47+
} finally {
48+
if (doNextRefresh) {
49+
setTimeout(syncTaskStatus, 2000);
4850
}
49-
});
50-
}
51+
}
52+
};
53+
54+
syncTaskStatus(); // no await
5155
}

0 commit comments

Comments
 (0)
Please sign in to comment.