diff --git a/test/cmd/lfstest-gitserver.go b/test/cmd/lfstest-gitserver.go index c75c8da9..61a738dc 100644 --- a/test/cmd/lfstest-gitserver.go +++ b/test/cmd/lfstest-gitserver.go @@ -20,6 +20,7 @@ import ( "os" "os/exec" "regexp" + "strconv" "strings" "sync" ) @@ -44,6 +45,7 @@ var ( "status-batch-403", "status-batch-404", "status-batch-410", "status-batch-422", "status-batch-500", "status-storage-403", "status-storage-404", "status-storage-410", "status-storage-422", "status-storage-500", "status-legacy-404", "status-legacy-410", "status-legacy-422", "status-legacy-403", "status-legacy-500", + "status-batch-resume-206", } ) @@ -284,9 +286,14 @@ func lfsBatchHandler(w http.ResponseWriter, r *http.Request, repo string) { } type batchReq struct { + Transfers []string `json:"transfers"` Operation string `json:"operation"` Objects []lfsObject `json:"objects"` } + type batchResp struct { + Transfer string `json:"transfer,omitempty"` + Objects []lfsObject `json:"objects"` + } buf := &bytes.Buffer{} tee := io.TeeReader(r.Body, buf) @@ -304,6 +311,7 @@ func lfsBatchHandler(w http.ResponseWriter, r *http.Request, repo string) { res := []lfsObject{} testingChunked := testingChunkedTransferEncoding(r) + var transferChoice string for _, obj := range objs.Objects { action := objs.Operation @@ -337,6 +345,14 @@ func lfsBatchHandler(w http.ResponseWriter, r *http.Request, repo string) { o.Err = &lfsError{Code: 422, Message: "welp"} case "status-batch-500": o.Err = &lfsError{Code: 500, Message: "welp"} + case "status-batch-resume-206": + for _, t := range objs.Transfers { + if t == "http-range" { + transferChoice = "http-range" + break + } + } + fallthrough default: // regular 200 response if addAction { o.Actions = map[string]lfsLink{ @@ -355,7 +371,7 @@ func lfsBatchHandler(w http.ResponseWriter, r *http.Request, repo string) { res = append(res, o) } - ores := map[string][]lfsObject{"objects": res} + ores := batchResp{Transfer: transferChoice, Objects: res} by, err := json.Marshal(ores) if err != nil { @@ -427,9 +443,32 @@ func storageHandler(w http.ResponseWriter, r *http.Request) { case "GET": parts := strings.Split(r.URL.Path, "/") oid := parts[len(parts)-1] + statusCode := 200 + byteLimit := 0 + resumeAt := int64(0) if by, ok := largeObjects.Get(repo, oid); ok { - w.Write(by) + if len(by) == len("status-batch-resume-206") && string(by) == "status-batch-resume-206" { + // Resume if header includes range, otherwise deliberately interrupt + if rangeHdr := r.Header.Get("Range"); rangeHdr != "" { + regex := regexp.MustCompile(`bytes=(\d+)\-.*`) + match := regex.FindStringSubmatch(rangeHdr) + if match != nil && len(match) > 1 { + statusCode = 206 + resumeAt, _ = strconv.ParseInt(match[1], 10, 32) + } + } else { + byteLimit = 10 + } + } + w.WriteHeader(statusCode) + if byteLimit > 0 { + w.Write(by[0:byteLimit]) + } else if resumeAt > 0 { + w.Write(by[resumeAt:]) + } else { + w.Write(by) + } return } diff --git a/test/test-resume-http-range.sh b/test/test-resume-http-range.sh new file mode 100755 index 00000000..de2a93b5 --- /dev/null +++ b/test/test-resume-http-range.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +. "test/testlib.sh" + +begin_test "resume-http-range" +( + set -e + + reponame="$(basename "$0" ".sh")" + setup_remote_repo "$reponame" + + clone_repo "$reponame" $reponame + + git lfs track "*.dat" 2>&1 | tee track.log + grep "Tracking \*.dat" track.log + + # this string announces to server that we want http-range support and to + # interrupt the transfer when started from 0 to cause resume + contents="status-batch-resume-206" + contents_oid=$(calc_oid "$contents") + + printf "$contents" > a.dat + git add a.dat + git add .gitattributes + git commit -m "add a.dat" 2>&1 | tee commit.log + git push origin master + + assert_server_object "$reponame" "$contents_oid" + + # delete local copy then fetch it back + # server will abort the transfer mid way (so will error) when not resuming + # then we can restart it + rm -rf .git/lfs/objects + git lfs fetch 2>&1 | tee fetchinterrupted.log + refute_local_object "$contents_oid" + + # now fetch again, this should try to resume and server should send remainder + # this time (it does not cut short when Range is requested) + GIT_TRACE=1 git lfs fetch 2>&1 | tee fetchresume.log + grep "http-range: server accepted resume" fetchresume.log + assert_local_object "$contents_oid" "${#contents}" + +) +end_test +