Skip to content

Commit 7d0e8d8

Browse files
committed
Restructured batch api. Add support for individual errors.
1 parent 5f68237 commit 7d0e8d8

File tree

2 files changed

+72
-62
lines changed

2 files changed

+72
-62
lines changed

routers/routes/web.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1098,7 +1098,7 @@ func RegisterRoutes(m *web.Route) {
10981098
m.Get("/objects/{oid}/{filename}", lfs.ObjectOidHandler)
10991099
m.Any("/objects/{oid}", lfs.ObjectOidHandler)
11001100
m.Post("/objects", lfs.PostHandler)
1101-
m.Post("/verify", lfs.VerifyHandler)
1101+
m.Post("/verify/{oid}", lfs.VerifyHandler)
11021102
m.Group("/locks", func() {
11031103
m.Get("/", lfs.GetListLockHandler)
11041104
m.Post("/", lfs.PostLockHandler)

services/lfs/server.go

Lines changed: 71 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ func (rc *requestContext) ObjectLink(oid string) string {
4545
}
4646

4747
// VerifyLink builds a URL for verifying the object.
48-
func (rc *requestContext) VerifyLink() string {
49-
return setting.AppURL + path.Join(rc.User, rc.Repo+".git", "info/lfs/verify")
48+
func (rc *requestContext) VerifyLink(oid string) string {
49+
return setting.AppURL + path.Join(rc.User, rc.Repo+".git", "info/lfs/verify", oid)
5050
}
5151

5252
// ObjectOidHandler is the main request routing entry point into LFS server functions
@@ -195,7 +195,7 @@ func getMetaHandler(ctx *context.Context) {
195195
if ctx.Req.Method == "GET" {
196196
json := jsoniter.ConfigCompatibleWithStandardLibrary
197197
enc := json.NewEncoder(ctx.Resp)
198-
if err := enc.Encode(represent(rc, meta.Pointer, true, false)); err != nil {
198+
if err := enc.Encode(buildObjectResponse(rc, meta.Pointer, true, false, 0)); err != nil {
199199
log.Error("Failed to encode representation as json. Error: %v", err)
200200
}
201201
}
@@ -267,7 +267,7 @@ func PostHandler(ctx *context.Context) {
267267

268268
json := jsoniter.ConfigCompatibleWithStandardLibrary
269269
enc := json.NewEncoder(ctx.Resp)
270-
if err := enc.Encode(represent(rc, meta.Pointer, meta.Existing, true)); err != nil {
270+
if err := enc.Encode(buildObjectResponse(rc, meta.Pointer, meta.Existing, true, 0)); err != nil {
271271
log.Error("Failed to encode representation as json. Error: %v", err)
272272
}
273273
logRequest(ctx.Req, sentStatus)
@@ -289,6 +289,17 @@ func BatchHandler(ctx *context.Context) {
289289

290290
bv := unpackbatch(ctx)
291291

292+
var isUpload bool
293+
if bv.Operation == "upload" {
294+
isUpload = true
295+
} else if bv.Operation == "download" {
296+
isUpload = false
297+
} else {
298+
log.Info("Attempt to BATCH with invalid operation: %s", bv.Operation)
299+
writeStatus(ctx, http.StatusBadRequest)
300+
return
301+
}
302+
292303
reqCtx := &requestContext{
293304
User: ctx.Params("username"),
294305
Repo: strings.TrimSuffix(ctx.Params("reponame"), ".git"),
@@ -302,12 +313,7 @@ func BatchHandler(ctx *context.Context) {
302313
return
303314
}
304315

305-
requireWrite := false
306-
if bv.Operation == "upload" {
307-
requireWrite = true
308-
}
309-
310-
if !authenticate(ctx, repository, reqCtx.Authorization, requireWrite) {
316+
if !authenticate(ctx, repository, reqCtx.Authorization, isUpload) {
311317
requireAuth(ctx)
312318
return
313319
}
@@ -316,41 +322,57 @@ func BatchHandler(ctx *context.Context) {
316322

317323
var responseObjects []*lfs_module.ObjectResponse
318324

319-
// Create a response object
320325
for _, object := range bv.Objects {
321326
if !object.IsValid() {
322-
log.Info("Invalid LFS OID[%s] attempt to BATCH in %s/%s", object.Oid, reqCtx.User, reqCtx.Repo)
327+
responseObjects = append(responseObjects, buildDownloadObjectResponse(reqCtx, object, http.StatusUnprocessableEntity))
323328
continue
324329
}
325330

326-
if requireWrite && setting.LFS.MaxFileSize > 0 && object.Size > setting.LFS.MaxFileSize {
327-
log.Info("Denied LFS OID[%s] upload of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d", object.Oid, object.Size, reqCtx.User, reqCtx.Repo, setting.LFS.MaxFileSize)
328-
writeStatus(ctx, http.StatusRequestEntityTooLarge)
331+
exist, err := contentStore.Exists(object)
332+
if err != nil {
333+
log.Error("Unable to check if LFS OID[%s] exist on %s/%s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, err)
334+
writeStatus(ctx, http.StatusInternalServerError)
329335
return
330336
}
331337

332-
exist, err := contentStore.Exists(object)
333-
if err != nil {
334-
log.Error("Unable to check if LFS OID[%s] exist on %s / %s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, err)
338+
meta, metaErr := repository.GetLFSMetaObjectByOid(object.Oid)
339+
if metaErr != nil && metaErr != models.ErrLFSObjectNotExist {
340+
log.Error("Unable to get LFS MetaObject [%s] for %s/%s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, metaErr)
335341
writeStatus(ctx, http.StatusInternalServerError)
336342
return
337343
}
338344

339-
meta, err := repository.GetLFSMetaObjectByOid(object.Oid)
340-
if err == nil { // Object is found and exists
341-
if exist {
342-
responseObjects = append(responseObjects, represent(reqCtx, meta.Pointer, true, false))
343-
continue
345+
var responseObject *lfs_module.ObjectResponse
346+
if isUpload {
347+
if !exists && setting.LFS.MaxFileSize > 0 && object.Size > setting.LFS.MaxFileSize {
348+
log.Info("Denied LFS OID[%s] upload of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d", object.Oid, object.Size, reqCtx.User, reqCtx.Repo, setting.LFS.MaxFileSize)
349+
writeStatus(ctx, http.StatusRequestEntityTooLarge)
350+
return
344351
}
345-
}
346352

347-
// Object is not found
348-
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: object, RepositoryID: repository.ID})
349-
if err == nil {
350-
responseObjects = append(responseObjects, represent(reqCtx, meta.Pointer, meta.Existing, !exist))
353+
if exists {
354+
if meta == nil {
355+
_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: object, RepositoryID: repository.ID})
356+
if err != nil {
357+
log.Error("Unable to create LFS MetaObject [%s] for %s/%s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, metaErr)
358+
writeStatus(ctx, http.StatusInternalServerError)
359+
return
360+
}
361+
}
362+
}
363+
364+
responseObject = buildObjectResponse(reqCtx, object, false, !exists, 0)
351365
} else {
352-
log.Error("Unable to write LFS OID[%s] size %d meta object in %v/%v to database. Error: %v", object.Oid, object.Size, reqCtx.User, reqCtx.Repo, err)
366+
errorCode := 0
367+
if !exist || meta == nil {
368+
errorCode = http.StatusNotFound
369+
} else if meta.Size != object.Size {
370+
errorCode = http.StatusUnprocessableEntity
371+
}
372+
373+
responseObject = buildObjectResponse(reqCtx, object, true, false, errorCode)
353374
}
375+
responseObjects = append(responseObjects, responseObject)
354376
}
355377

356378
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
@@ -432,44 +454,32 @@ func VerifyHandler(ctx *context.Context) {
432454
logRequest(ctx.Req, http.StatusOK)
433455
}
434456

435-
// represent takes a requestContext and Meta and turns it into a ObjectResponse suitable
436-
// for json encoding
437-
func represent(rc *requestContext, pointer lfs_module.Pointer, download, upload bool) *lfs_module.ObjectResponse {
438-
rep := &lfs_module.ObjectResponse{
439-
Pointer: pointer,
440-
Actions: make(map[string]*lfs_module.Link),
441-
}
442-
443-
header := make(map[string]string)
444-
445-
if rc.Authorization == "" {
446-
//https://github.com/github/git-lfs/issues/1088
447-
header["Authorization"] = "Authorization: Basic dummy"
457+
func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, download, upload bool, errorCode int) *lfs_module.ObjectResponse {
458+
rep := &lfs_module.ObjectResponse{Pointer: pointer}
459+
if errorCode > 0 {
460+
rep.Error = &ObjectError{
461+
Code: errorCode,
462+
Message: http.StatusText(errorCode),
463+
}
448464
} else {
449-
header["Authorization"] = rc.Authorization
450-
}
465+
rep.Actions = make(map[string]*lfs_module.Link)
451466

452-
if download {
453-
rep.Actions["download"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
454-
}
455-
456-
if upload {
457-
rep.Actions["upload"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
458-
}
459-
460-
if upload && !download {
461-
// Force client side verify action while gitea lacks proper server side verification
467+
header := make(map[string]string)
462468
verifyHeader := make(map[string]string)
463-
for k, v := range header {
464-
verifyHeader[k] = v
469+
470+
if len(rc.Authorization) > 0 {
471+
header["Authorization"] = rc.Authorization
472+
verifyHeader["Authorization"] = rc.Authorization
465473
}
466474

467-
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
468-
verifyHeader["Accept"] = lfs_module.MediaType
469-
470-
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(), Header: verifyHeader}
475+
if download {
476+
rep.Actions["download"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
477+
}
478+
if upload {
479+
rep.Actions["upload"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
480+
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer.Oid), Header: verifyHeader}
481+
}
471482
}
472-
473483
return rep
474484
}
475485

0 commit comments

Comments
 (0)