New upstream version 0.8.5+debian

parent 18134ea8
image: golang:1.6.2
.test_template: &test_definition
script:
- apt-get update -qq && apt-get install -y unzip bzip2
- go version
- make test
test:
script:
- apt-get update -qq && apt-get install -y unzip bzip2
- make test
test:go1.5.4:
<<: *test_definition
image: golang:1.5.4
test:go1.6.3:
<<: *test_definition
image: golang:1.6.3
test:go1.7.1:
<<: *test_definition
image: golang:1.7.1
......@@ -2,6 +2,22 @@
Formerly known as 'gitlab-git-http-server'.
v0.8.5
Simplify revspec for 'git format-patch'.
v0.8.4
Fix Go 1.5 compatibility broken in 0.8.3. Update CI configuration so
that tests run on Go 1.5, 1.6 and 1.7 (was only 1.6 before).
v0.8.3
Add rate-limiting feature for /api requests (disabled by default).
Suppress non-zero exit code error from git-upload-pack during shallow
Git clone (only affects logging and Sentry). Don't treat EEXIST as an
error during git archive finalization.
v0.8.2
Recognize more archive formats in git.SendArchive. Make 502 errors
......
......@@ -13,6 +13,12 @@ gitlab-workhorse'][brief-history-blog].
gitlab-workhorse [OPTIONS]
Options:
-apiLimit uint
Number of API requests allowed at single time
-apiQueueDuration duration
Maximum queueing duration of requests (default 30s)
-apiQueueLimit uint
Number of API requests allowed to be queued
-authBackend string
Authentication/authorization backend (default "http://localhost:8080")
-authSocket string
......
......@@ -147,13 +147,13 @@ func (api *API) PreAuthorizeHandler(h HandleFunc, suffix string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authReq, err := api.newRequest(r, nil, suffix)
if err != nil {
helper.Fail500(w, fmt.Errorf("preAuthorizeHandler: newUpstreamRequest: %v", err))
helper.Fail500(w, r, fmt.Errorf("preAuthorizeHandler newUpstreamRequest: %v", err))
return
}
authResponse, err := api.Client.Do(authReq)
if err != nil {
helper.Fail500(w, fmt.Errorf("preAuthorizeHandler: do %v: %v", authReq.URL.Path, err))
helper.Fail500(w, r, fmt.Errorf("preAuthorizeHandler: do request: %v", err))
return
}
defer authResponse.Body.Close()
......@@ -173,7 +173,7 @@ func (api *API) PreAuthorizeHandler(h HandleFunc, suffix string) http.Handler {
}
if contentType := authResponse.Header.Get("Content-Type"); contentType != ResponseContentType {
helper.Fail500(w, fmt.Errorf("preAuthorizeHandler: API responded with wrong content type: %v", contentType))
helper.Fail500(w, r, fmt.Errorf("preAuthorizeHandler: API responded with wrong content type: %v", contentType))
return
}
......@@ -182,7 +182,7 @@ func (api *API) PreAuthorizeHandler(h HandleFunc, suffix string) http.Handler {
// request metadata. We must extract this information from the auth
// response body.
if err := json.NewDecoder(authResponse.Body).Decode(a); err != nil {
helper.Fail500(w, fmt.Errorf("preAuthorizeHandler: decode authorization response: %v", err))
helper.Fail500(w, r, fmt.Errorf("preAuthorizeHandler: decode authorization response: %v", err))
return
}
// Don't hog a TCP connection in CLOSE_WAIT, we can already close it now
......
......@@ -11,7 +11,7 @@ import (
// leaking to the end user
func Block(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := &blocker{rw: w}
rw := &blocker{rw: w, r: r}
defer rw.Flush()
h.ServeHTTP(rw, r)
})
......@@ -19,6 +19,7 @@ func Block(h http.Handler) http.Handler {
type blocker struct {
rw http.ResponseWriter
r *http.Request
hijacked bool
status int
}
......@@ -47,7 +48,7 @@ func (b *blocker) WriteHeader(status int) {
b.status = 500
b.Header().Del("Content-Length")
b.hijacked = true
helper.Fail500(b.rw, fmt.Errorf("api.blocker: forbidden content-type: %q", ResponseContentType))
helper.Fail500(b.rw, b.r, fmt.Errorf("api.blocker: forbidden content-type: %q", ResponseContentType))
return
}
......
package artifacts
import (
"errors"
"fmt"
"io/ioutil"
"mime/multipart"
......@@ -75,7 +74,7 @@ func (a *artifactsUploadProcessor) Cleanup() {
func UploadArtifacts(myAPI *api.API, h http.Handler) http.Handler {
return myAPI.PreAuthorizeHandler(func(w http.ResponseWriter, r *http.Request, a *api.Response) {
if a.TempPath == "" {
helper.Fail500(w, errors.New("UploadArtifacts: TempPath is empty"))
helper.Fail500(w, r, fmt.Errorf("UploadArtifacts: TempPath is empty"))
return
}
......
......@@ -2,7 +2,6 @@ package artifacts
import (
"bufio"
"errors"
"fmt"
"io"
"log"
......@@ -28,14 +27,14 @@ var SendEntry = &entry{"artifacts-entry:"}
func (e *entry) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
var params entryParams
if err := e.Unpack(&params, sendData); err != nil {
helper.Fail500(w, fmt.Errorf("SendEntry: unpack sendData: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendEntry: unpack sendData: %v", err))
return
}
log.Printf("SendEntry: sending %q from %q for %q", params.Entry, params.Archive, r.URL.Path)
if params.Archive == "" || params.Entry == "" {
helper.Fail500(w, errors.New("SendEntry: Archive or Entry is empty"))
helper.Fail500(w, r, fmt.Errorf("SendEntry: Archive or Entry is empty"))
return
}
......@@ -44,7 +43,7 @@ func (e *entry) Inject(w http.ResponseWriter, r *http.Request, sendData string)
if os.IsNotExist(err) {
http.NotFound(w, r)
} else if err != nil {
helper.Fail500(w, fmt.Errorf("SendEntry: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendEntry: %v", err))
}
}
......
......@@ -25,7 +25,7 @@ var DefaultTransport = &http.Transport{
}
// Custom error for pretty Sentry 'issues'
type Error502 error
type Error struct{ error }
type RoundTripper struct {
Transport *http.Transport
......@@ -81,9 +81,10 @@ func (t *RoundTripper) RoundTrip(r *http.Request) (res *http.Response, err error
// instead of 500s we catch the RoundTrip error here and inject a
// 502 response.
if err != nil {
helper.LogError(Error502(fmt.Errorf("badgateway: %s %q failed after %.3fs: %v",
r.Method, r.RequestURI, time.Since(start).Seconds(), err,
)))
helper.LogError(
r,
&Error{fmt.Errorf("badgateway: failed after %.3fs: %v", time.Since(start).Seconds(), err)},
)
res = &http.Response{
StatusCode: http.StatusBadGateway,
......
......@@ -34,7 +34,7 @@ var SendArchive = &archive{"git-archive:"}
func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
var params archiveParams
if err := a.Unpack(&params, sendData); err != nil {
helper.Fail500(w, fmt.Errorf("SendArchive: unpack sendData: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendArchive: unpack sendData: %v", err))
return
}
......@@ -42,7 +42,7 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
urlPath := r.URL.Path
format, ok := parseBasename(filepath.Base(urlPath))
if !ok {
helper.Fail500(w, fmt.Errorf("handleGetArchive: invalid format: %s", urlPath))
helper.Fail500(w, r, fmt.Errorf("SendArchive: invalid format: %s", urlPath))
return
}
......@@ -65,7 +65,7 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
// to finalize the cached archive.
tempFile, err := prepareArchiveTempfile(path.Dir(params.ArchivePath), archiveFilename)
if err != nil {
helper.Fail500(w, fmt.Errorf("handleGetArchive: create tempfile: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendArchive: create tempfile: %v", err))
return
}
defer tempFile.Close()
......@@ -76,12 +76,12 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
archiveCmd := gitCommand("", "git", "--git-dir="+params.RepoPath, "archive", "--format="+archiveFormat, "--prefix="+params.ArchivePrefix+"/", params.CommitId)
archiveStdout, err := archiveCmd.StdoutPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("handleGetArchive: archive stdout: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendArchive: archive stdout: %v", err))
return
}
defer archiveStdout.Close()
if err := archiveCmd.Start(); err != nil {
helper.Fail500(w, fmt.Errorf("handleGetArchive: start %v: %v", archiveCmd.Args, err))
helper.Fail500(w, r, fmt.Errorf("SendArchive: start %v: %v", archiveCmd.Args, err))
return
}
defer helper.CleanUpProcessGroup(archiveCmd) // Ensure brute force subprocess clean-up
......@@ -95,13 +95,13 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
stdout, err = compressCmd.StdoutPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("handleGetArchive: compress stdout: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendArchive: compress stdout: %v", err))
return
}
defer stdout.Close()
if err := compressCmd.Start(); err != nil {
helper.Fail500(w, fmt.Errorf("handleGetArchive: start %v: %v", compressCmd.Args, err))
helper.Fail500(w, r, fmt.Errorf("SendArchive: start %v: %v", compressCmd.Args, err))
return
}
defer helper.CleanUpProcessGroup(compressCmd)
......@@ -116,22 +116,22 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
setArchiveHeaders(w, format, archiveFilename)
w.WriteHeader(200) // Don't bother with HTTP 500 from this point on, just return
if _, err := io.Copy(w, archiveReader); err != nil {
helper.LogError(fmt.Errorf("handleGetArchive: copy 'git archive' output: %v", err))
helper.LogError(r, &copyError{fmt.Errorf("SendArchive: copy 'git archive' output: %v", err)})
return
}
if err := archiveCmd.Wait(); err != nil {
helper.LogError(fmt.Errorf("handleGetArchive: archiveCmd: %v", err))
helper.LogError(r, fmt.Errorf("SendArchive: archiveCmd: %v", err))
return
}
if compressCmd != nil {
if err := compressCmd.Wait(); err != nil {
helper.LogError(fmt.Errorf("handleGetArchive: compressCmd: %v", err))
helper.LogError(r, fmt.Errorf("SendArchive: compressCmd: %v", err))
return
}
}
if err := finalizeCachedArchive(tempFile, params.ArchivePath); err != nil {
helper.LogError(fmt.Errorf("handleGetArchive: finalize cached archive: %v", err))
helper.LogError(r, fmt.Errorf("SendArchive: finalize cached archive: %v", err))
return
}
}
......@@ -173,7 +173,11 @@ func finalizeCachedArchive(tempFile *os.File, archivePath string) error {
if err := tempFile.Close(); err != nil {
return err
}
return os.Link(tempFile.Name(), archivePath)
if err := os.Link(tempFile.Name(), archivePath); err != nil && !os.IsExist(err) {
return err
}
return nil
}
func parseBasename(basename string) (string, bool) {
......
package git
import (
"io/ioutil"
"testing"
)
......@@ -27,3 +28,17 @@ func TestParseBasename(t *testing.T) {
}
}
}
func TestFinalizeArchive(t *testing.T) {
tempFile, err := ioutil.TempFile("", "gitlab-workhorse-test")
if err != nil {
t.Fatal(err)
}
defer tempFile.Close()
// Deliberately cause an EEXIST error: we know tempFile.Name() already exists
err = finalizeCachedArchive(tempFile, tempFile.Name())
if err != nil {
t.Fatalf("expected nil from finalizeCachedArchive, received %v", err)
}
}
......@@ -19,7 +19,7 @@ var SendBlob = &blob{"git-blob:"}
func (b *blob) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
var params blobParams
if err := b.Unpack(&params, sendData); err != nil {
helper.Fail500(w, fmt.Errorf("SendBlob: unpack sendData: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendBlob: unpack sendData: %v", err))
return
}
......@@ -27,29 +27,29 @@ func (b *blob) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
sizeOutput, err := gitCommand("", "git", "--git-dir="+params.RepoPath, "cat-file", "-s", params.BlobId).Output()
if err != nil {
helper.Fail500(w, fmt.Errorf("SendBlob: get blob size: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendBlob: get blob size: %v", err))
return
}
gitShowCmd := gitCommand("", "git", "--git-dir="+params.RepoPath, "cat-file", "blob", params.BlobId)
stdout, err := gitShowCmd.StdoutPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("SendBlob: git cat-file stdout: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendBlob: git cat-file stdout: %v", err))
return
}
if err := gitShowCmd.Start(); err != nil {
helper.Fail500(w, fmt.Errorf("SendBlob: start %v: %v", gitShowCmd, err))
helper.Fail500(w, r, fmt.Errorf("SendBlob: start %v: %v", gitShowCmd, err))
return
}
defer helper.CleanUpProcessGroup(gitShowCmd)
w.Header().Set("Content-Length", strings.TrimSpace(string(sizeOutput)))
if _, err := io.Copy(w, stdout); err != nil {
helper.LogError(fmt.Errorf("SendBlob: copy git cat-file stdout: %v", err))
helper.LogError(r, &copyError{fmt.Errorf("SendBlob: copy git cat-file stdout: %v", err)})
return
}
if err := gitShowCmd.Wait(); err != nil {
helper.LogError(fmt.Errorf("SendBlob: wait for git cat-file: %v", err))
helper.LogError(r, fmt.Errorf("SendBlob: wait for git cat-file: %v", err))
return
}
}
......@@ -22,7 +22,7 @@ var SendDiff = &diff{"git-diff:"}
func (d *diff) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
var params diffParams
if err := d.Unpack(&params, sendData); err != nil {
helper.Fail500(w, fmt.Errorf("SendDiff: unpack sendData: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendDiff: unpack sendData: %v", err))
return
}
......@@ -31,23 +31,26 @@ func (d *diff) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
gitDiffCmd := gitCommand("", "git", "--git-dir="+params.RepoPath, "diff", params.ShaFrom, params.ShaTo)
stdout, err := gitDiffCmd.StdoutPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("SendDiff: create stdout pipe: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendDiff: create stdout pipe: %v", err))
return
}
if err := gitDiffCmd.Start(); err != nil {
helper.Fail500(w, fmt.Errorf("SendDiff: start %v: %v", gitDiffCmd, err))
helper.Fail500(w, r, fmt.Errorf("SendDiff: start %v: %v", gitDiffCmd.Args, err))
return
}
defer helper.CleanUpProcessGroup(gitDiffCmd)
w.Header().Del("Content-Length")
if _, err := io.Copy(w, stdout); err != nil {
helper.LogError(fmt.Errorf("SendDiff: copy %v stdout: %v", gitDiffCmd, err))
helper.LogError(
r,
&copyError{fmt.Errorf("SendDiff: copy %v stdout: %v", gitDiffCmd.Args, err)},
)
return
}
if err := gitDiffCmd.Wait(); err != nil {
helper.LogError(fmt.Errorf("SendDiff: wait for %v: %v", gitDiffCmd, err))
helper.LogError(r, fmt.Errorf("SendDiff: wait for %v: %v", gitDiffCmd.Args, err))
return
}
}
package git
// For cosmetic purposes in Sentry
type copyError struct{ error }
......@@ -22,34 +22,34 @@ var SendPatch = &patch{"git-format-patch:"}
func (p *patch) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
var params patchParams
if err := p.Unpack(&params, sendData); err != nil {
helper.Fail500(w, fmt.Errorf("SendPatch: unpack sendData: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendPatch: unpack sendData: %v", err))
return
}
log.Printf("SendPatch: sending patch between %q and %q for %q", params.ShaFrom, params.ShaTo, r.URL.Path)
gitRange := fmt.Sprintf("%v...%v", params.ShaFrom, params.ShaTo)
gitRange := fmt.Sprintf("%s..%s", params.ShaFrom, params.ShaTo)
gitPatchCmd := gitCommand("", "git", "--git-dir="+params.RepoPath, "format-patch", gitRange, "--stdout")
stdout, err := gitPatchCmd.StdoutPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("SendPatch: create stdout pipe: %v", err))
helper.Fail500(w, r, fmt.Errorf("SendPatch: create stdout pipe: %v", err))
return
}
if err := gitPatchCmd.Start(); err != nil {
helper.Fail500(w, fmt.Errorf("SendPatch: start %v: %v", gitPatchCmd, err))
helper.Fail500(w, r, fmt.Errorf("SendPatch: start %v: %v", gitPatchCmd.Args, err))
return
}
defer helper.CleanUpProcessGroup(gitPatchCmd)
w.Header().Del("Content-Length")
if _, err := io.Copy(w, stdout); err != nil {
helper.LogError(fmt.Errorf("SendPatch: copy %v stdout: %v", gitPatchCmd, err))
helper.LogError(r, &copyError{fmt.Errorf("SendPatch: copy %v stdout: %v", gitPatchCmd.Args, err)})
return
}
if err := gitPatchCmd.Wait(); err != nil {
helper.LogError(fmt.Errorf("SendPatch: wait for %v: %v", gitPatchCmd, err))
helper.LogError(r, fmt.Errorf("SendPatch: wait for %v: %v", gitPatchCmd.Args, err))
return
}
}
......@@ -5,12 +5,13 @@ In this file we handle the Git 'smart HTTP' protocol
package git
import (
"errors"
"bytes"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
......@@ -40,7 +41,7 @@ func looksLikeRepo(p string) bool {
func repoPreAuthorizeHandler(myAPI *api.API, handleFunc api.HandleFunc) http.Handler {
return myAPI.PreAuthorizeHandler(func(w http.ResponseWriter, r *http.Request, a *api.Response) {
if a.RepoPath == "" {
helper.Fail500(w, errors.New("repoPreAuthorizeHandler: RepoPath empty"))
helper.Fail500(w, r, fmt.Errorf("repoPreAuthorizeHandler: RepoPath empty"))
return
}
......@@ -65,12 +66,12 @@ func handleGetInfoRefs(w http.ResponseWriter, r *http.Request, a *api.Response)
cmd := gitCommand(a.GL_ID, "git", subCommand(rpc), "--stateless-rpc", "--advertise-refs", a.RepoPath)
stdout, err := cmd.StdoutPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("handleGetInfoRefs: stdout: %v", err))
helper.Fail500(w, r, fmt.Errorf("handleGetInfoRefs: stdout: %v", err))
return
}
defer stdout.Close()
if err := cmd.Start(); err != nil {
helper.Fail500(w, fmt.Errorf("handleGetInfoRefs: start %v: %v", cmd.Args, err))
helper.Fail500(w, r, fmt.Errorf("handleGetInfoRefs: start %v: %v", cmd.Args, err))
return
}
defer helper.CleanUpProcessGroup(cmd) // Ensure brute force subprocess clean-up
......@@ -80,57 +81,85 @@ func handleGetInfoRefs(w http.ResponseWriter, r *http.Request, a *api.Response)
w.Header().Add("Cache-Control", "no-cache")
w.WriteHeader(200) // Don't bother with HTTP 500 from this point on, just return
if err := pktLine(w, fmt.Sprintf("# service=%s\n", rpc)); err != nil {
helper.LogError(fmt.Errorf("handleGetInfoRefs: pktLine: %v", err))
helper.LogError(r, fmt.Errorf("handleGetInfoRefs: pktLine: %v", err))
return
}
if err := pktFlush(w); err != nil {
helper.LogError(fmt.Errorf("handleGetInfoRefs: pktFlush: %v", err))
helper.LogError(r, fmt.Errorf("handleGetInfoRefs: pktFlush: %v", err))
return
}
if _, err := io.Copy(w, stdout); err != nil {
helper.LogError(fmt.Errorf("handleGetInfoRefs: copy output of %v: %v", cmd.Args, err))
helper.LogError(
r,
&copyError{fmt.Errorf("handleGetInfoRefs: copy output of %v: %v", cmd.Args, err)},
)
return
}
if err := cmd.Wait(); err != nil {
helper.LogError(fmt.Errorf("handleGetInfoRefs: wait for %v: %v", cmd.Args, err))
helper.LogError(r, fmt.Errorf("handleGetInfoRefs: wait for %v: %v", cmd.Args, err))
return
}
}
func handlePostRPC(w http.ResponseWriter, r *http.Request, a *api.Response) {
var err error
var body io.Reader
var isShallowClone bool
// Get Git action from URL
action := filepath.Base(r.URL.Path)
if !(action == "git-upload-pack" || action == "git-receive-pack") {
// The 'dumb' Git HTTP protocol is not supported
helper.Fail500(w, fmt.Errorf("handlePostRPC: unsupported action: %s", r.URL.Path))
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: unsupported action: %s", r.URL.Path))
return
}
if action == "git-upload-pack" {
buffer := &bytes.Buffer{}
// Only sniff on the first 4096 bytes: we assume that if we find no
// 'deepen' message in the first 4096 bytes there won't be one later
// either.
_, err = io.Copy(buffer, io.LimitReader(r.Body, 4096))
if err != nil {
helper.Fail500(w, r, &copyError{fmt.Errorf("handlePostRPC: buffer git-upload-pack body: %v", err)})
return
}
isShallowClone, err = scanDeepen(bytes.NewReader(buffer.Bytes()))
body = io.MultiReader(buffer, r.Body)
if err != nil {
// Do not pass on the error: our failure to parse the
// request body should not abort the request.
helper.LogError(r, fmt.Errorf("parseBody (non-fatal): %v", err))
}
} else {
body = r.Body
}
// Prepare our Git subprocess
cmd := gitCommand(a.GL_ID, "git", subCommand(action), "--stateless-rpc", a.RepoPath)
stdout, err := cmd.StdoutPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("handlePostRPC: stdout: %v", err))
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: stdout: %v", err))
return
}
defer stdout.Close()
stdin, err := cmd.StdinPipe()
if err != nil {
helper.Fail500(w, fmt.Errorf("handlePostRPC: stdin: %v", err))
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: stdin: %v", err))
return
}
defer stdin.Close()
if err := cmd.Start(); err != nil {
helper.Fail500(w, fmt.Errorf("handlePostRPC: start %v: %v", cmd.Args, err))
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: start %v: %v", cmd.Args, err))
return
}
defer helper.CleanUpProcessGroup(cmd) // Ensure brute force subprocess clean-up
// Write the client request body to Git's standard input
if _, err := io.Copy(stdin, r.Body); err != nil {
helper.Fail500(w, fmt.Errorf("handlePostRPC write to %v: %v", cmd.Args, err))
if _, err := io.Copy(stdin, body); err != nil {
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: write to %v: %v", cmd.Args, err))
return
}
// Signal to the Git subprocess that no more data is coming
......@@ -147,25 +176,23 @@ func handlePostRPC(w http.ResponseWriter, r *http.Request, a *api.Response) {
// This io.Copy may take a long time, both for Git push and pull.
if _, err := io.Copy(w, stdout); err != nil {
helper.LogError(fmt.Errorf("handlePostRPC copy output of %v: %v", cmd.Args, err))
helper.LogError(
r,
&copyError{fmt.Errorf("handlePostRPC: copy output of %v: %v", cmd.Args, err)},
)
return
}
if err := cmd.Wait(); err != nil {
helper.LogError(fmt.Errorf("handlePostRPC wait for %v: %v", cmd.Args, err))
if err := cmd.Wait(); err != nil && !(isExitError(err) && isShallowClone) {
helper.LogError(r, fmt.Errorf("handlePostRPC: wait for %v: %v", cmd.Args, err))
return
}
}
func subCommand(rpc string) string {
return strings.TrimPrefix(rpc, "git-")
func isExitError(err error) bool {
_, ok := err.(*exec.ExitError)
return ok
}
func pktLine(w io.Writer, s string) error {
_, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
return err
}
func pktFlush(w io.Writer) error {
_, err := fmt.Fprint(w, "0000")
return err
func subCommand(rpc string) string {
return strings.TrimPrefix(rpc, "git-")
}
package git
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
)
func pktLine(w io.Writer, s string) error {
_, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
return err
}
func pktFlush(w io.Writer) error {
_, err := fmt.Fprint(w, "0000")
return err
}
func scanDeepen(body io.Reader) (bool, error) {
hasDeepen := false
scanner := bufio.NewScanner(body)
scanner.Split(pktLineSplitter)
for scanner.Scan() {
if bytes.HasPrefix(scanner.Bytes(), []byte("deepen")) {
hasDeepen = true
break
}
}
return hasDeepen, scanner.Err()
}
func pktLineSplitter(data []byte, atEOF bool) (advance int, token []byte, err error) {
if len(data) < 4 {
if atEOF && len(data) > 0 {
return 0, nil, fmt.Errorf("pktLineSplitter: incomplete length prefix on %q", data)
}
return 0, nil, nil // want more data
}
if bytes.HasPrefix(data, []byte("0000")) {
// special case: "0000" terminator packet: return empty token
return 4, data[:0], nil
}
// We have at least 4 bytes available so we can decode the 4-hex digit
// length prefix of the packet line.
pktLength64, err := strconv.ParseInt(string(data[:4]), 16, 0)
if err != nil {
return 0, nil, fmt.Errorf("pktLineSplitter: decode length: %v", err)
}
// Cast is safe because we requested an int-size number from strconv.ParseInt
pktLength := int(pktLength64)
if pktLength < 0 {
return 0, nil, fmt.Errorf("pktLineSplitter: invalid length: %d", pktLength)
}
if len(data) < pktLength {
if atEOF {
return 0, nil, fmt.Errorf("pktLineSplitter: less than %d bytes in input %q", pktLength, data)
}
return 0, nil, nil // want more data
}
// return "pkt" token without length prefix
return pktLength, data[4:pktLength], nil
}
package git
import (
"bytes"
"testing"
)
func TestSuccessfulScanDeepen(t *testing.T) {
examples := []struct {
input string