Skip to content

Commit 1d854f7

Browse files
Improve API repo migration (#11)
* API: make migrateOptions consistent. - There's a subtle difference between the option provided when migrating see e153cf0 * API: use `task.MigrateRepository` when migrating from the API. - Remove the repo creation step; `task.MigrateRepository` creates repo internally, creating a repo before calling it will cause conflict which will result in migration failure. * API: make error handling for `migration` consistent with UI. * fixup! API: make error handling for `migration` consistent with UI. * API: add migration status API endpoint. * fixup! API: add migration status API endpoint. * Clean docker file * API: fix migration status endpoint. - Rtuse the same logic for getting task status in the UI. Co-authored-by: Kaspar Emanuel <[email protected]>
1 parent d13f540 commit 1d854f7

File tree

5 files changed

+107
-106
lines changed

5 files changed

+107
-106
lines changed

Dockerfile.gitea.dev

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,49 @@
1-
2-
###################################
3-
#Build stage
41
FROM golang:1.15-alpine3.12
52

63
ARG GOPROXY
74
ENV GOPROXY ${GOPROXY:-direct}
85

9-
ARG GITEA_VERSION
106
ARG TAGS="sqlite sqlite_unlock_notify"
117
ENV TAGS "bindata timetzdata $TAGS"
128
ARG CGO_EXTRA_CFLAGS
139

14-
#Build deps
15-
RUN apk --no-cache add build-base git nodejs npm
10+
#Build & runtime deps
11+
RUN apk --no-cache add \
12+
build-base \
13+
git \
14+
nodejs \
15+
npm \
16+
bash \
17+
ca-certificates \
18+
curl \
19+
gettext \
20+
git \
21+
linux-pam \
22+
openssh \
23+
s6 \
24+
sqlite \
25+
su-exec \
26+
gnupg
27+
1628

1729
#Setup repo
1830
COPY . /go/src/code.gitea.io/gitea
1931
WORKDIR /go/src/code.gitea.io/gitea
2032

21-
#Checkout version if set
22-
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
23-
&& make clean-all build
33+
RUN make build
2434

2535
EXPOSE 22 3000
2636

27-
RUN apk --no-cache add \
28-
bash \
29-
ca-certificates \
30-
curl \
31-
gettext \
32-
git \
33-
linux-pam \
34-
openssh \
35-
s6 \
36-
sqlite \
37-
su-exec \
38-
gnupg
39-
4037
RUN addgroup \
41-
-S -g 1000 \
42-
git && \
38+
-S -g 1000 \
39+
git && \
4340
adduser \
44-
-S -H -D \
45-
-h /data/git \
46-
-s /bin/bash \
47-
-u 1000 \
48-
-G git \
49-
git && \
41+
-S -H -D \
42+
-h /data/git \
43+
-s /bin/bash \
44+
-u 1000 \
45+
-G git \
46+
git && \
5047
echo "git:$(dd if=/dev/urandom bs=24 count=1 status=none | base64)" | chpasswd
5148

5249
ENV USER git

modules/task/task.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models.
7777
opts.CloneAddr = util.NewStringURLSanitizer(opts.CloneAddr, true).Replace(opts.CloneAddr)
7878
opts.AuthPasswordEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthPassword)
7979
if err != nil {
80-
return nil, nil.err
80+
return nil, nil, err
8181
}
8282
opts.AuthPassword = ""
8383
opts.AuthTokenEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthToken)

