Skip to content

Commit f29c301

Browse files
6543zeripath
andauthored
Refactor api repo dir (#10048)
* mv migrate * mv mirror Co-authored-by: zeripath <[email protected]>
1 parent 82a9797 commit f29c301

File tree

3 files changed

+264
-234
lines changed

3 files changed

+264
-234
lines changed

routers/api/v1/repo/migrate.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package repo
6+
7+
import (
8+
"bytes"
9+
"errors"
10+
"fmt"
11+
"net/http"
12+
"net/url"
13+
"strings"
14+
15+
"code.gitea.io/gitea/models"
16+
"code.gitea.io/gitea/modules/auth"
17+
"code.gitea.io/gitea/modules/context"
18+
"code.gitea.io/gitea/modules/graceful"
19+
"code.gitea.io/gitea/modules/log"
20+
"code.gitea.io/gitea/modules/migrations"
21+
"code.gitea.io/gitea/modules/notification"
22+
repo_module "code.gitea.io/gitea/modules/repository"
23+
"code.gitea.io/gitea/modules/setting"
24+
api "code.gitea.io/gitea/modules/structs"
25+
"code.gitea.io/gitea/modules/util"
26+
)
27+
28+
// Migrate migrate remote git repository to gitea
29+
func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
30+
// swagger:operation POST /repos/migrate repository repoMigrate
31+
// ---
32+
// summary: Migrate a remote git repository
33+
// consumes:
34+
// - application/json
35+
// produces:
36+
// - application/json
37+
// parameters:
38+
// - name: body
39+
// in: body
40+
// schema:
41+
// "$ref": "#/definitions/MigrateRepoForm"
42+
// responses:
43+
// "201":
44+
// "$ref": "#/responses/Repository"
45+
// "403":
46+
// "$ref": "#/responses/forbidden"
47+
// "422":
48+
// "$ref": "#/responses/validationError"
49+
50+
ctxUser := ctx.User
51+
// Not equal means context user is an organization,
52+
// or is another user/organization if current user is admin.
53+
if form.UID != ctxUser.ID {
54+
org, err := models.GetUserByID(form.UID)
55+
if err != nil {
56+
if models.IsErrUserNotExist(err) {
57+
ctx.Error(http.StatusUnprocessableEntity, "", err)
58+
} else {
59+
ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
60+
}
61+
return
62+
}
63+
ctxUser = org
64+
}
65+
66+
if ctx.HasError() {
67+
ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
68+
return
69+
}
70+
71+
if !ctx.User.IsAdmin {
72+
if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
73+
ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
74+
return
75+
}
76+
77+
if ctxUser.IsOrganization() {
78+
// Check ownership of organization.
79+
isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
80+
if err != nil {
81+
ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
82+
return
83+
} else if !isOwner {
84+
ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
85+
return
86+
}
87+
}
88+
}
89+
90+
remoteAddr, err := form.ParseRemoteAddr(ctx.User)
91+
if err != nil {
92+
if models.IsErrInvalidCloneAddr(err) {
93+
addrErr := err.(models.ErrInvalidCloneAddr)
94+
switch {
95+
case addrErr.IsURLError:
96+
ctx.Error(http.StatusUnprocessableEntity, "", err)
97+
case addrErr.IsPermissionDenied:
98+
ctx.Error(http.StatusUnprocessableEntity, "", "You are not allowed to import local repositories.")
99+
case addrErr.IsInvalidPath:
100+
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid local path, it does not exist or not a directory.")
101+
default:
102+
ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
103+
}
104+
} else {
105+
ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err)
106+
}
107+
return
108+
}
109+
110+
var gitServiceType = api.PlainGitService
111+
u, err := url.Parse(remoteAddr)
112+
if err == nil && strings.EqualFold(u.Host, "github.com") {
113+
gitServiceType = api.GithubService
114+
}
115+
116+
var opts = migrations.MigrateOptions{
117+
CloneAddr: remoteAddr,
118+
RepoName: form.RepoName,
119+
Description: form.Description,
120+
Private: form.Private || setting.Repository.ForcePrivate,
121+
Mirror: form.Mirror,
122+
AuthUsername: form.AuthUsername,
123+
AuthPassword: form.AuthPassword,
124+
Uncyclo: form.Uncyclo,
125+
Issues: form.Issues,
126+
Milestones: form.Milestones,
127+
Labels: form.Labels,
128+
Comments: true,
129+
PullRequests: form.PullRequests,
130+
Releases: form.Releases,
131+
GitServiceType: gitServiceType,
132+
}
133+
if opts.Mirror {
134+
opts.Issues = false
135+
opts.Milestones = false
136+
opts.Labels = false
137+
opts.Comments = false
138+
opts.PullRequests = false
139+
opts.Releases = false
140+
}
141+
142+
repo, err := repo_module.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
143+
Name: opts.RepoName,
144+
Description: opts.Description,
145+
OriginalURL: form.CloneAddr,
146+
GitServiceType: gitServiceType,
147+
IsPrivate: opts.Private,
148+
IsMirror: opts.Mirror,
149+
Status: models.RepositoryBeingMigrated,
150+
})
151+
if err != nil {
152+
handleMigrateError(ctx, ctxUser, remoteAddr, err)
153+
return
154+
}
155+
156+
opts.MigrateToRepoID = repo.ID
157+
158+
defer func() {
159+
if e := recover(); e != nil {
160+
var buf bytes.Buffer
161+
fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
162+
163+
err = errors.New(buf.String())
164+
}
165+
166+
if err == nil {
167+
repo.Status = models.RepositoryReady
168+
if err := models.UpdateRepositoryCols(repo, "status"); err == nil {
169+
notification.NotifyMigrateRepository(ctx.User, ctxUser, repo)
170+
return
171+
}
172+
}
173+
174+
if repo != nil {
175+
if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
176+
log.Error("DeleteRepository: %v", errDelete)
177+
}
178+
}
179+
}()
180+
181+
if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, ctxUser.Name, opts); err != nil {
182+
handleMigrateError(ctx, ctxUser, remoteAddr, err)
183+
return
184+
}
185+
186+
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
187+
ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeAdmin))
188+
}
189+
190+
func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
191+
switch {
192+
case models.IsErrRepoAlreadyExist(err):
193+
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
194+
case migrations.IsRateLimitError(err):
195+
ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
196+
case migrations.IsTwoFactorAuthError(err):
197+
ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.")
198+
case models.IsErrReachLimitOfRepo(err):
199+
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
200+
case models.IsErrNameReserved(err):
201+
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
202+
case models.IsErrNamePatternNotAllowed(err):
203+
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
204+
default:
205+
err = util.URLSanitizedError(err, remoteAddr)
206+
if strings.Contains(err.Error(), "Authentication failed") ||
207+
strings.Contains(err.Error(), "Bad credentials") ||
208+
strings.Contains(err.Error(), "could not read Username") {
209+
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Authentication failed: %v.", err))
210+
} else if strings.Contains(err.Error(), "fatal:") {
211+
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Migration failed: %v.", err))
212+
} else {
213+
ctx.Error(http.StatusInternalServerError, "MigrateRepository", err)
214+
}
215+
}
216+
}

routers/api/v1/repo/mirror.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package repo
6+
7+
import (
8+
"net/http"
9+
10+
"code.gitea.io/gitea/models"
11+
"code.gitea.io/gitea/modules/context"
12+
mirror_service "code.gitea.io/gitea/services/mirror"
13+
)
14+
15+
// MirrorSync adds a mirrored repository to the sync queue
16+
func MirrorSync(ctx *context.APIContext) {
17+
// swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
18+
// ---
19+
// summary: Sync a mirrored repository
20+
// produces:
21+
// - application/json
22+
// parameters:
23+
// - name: owner
24+
// in: path
25+
// description: owner of the repo to sync
26+
// type: string
27+
// required: true
28+
// - name: repo
29+
// in: path
30+
// description: name of the repo to sync
31+
// type: string
32+
// required: true
33+
// responses:
34+
// "200":
35+
// "$ref": "#/responses/empty"
36+
// "403":
37+
// "$ref": "#/responses/forbidden"
38+
39+
repo := ctx.Repo.Repository
40+
41+
if !ctx.Repo.CanWrite(models.UnitTypeCode) {
42+
ctx.Error(http.StatusForbidden, "MirrorSync", "Must have write access")
43+
}
44+
45+
mirror_service.StartToMirror(repo.ID)
46+
47+
ctx.Status(http.StatusOK)
48+
}

0 commit comments

Comments
 (0)