routers/api/v1/api.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,10 @@ func Routes() *web.Route {
720720

721721
m.Get("/issues/search", repo.SearchIssues)
722722

723-
m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
723+
m.Group("/migrate", func() {
724+
m.Post("", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
725+
m.Get("/status", repo.GetMigratingTask)
726+
})
724727

725728
m.Group("/{username}/{reponame}", func() {
726729
m.Combo("").Get(reqAnyRepoReader(), repo.Get).

routers/api/v1/repo/migrate.go

Lines changed: 74 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,21 @@
55
package repo
66

77
import (
8-
"bytes"
9-
"errors"
108
"fmt"
119
"net/http"
1210
"strings"
1311

1412
"code.gitea.io/gitea/models"
1513
"code.gitea.io/gitea/modules/context"
1614
"code.gitea.io/gitea/modules/convert"
17-
"code.gitea.io/gitea/modules/graceful"
15+
auth "code.gitea.io/gitea/modules/forms"
1816
"code.gitea.io/gitea/modules/lfs"
1917
"code.gitea.io/gitea/modules/log"
2018
"code.gitea.io/gitea/modules/migrations"
2119
"code.gitea.io/gitea/modules/migrations/base"
22-
"code.gitea.io/gitea/modules/notification"
23-
repo_module "code.gitea.io/gitea/modules/repository"
2420
"code.gitea.io/gitea/modules/setting"
2521
api "code.gitea.io/gitea/modules/structs"
22+
"code.gitea.io/gitea/modules/task"
2623
"code.gitea.io/gitea/modules/util"
2724
"code.gitea.io/gitea/modules/web"
2825
"code.gitea.io/gitea/services/forms"
@@ -53,6 +50,31 @@ func Migrate(ctx *context.APIContext) {
5350
form := web.GetForm(ctx).(*api.MigrateRepoOptions)
5451

5552
//get repoOwner
53+
if setting.Repository.DisableMigrations {
54+
ctx.Error(http.StatusForbidden, "MigrationsGlobalDisabled", fmt.Errorf("the site administrator has disabled migrations"))
55+
return
56+
}
57+
58+
if form.Mirror && setting.Repository.DisableMirrors {
59+
ctx.Error(http.StatusForbidden, "MirrorsGlobalDisabled", fmt.Errorf("the site administrator has disabled mirrors"))
60+
return
61+
}
62+
63+
form.LFS = form.LFS && setting.LFS.StartServer
64+
65+
if form.LFS && len(form.LFSEndpoint) > 0 {
66+
ep := lfs.DetermineEndpoint("", form.LFSEndpoint)
67+
if ep == nil {
68+
ctx.Error(http.StatusInternalServerError, "", ctx.Tr("repo.migrate.invalid_lfs_endpoint"))
69+
return
70+
}
71+
err = migrations.IsMigrateURLAllowed(ep.String(), ctx.User)
72+
if err != nil {
73+
handleRemoteAddrError(ctx, err)
74+
return
75+
}
76+
}
77+
5678
var (
5779
repoOwner *models.User
5880
err error
@@ -108,31 +130,6 @@ func Migrate(ctx *context.APIContext) {
108130

109131
gitServiceType := convert.ToGitServiceType(form.Service)
110132

111-
if form.Mirror && setting.Repository.DisableMirrors {
112-
ctx.Error(http.StatusForbidden, "MirrorsGlobalDisabled", fmt.Errorf("the site administrator has disabled mirrors"))
113-
return
114-
}
115-
116-
if setting.Repository.DisableMigrations {
117-
ctx.Error(http.StatusForbidden, "MigrationsGlobalDisabled", fmt.Errorf("the site administrator has disabled migrations"))
118-
return
119-
}
120-
121-
form.LFS = form.LFS && setting.LFS.StartServer
122-
123-
if form.LFS && len(form.LFSEndpoint) > 0 {
124-
ep := lfs.DetermineEndpoint("", form.LFSEndpoint)
125-
if ep == nil {
126-
ctx.Error(http.StatusInternalServerError, "", ctx.Tr("repo.migrate.invalid_lfs_endpoint"))
127-
return
128-
}
129-
err = migrations.IsMigrateURLAllowed(ep.String(), ctx.User)
130-
if err != nil {
131-
handleRemoteAddrError(ctx, err)
132-
return
133-
}
134-
}
135-
136133
var opts = migrations.MigrateOptions{
137134
CloneAddr: remoteAddr,
138135
RepoName: form.RepoName,
@@ -145,7 +142,7 @@ func Migrate(ctx *context.APIContext) {
145142
AuthPassword: form.AuthPassword,
146143
AuthToken: form.AuthToken,
147144
Wiki: form.Wiki,
148-
Issues: form.Issues,
145+
Issues: form.Issues || form.PullRequests,
149146
Milestones: form.Milestones,
150147
Labels: form.Labels,
151148
Comments: true,
@@ -163,63 +160,33 @@ func Migrate(ctx *context.APIContext) {
163160
opts.Releases = false
164161
}
165162

166-
repo, err := repo_module.CreateRepository(ctx.User, repoOwner, models.CreateRepoOptions{
167-
Name: opts.RepoName,
168-
Description: opts.Description,
169-
OriginalURL: form.CloneAddr,
170-
GitServiceType: gitServiceType,
171-
IsPrivate: opts.Private,
172-
IsMirror: opts.Mirror,
173-
Status: models.RepositoryBeingMigrated,
174-
})
175-
if err != nil {
176-
handleMigrateError(ctx, repoOwner, remoteAddr, err)
163+
if err = models.CheckCreateRepository(ctx.User, repoOwner, opts.RepoName, false); err != nil {
164+
handleMigrateError(ctx, repoOwner, &opts, err)
177165
return
178166
}
179167

180-
opts.MigrateToRepoID = repo.ID
181-
182-
defer func() {
183-
if e := recover(); e != nil {
184-
var buf bytes.Buffer
185-
fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
186-
187-
err = errors.New(buf.String())
188-
}
189-
190-
if err == nil {
191-
notification.NotifyMigrateRepository(ctx.User, repoOwner, repo)
192-
return
193-
}
194-
195-
if repo != nil {
196-
if errDelete := models.DeleteRepository(ctx.User, repoOwner.ID, repo.ID); errDelete != nil {
197-
log.Error("DeleteRepository: %v", errDelete)
198-
}
199-
}
200-
}()
201-
202-
if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, repoOwner.Name, opts, nil); err != nil {
203-
handleMigrateError(ctx, repoOwner, remoteAddr, err)
168+
repo, err := task.MigrateRepository(ctx.User, repoOwner, opts)
169+
if err == nil {
170+
log.Trace("Repository migrated: %s/%s", repoOwner.Name, form.RepoName)
171+
ctx.JSON(http.StatusCreated, convert.ToRepo(repo, models.AccessModeAdmin))
204172
return
205173
}
206174

207-
log.Trace("Repository migrated: %s/%s", repoOwner.Name, form.RepoName)
208-
ctx.JSON(http.StatusCreated, convert.ToRepo(repo, models.AccessModeAdmin))
175+
handleMigrateError(ctx, repoOwner, &opts, err)
209176
}
210177

211-
func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
178+
func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, migrationOpts *migrations.MigrateOptions, err error) {
212179
switch {
213-
case models.IsErrRepoAlreadyExist(err):
214-
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
215-
case models.IsErrRepoFilesAlreadyExist(err):
216-
ctx.Error(http.StatusConflict, "", "Files already exist for this repository. Adopt them or delete them.")
217180
case migrations.IsRateLimitError(err):
218181
ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
219182
case migrations.IsTwoFactorAuthError(err):
220183
ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.")
221184
case models.IsErrReachLimitOfRepo(err):
222185
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
186+
case models.IsErrRepoAlreadyExist(err):
187+
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
188+
case models.IsErrRepoFilesAlreadyExist(err):
189+
ctx.Error(http.StatusConflict, "", "Files already exist for this repository. Adopt them or delete them.")
223190
case models.IsErrNameReserved(err):
224191
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
225192
case models.IsErrNameCharsNotAllowed(err):
@@ -231,6 +198,7 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA
231198
case base.IsErrNotSupported(err):
232199
ctx.Error(http.StatusUnprocessableEntity, "", err)
233200
default:
201+
remoteAddr, _ := auth.ParseRemoteAddr(migrationOpts.CloneAddr, migrationOpts.AuthUsername, migrationOpts.AuthPassword)
234202
err = util.NewStringURLSanitizedError(err, remoteAddr, true)
235203
if strings.Contains(err.Error(), "Authentication failed") ||
236204
strings.Contains(err.Error(), "Bad credentials") ||
@@ -267,3 +235,36 @@ func handleRemoteAddrError(ctx *context.APIContext, err error) {
267235
ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err)
268236
}
269237
}
238+
239+
// GetMigratingTask returns the migrating task by repo's id
240+
func GetMigratingTask(ctx *context.APIContext) {
241+
// swagger:operation GET /repos/migrate/status task
242+
// ---
243+
// summary: Get the migration status of a repository by its id
244+
// produces:
245+
// - application/json
246+
// parameters:
247+
// - name: repo_id
248+
// in: query
249+
// description: repository id
250+
// type: int64
251+
// responses:
252+
// "200":
253+
// "$ref": "#/responses/"
254+
// "404":
255+
// "$ref": "#/response/"
256+
t, err := models.GetMigratingTask(ctx.QueryInt64("repo_id"))
257+
258+
if err != nil {
259+
ctx.JSON(http.StatusNotFound, err)
260+
return
261+
}
262+
263+
ctx.JSON(200, map[string]interface{}{
264+
"status": t.Status,
265+
"err": t.Errors,
266+
"repo-id": t.RepoID,
267+
"start": t.StartTime,
268+
"end": t.EndTime,
269+
})
270+
}

routers/web/repo/migrate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func MigratePost(ctx *context.Context) {
232232
return
233233
}
234234

235-
err = task.MigrateRepository(ctx.User, ctxUser, opts)
235+
_, err = task.MigrateRepository(ctx.User, ctxUser, opts)
236236
if err == nil {
237237
ctx.Redirect(ctxUser.HomeLink() + "/" + opts.RepoName)
238238
return

0 commit comments

Comments
 (0)