Imported Upstream version 2.5.1 upstream/2.5.1
authorhyokeun <hyokeun.jeon@samsung.com>
Fri, 21 Dec 2018 05:52:32 +0000 (14:52 +0900)
committerhyokeun <hyokeun.jeon@samsung.com>
Fri, 21 Dec 2018 05:52:32 +0000 (14:52 +0900)
20 files changed:
CHANGELOG.md
LICENSE.md
Makefile
commands/command_env.go
config/config.go
config/version.go
debian/changelog
docs/man/git-lfs-config.5.ronn
docs/man/mangen.go
errors/types.go
lfsapi/errors.go
rpm/SPECS/git-lfs.spec
script/install.sh [changed mode: 0644->0755]
t/t-content-type.sh [new file with mode: 0755]
t/t-env.sh
t/t-pull.sh
t/t-push-failures-remote.sh
tq/basic_upload.go
tq/transfer_queue.go
versioninfo.json

index 4c24d01..b98e9ac 100644 (file)
@@ -1,5 +1,29 @@
 # Git LFS Changelog
 
+## 2.5.1 (2 August, 2018)
+
+This release contains miscellaneous bug fixes since v2.5.0. Most notably,
+release v2.5.1 allows a user to disable automatic Content-Type detection
+(released in v2.5.0) via `git config lfs.contenttype false` for hosts that do
+not support it.
+
+### Features
+
+* tq: make Content-Type detection disable-able #3163 (@ttaylorr)
+
+### Bugs
+
+* Makefile: add explicit rule for commands/mancontent_gen.go #3160 (@jj1bdx)
+* script/install.sh: mark as executable #3155 (@ttaylorr)
+* config: add origin to remote list #3152 (@PastelMobileSuit)
+
+### Misc
+
+* docs/man/mangen.go: don't show non-fatal output without --verbose #3168 (@ttaylorr)
+* LICENSE.md: update copyright year #3156 (@IMJ355)
+* Makefile: silence some output #3164 (@ttaylorr)
+* Makefile: list prerequisites for resource.syso #3153 (@ttaylorr)
+
 ## 2.5.0 (26 July, 2018)
 
 This release adds three new migration modes, updated developer ergonomics, and
index b3e3c5d..e216fd3 100644 (file)
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2014-2016 GitHub, Inc. and Git LFS contributors
+Copyright (c) 2014-2018 GitHub, Inc. and Git LFS contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
index 59555d4..64ae0a5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -140,6 +140,17 @@ BUILD_TARGETS = bin/git-lfs-darwin-amd64 bin/git-lfs-darwin-386 \
        bin/git-lfs-freebsd-amd64 bin/git-lfs-freebsd-386 \
        bin/git-lfs-windows-amd64.exe bin/git-lfs-windows-386.exe
 
+# mangen is a shorthand for ensuring that commands/mancontent_gen.go is kept
+# up-to-date with the contents of docs/man/*.ronn.
+.PHONY : mangen
+mangen : commands/mancontent_gen.go
+
+# commands/mancontent_gen.go is generated by running 'go generate' on package
+# 'commands' of Git LFS. It depends upon the contents of the 'docs' directory
+# and converts those manpages into code.
+commands/mancontent_gen.go : $(wildcard docs/man/*.ronn)
+       $(GO) generate github.com/git-lfs/git-lfs/commands
+
 # Targets 'all' and 'build' build binaries of Git LFS for the above release
 # matrix.
 .PHONY : all build
@@ -153,21 +164,21 @@ all build : $(BUILD_TARGETS)
 #
 # On Windows, they also depend on the resource.syso target, which installs and
 # embeds the versioninfo into the binary.
-bin/git-lfs-darwin-amd64 : $(SOURCES)
+bin/git-lfs-darwin-amd64 : $(SOURCES) mangen
        $(call BUILD,darwin,amd64,-darwin-amd64)
-bin/git-lfs-darwin-386 : $(SOURCES)
+bin/git-lfs-darwin-386 : $(SOURCES) mangen
        $(call BUILD,darwin,386,-darwin-386)
-bin/git-lfs-linux-amd64 : $(SOURCES)
+bin/git-lfs-linux-amd64 : $(SOURCES) mangen
        $(call BUILD,linux,amd64,-linux-amd64)
-bin/git-lfs-linux-386 : $(SOURCES)
+bin/git-lfs-linux-386 : $(SOURCES) mangen
        $(call BUILD,linux,386,-linux-386)
-bin/git-lfs-freebsd-amd64 : $(SOURCES)
+bin/git-lfs-freebsd-amd64 : $(SOURCES) mangen
        $(call BUILD,freebsd,amd64,-freebsd-amd64)
-bin/git-lfs-freebsd-386 : $(SOURCES)
+bin/git-lfs-freebsd-386 : $(SOURCES) mangen
        $(call BUILD,freebsd,386,-freebsd-386)
-bin/git-lfs-windows-amd64.exe : resource.syso $(SOURCES)
+bin/git-lfs-windows-amd64.exe : resource.syso $(SOURCES) mangen
        $(call BUILD,windows,amd64,-windows-amd64.exe)
-bin/git-lfs-windows-386.exe : resource.syso $(SOURCES)
+bin/git-lfs-windows-386.exe : resource.syso $(SOURCES) mangen
        $(call BUILD,windows,386,-windows-386.exe)
 
 # .DEFAULT_GOAL sets the operating system-appropriate Git LFS binary as the
@@ -176,18 +187,24 @@ bin/git-lfs-windows-386.exe : resource.syso $(SOURCES)
 
 # bin/git-lfs targets the default output of Git LFS on non-Windows operating
 # systems, and respects the build knobs as above.
-bin/git-lfs : $(SOURCES) fmt
+bin/git-lfs : $(SOURCES) fmt mangen
        $(call BUILD,$(GOOS),$(GOARCH),)
 
 # bin/git-lfs.exe targets the default output of Git LFS on Windows systems, and
 # respects the build knobs as above.
-bin/git-lfs.exe : $(SOURCES) resource.syso
+bin/git-lfs.exe : $(SOURCES) resource.syso mangen
        $(call BUILD,$(GOOS),$(GOARCH),.exe)
 
 # resource.syso installs the 'goversioninfo' command and uses it in order to
 # generate a binary that has information included necessary to create the
 # Windows installer.
-resource.syso:
+#
+# Generating a new resource.syso is a pure function of the contents in the
+# prerequisites listed below.
+resource.syso : \
+versioninfo.json script/windows-installer/git-lfs-logo.bmp \
+script/windows-installer/git-lfs-logo.ico \
+script/windows-installer/git-lfs-wizard-image.bmp
        @$(GO) get github.com/josephspurrier/goversioninfo/cmd/goversioninfo
        $(GO) generate
 
@@ -305,7 +322,7 @@ vendor : glide.lock
 .PHONY : fmt
 ifeq ($(shell test -x "`which $(GOIMPORTS)`"; echo $$?),0)
 fmt : $(SOURCES) | lint
-       $(GOIMPORTS) $(GOIMPORTS_EXTRA_OPTS) $?;
+       @$(GOIMPORTS) $(GOIMPORTS_EXTRA_OPTS) $?;
 else
 fmt : $(SOURCES) | lint
        @echo "git-lfs: skipping fmt, no goimports found at \`$(GOIMPORTS)\` ..."
@@ -315,7 +332,7 @@ endif
 # are vendored in via vendor (see: above).
 .PHONY : lint
 lint : $(SOURCES)
-       $(GO) list -f '{{ join .Deps "\n" }}' . \
+       @$(GO) list -f '{{ join .Deps "\n" }}' . \
        | $(XARGS) $(GO) list -f '{{ if not .Standard }}{{ .ImportPath }}{{ end }}' \
        | $(GREP) -v "github.com/git-lfs/git-lfs" || exit 0
 
index d463318..6d76528 100644 (file)
@@ -19,8 +19,10 @@ func envCommand(cmd *cobra.Command, args []string) {
        Print(gitV)
        Print("")
 
+       defaultRemote := ""
        if cfg.IsDefaultRemote() {
-               endpoint := getAPIClient().Endpoints.Endpoint("download", cfg.Remote())
+               defaultRemote = cfg.Remote()
+               endpoint := getAPIClient().Endpoints.Endpoint("download", defaultRemote)
                if len(endpoint.Url) > 0 {
                        access := getAPIClient().Endpoints.AccessFor(endpoint.Url)
                        Print("Endpoint=%s (auth=%s)", endpoint.Url, access)
@@ -31,6 +33,9 @@ func envCommand(cmd *cobra.Command, args []string) {
        }
 
        for _, remote := range cfg.Remotes() {
+               if remote == defaultRemote {
+                       continue
+               }
                remoteEndpoint := getAPIClient().Endpoints.RemoteEndpoint("download", remote)
                remoteAccess := getAPIClient().Endpoints.AccessFor(remoteEndpoint.Url)
                Print("Endpoint (%s)=%s (auth=%s)", remote, remoteEndpoint.Url, remoteAccess)
index 0f0c5a2..b6945b4 100644 (file)
@@ -82,10 +82,7 @@ func (c *Configuration) readGitConfig(gitconfigs ...*git.ConfigurationSource) En
        gf, extensions, uniqRemotes := readGitConfig(gitconfigs...)
        c.extensions = extensions
        c.remotes = make([]string, 0, len(uniqRemotes))
-       for remote, isOrigin := range uniqRemotes {
-               if isOrigin {
-                       continue
-               }
+       for remote := range uniqRemotes {
                c.remotes = append(c.remotes, remote)
        }
 
index e11dfe7..16b6410 100644 (file)
@@ -12,7 +12,7 @@ var (
 )
 
 const (
-       Version = "2.5.0"
+       Version = "2.5.1"
 )
 
 func init() {
index ecd7ae4..1bb2e38 100644 (file)
@@ -1,3 +1,9 @@
+git-lfs (2.5.1) stable; urgency=low
+
+  * New upstream version
+
+ -- Taylor Blau <me@ttaylorr.com>  Thu, 2 Aug 2018 14:29:00 +0000
+
 git-lfs (2.5.0) stable; urgency=low
 
   * New upstream version
index 579742f..5ad6375 100644 (file)
@@ -266,6 +266,13 @@ be scoped inside the configuration for a remote.
   https://git-scm.com/docs/git-config#git-config-httplturlgt. To set this value
   per-host: `git config --global lfs.https://github.com/.locksverify [true|false]`.
 
+* `lfs.<url>.contenttype`
+
+  Determines whether Git LFS should attempt to detect an appropriate HTTP
+  `Content-Type` header when uploading using the 'basic' upload adapter. If set
+  to false, the default header of `Content-Type: application/octet-stream` is
+  chosen instead. Default: 'true'.
+
 * `lfs.skipdownloaderrors`
 
   Causes Git LFS not to abort the smudge filter when a download error is
index ccf651d..b1cda4f 100644 (file)
@@ -2,7 +2,9 @@ package main
 
 import (
        "bufio"
+       "flag"
        "fmt"
+       "io"
        "io/ioutil"
        "os"
        "path/filepath"
@@ -10,6 +12,17 @@ import (
        "strings"
 )
 
+func infof(w io.Writer, format string, a ...interface{}) {
+       if !*verbose {
+               return
+       }
+       fmt.Fprintf(w, format, a...)
+}
+
+func warnf(w io.Writer, format string, a ...interface{}) {
+       fmt.Fprintf(w, format, a...)
+}
+
 func readManDir() (string, []os.FileInfo) {
        rootDirs := []string{
                "..",
@@ -24,23 +37,29 @@ func readManDir() (string, []os.FileInfo) {
                }
        }
 
-       fmt.Fprintf(os.Stderr, "Failed to open man dir: %v\n", err)
+       warnf(os.Stderr, "Failed to open man dir: %v\n", err)
        os.Exit(2)
        return "", nil
 }
 
+var (
+       verbose = flag.Bool("verbose", false, "Show verbose output.")
+)
+
 // Reads all .ronn files & and converts them to string literals
 // triggered by "go generate" comment
 // Literals are inserted into a map using an init function, this means
 // that there are no compilation errors if 'go generate' hasn't been run, just
 // blank man files.
 func main() {
-       fmt.Fprintf(os.Stderr, "Converting man pages into code...\n")
+       flag.Parse()
+
+       infof(os.Stderr, "Converting man pages into code...\n")
        rootDir, fs := readManDir()
        manDir := filepath.Join(rootDir, "docs", "man")
        out, err := os.Create(filepath.Join(rootDir, "commands", "mancontent_gen.go"))
        if err != nil {
-               fmt.Fprintf(os.Stderr, "Failed to create go file: %v\n", err)
+               warnf(os.Stderr, "Failed to create go file: %v\n", err)
                os.Exit(2)
        }
        out.WriteString("package commands\n\nfunc init() {\n")
@@ -55,7 +74,7 @@ func main() {
        count := 0
        for _, f := range fs {
                if match := fileregex.FindStringSubmatch(f.Name()); match != nil {
-                       fmt.Fprintf(os.Stderr, "%v\n", f.Name())
+                       infof(os.Stderr, "%v\n", f.Name())
                        cmd := match[1]
                        if len(cmd) == 0 {
                                // This is git-lfs.1.ronn
@@ -64,7 +83,7 @@ func main() {
                        out.WriteString("ManPages[\"" + cmd + "\"] = `")
                        contentf, err := os.Open(filepath.Join(manDir, f.Name()))
                        if err != nil {
-                               fmt.Fprintf(os.Stderr, "Failed to open %v: %v\n", f.Name(), err)
+                               warnf(os.Stderr, "Failed to open %v: %v\n", f.Name(), err)
                                os.Exit(2)
                        }
                        // Process the ronn to make it nicer as help text
@@ -145,6 +164,6 @@ func main() {
                }
        }
        out.WriteString("}\n")
-       fmt.Fprintf(os.Stderr, "Successfully processed %d man pages.\n", count)
+       infof(os.Stderr, "Successfully processed %d man pages.\n", count)
 
 }
index b7253fa..d032454 100644 (file)
@@ -127,6 +127,20 @@ func IsDownloadDeclinedError(err error) bool {
        return false
 }
 
+// IsDownloadDeclinedError indicates that the upload operation failed because of
+// an HTTP 422 response code.
+func IsUnprocessableEntityError(err error) bool {
+       if e, ok := err.(interface {
+               UnprocessableEntityError() bool
+       }); ok {
+               return e.UnprocessableEntityError()
+       }
+       if parent := parentOf(err); parent != nil {
+               return IsUnprocessableEntityError(parent)
+       }
+       return false
+}
+
 // IsRetriableError indicates the low level transfer had an error but the
 // caller may retry the operation.
 func IsRetriableError(err error) bool {
@@ -321,6 +335,20 @@ func NewDownloadDeclinedError(err error, msg string) error {
        return downloadDeclinedError{newWrappedError(err, msg)}
 }
 
+// Definitions for IsUnprocessableEntityError()
+
+type unprocessableEntityError struct {
+       *wrappedError
+}
+
+func (e unprocessableEntityError) UnprocessableEntityError() bool {
+       return true
+}
+
+func NewUnprocessableEntityError(err error) error {
+       return unprocessableEntityError{newWrappedError(err, "")}
+}
+
 // Definitions for IsRetriableError()
 
 type retriableError struct {
index dd9b64e..916633b 100644 (file)
@@ -58,6 +58,10 @@ func (c *Client) handleResponse(res *http.Response) error {
                return errors.NewAuthError(err)
        }
 
+       if res.StatusCode == 422 {
+               return errors.NewUnprocessableEntityError(err)
+       }
+
        if res.StatusCode > 499 && res.StatusCode != 501 && res.StatusCode != 507 && res.StatusCode != 509 {
                return errors.NewFatalError(err)
        }
@@ -92,6 +96,7 @@ var (
                401: "Authorization error: %s\nCheck that you have proper access to the repository",
                403: "Authorization error: %s\nCheck that you have proper access to the repository",
                404: "Repository or object not found: %s\nCheck that it exists and that you have proper access to it",
+               422: "Unprocessable entity: %s",
                429: "Rate limit exceeded: %s",
                500: "Server error: %s",
                501: "Not Implemented: %s",
index 58a05d1..c8cebdb 100644 (file)
@@ -1,5 +1,5 @@
 Name:           git-lfs
-Version:        2.5.0
+Version:        2.5.1
 Release:        1%{?dist}
 Summary:        Git extension for versioning large files
 
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/t/t-content-type.sh b/t/t-content-type.sh
new file mode 100755 (executable)
index 0000000..3b0faea
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+
+. "$(dirname "$0")/testlib.sh"
+
+begin_test "content-type: is enabled by default"
+(
+  set -e
+
+  reponame="content-type-enabled-default"
+  setup_remote_repo "$reponame"
+  clone_repo "$reponame" "$reponame"
+
+  git lfs track "*.tar.gz"
+  printf "aaaaaaaaaa" > a.txt
+  tar -czf a.tar.gz a.txt
+  rm a.txt
+
+  git add .gitattributes a.tar.gz
+  git commit -m "initial commit"
+  GIT_CURL_VERBOSE=1 git push origin master 2>&1 | tee push.log
+
+  [ 1 -eq "$(grep -c "Content-Type: application/x-gzip" push.log)" ]
+)
+end_test
+
+begin_test "content-type: is disabled by configuration"
+(
+  set -e
+
+  reponame="content-type-disabled-by-configuration"
+  setup_remote_repo "$reponame"
+  clone_repo "$reponame" "$reponame"
+
+  git lfs track "*.tar.gz"
+  printf "aaaaaaaaaa" > a.txt
+  tar -czf a.tar.gz a.txt
+  rm a.txt
+
+  git add .gitattributes a.tar.gz
+  git commit -m "initial commit"
+  git config "lfs.$GITSERVER.contenttype" 0
+  GIT_CURL_VERBOSE=1 git push origin master 2>&1 | tee push.log
+
+  [ 0 -eq "$(grep -c "Content-Type: application/x-gzip" push.log)" ]
+)
+end_test
+
+begin_test "content-type: warning message"
+(
+  set -e
+
+  reponame="content-type-warning-message"
+  setup_remote_repo "$reponame"
+  clone_repo "$reponame" "$reponame"
+
+  git lfs track "*.txt"
+  printf "status-storage-422" > a.txt
+
+  git add .gitattributes a.txt
+  git commit -m "initial commit"
+  git push origin master 2>&1 | tee push.log
+
+  grep "info: Uploading failed due to unsupported Content-Type header(s)." push.log
+  grep "info: Consider disabling Content-Type detection with:" push.log
+  grep "info:   $ git config lfs.contenttype false" push.log
+)
+end_test
index 5145a3f..e121309 100755 (executable)
@@ -901,3 +901,61 @@ UploadTransfers=basic,supertransfer,tus
 
 )
 end_test
+
+begin_test "env with multiple remotes and ref"
+(
+  set -e
+  reponame="env-multiple-remotes-ref"
+  mkdir $reponame
+  cd $reponame
+  git init
+  git remote add origin "$GITSERVER/env-origin-remote"
+  git remote add other "$GITSERVER/env-other-remote"
+
+  touch a.txt
+  git add a.txt
+  git commit -m "initial commit"
+
+  endpoint="$GITSERVER/env-origin-remote.git/info/lfs (auth=none)"
+  endpoint2="$GITSERVER/env-other-remote.git/info/lfs (auth=none)"
+  localwd=$(native_path "$TRASHDIR/$reponame")
+  localgit=$(native_path "$TRASHDIR/$reponame/.git")
+  localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
+  lfsstorage=$(native_path "$TRASHDIR/$reponame/.git/lfs")
+  localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
+  tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
+  envVars=$(printf "%s" "$(env | grep "^GIT")")
+  expected=$(printf '%s
+%s
+
+Endpoint=%s
+Endpoint (other)=%s
+LocalWorkingDir=%s
+LocalGitDir=%s
+LocalGitStorageDir=%s
+LocalMediaDir=%s
+LocalReferenceDirs=
+TempDir=%s
+ConcurrentTransfers=3
+TusTransfers=false
+BasicTransfersOnly=false
+SkipDownloadErrors=false
+FetchRecentAlways=false
+FetchRecentRefsDays=7
+FetchRecentCommitsDays=0
+FetchRecentRefsIncludeRemotes=true
+PruneOffsetDays=3
+PruneVerifyRemoteAlways=false
+PruneRemoteName=origin
+LfsStorageDir=%s
+AccessDownload=none
+AccessUpload=none
+DownloadTransfers=basic
+UploadTransfers=basic
+%s
+%s
+' "$(git lfs version)" "$(git version)" "$endpoint" "$endpoint2" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$lfsstorage" "$envVars" "$envInitConfig")
+  actual=$(git lfs env | grep -v "^GIT_EXEC_PATH=")
+  contains_same_elements "$expected" "$actual"
+)
+end_test
index b46eaa3..33607e7 100755 (executable)
@@ -218,6 +218,36 @@ begin_test "pull with raw remote url"
 )
 end_test
 
+begin_test "pull with multiple remotes"
+(
+  set -e
+  mkdir multiple
+  cd multiple
+  git init
+  git lfs install --local --skip-smudge
+
+  git remote add origin "$GITSERVER/t-pull"
+  git remote add bad-remote "invalid-url"
+  git pull origin master
+
+  contents="a"
+  contents_oid=$(calc_oid "$contents")
+
+  # LFS object not downloaded, pointer in working directory
+  refute_local_object "$contents_oid"
+  grep "$contents_oid" a.dat
+
+  # pull should default to origin instead of bad-remote
+  git lfs pull
+  echo "pulled!"
+
+  # LFS object downloaded and in working directory
+  assert_local_object "$contents_oid" 1
+  [ "0" = "$(grep -c "$contents_oid" a.dat)" ]
+  [ "a" = "$(cat a.dat)" ]
+)
+end_test
+
 begin_test "pull: with missing object"
 (
   set -e
index b5b6c8a..358e72c 100755 (executable)
@@ -65,14 +65,6 @@ begin_test "push: upload file with storage 410"
 )
 end_test
 
-begin_test "push: upload file with storage 422"
-(
-  set -e
-
-  push_fail_test "status-storage-422"
-)
-end_test
-
 begin_test "push: upload file with storage 500"
 (
   set -e
index 8e7a3d3..8557565 100644 (file)
@@ -9,6 +9,7 @@ import (
        "strconv"
        "strings"
 
+       "github.com/git-lfs/git-lfs/config"
        "github.com/git-lfs/git-lfs/errors"
        "github.com/git-lfs/git-lfs/lfsapi"
        "github.com/git-lfs/git-lfs/tools"
@@ -72,7 +73,7 @@ func (a *basicUploadAdapter) DoTransfer(ctx interface{}, t *Transfer, cb Progres
        }
        defer f.Close()
 
-       if err := setContentTypeFor(req, f); err != nil {
+       if err := a.setContentTypeFor(req, f); err != nil {
                return err
        }
 
@@ -101,6 +102,17 @@ func (a *basicUploadAdapter) DoTransfer(ctx interface{}, t *Transfer, cb Progres
        req = a.apiClient.LogRequest(req, "lfs.data.upload")
        res, err := a.doHTTP(t, req)
        if err != nil {
+               if errors.IsUnprocessableEntityError(err) {
+                       // If we got an HTTP 422, we do _not_ want to retry the
+                       // request later below, because it is likely that the
+                       // implementing server does not support non-standard
+                       // Content-Type headers.
+                       //
+                       // Instead, return immediately and wait for the
+                       // *tq.TransferQueue to report an error message.
+                       return err
+               }
+
                // We're about to return a retriable error, meaning that this
                // transfer will either be retried, or it will fail.
                //
@@ -135,6 +147,32 @@ func (a *basicUploadAdapter) DoTransfer(ctx interface{}, t *Transfer, cb Progres
        return verifyUpload(a.apiClient, a.remote, t)
 }
 
+func (a *adapterBase) setContentTypeFor(req *http.Request, r io.ReadSeeker) error {
+       uc := config.NewURLConfig(a.apiClient.GitEnv())
+       disabled := !uc.Bool("lfs", req.URL.String(), "contenttype", true)
+       if len(req.Header.Get("Content-Type")) != 0 || disabled {
+               return nil
+       }
+
+       buffer := make([]byte, 512)
+       n, err := r.Read(buffer)
+       if err != nil && err != io.EOF {
+               return errors.Wrap(err, "content type detect")
+       }
+
+       contentType := http.DetectContentType(buffer[:n])
+       if _, err := r.Seek(0, 0); err != nil {
+               return errors.Wrap(err, "content type rewind")
+       }
+
+       if contentType == "" {
+               contentType = defaultContentType
+       }
+
+       req.Header.Set("Content-Type", contentType)
+       return nil
+}
+
 // startCallbackReader is a reader wrapper which calls a function as soon as the
 // first Read() call is made. This callback is only made once
 type startCallbackReader struct {
@@ -173,27 +211,3 @@ func configureBasicUploadAdapter(m *Manifest) {
                return nil
        })
 }
-
-func setContentTypeFor(req *http.Request, r io.ReadSeeker) error {
-       if len(req.Header.Get("Content-Type")) != 0 {
-               return nil
-       }
-
-       buffer := make([]byte, 512)
-       n, err := r.Read(buffer)
-       if err != nil && err != io.EOF {
-               return errors.Wrap(err, "content type detect")
-       }
-
-       contentType := http.DetectContentType(buffer[:n])
-       if _, err := r.Seek(0, 0); err != nil {
-               return errors.Wrap(err, "content type rewind")
-       }
-
-       if contentType == "" {
-               contentType = defaultContentType
-       }
-
-       req.Header.Set("Content-Type", contentType)
-       return nil
-}
index 81367ce..eba9f67 100644 (file)
@@ -1,6 +1,7 @@
 package tq
 
 import (
+       "fmt"
        "os"
        "sort"
        "sync"
@@ -127,6 +128,11 @@ type TransferQueue struct {
        wait     sync.WaitGroup
        manifest *Manifest
        rc       *retryCounter
+
+       // unsupportedContentType indicates whether the transfer queue ever saw
+       // an HTTP 422 response indicating that their upload destination does
+       // not support Content-Type detection.
+       unsupportedContentType bool
 }
 
 // objects holds a set of objects.
@@ -651,8 +657,13 @@ func (q *TransferQueue) handleTransferResult(
                        // If the error wasn't retriable, OR the object has
                        // exceeded its retry budget, it will be NOT be sent to
                        // the retry channel, and the error will be reported
-                       // immediately.
-                       q.errorc <- res.Error
+                       // immediately (unless the error is in response to a
+                       // HTTP 422).
+                       if errors.IsUnprocessableEntityError(res.Error) {
+                               q.unsupportedContentType = true
+                       } else {
+                               q.errorc <- res.Error
+                       }
                        q.wait.Done()
                }
        } else {
@@ -763,6 +774,17 @@ func (q *TransferQueue) toAdapterCfg(e lfsapi.Endpoint) AdapterConfig {
        }
 }
 
+var (
+       // contentTypeWarning is the message printed when a server returns an
+       // HTTP 422 at the end of a push.
+       contentTypeWarning = []string{
+               "Uploading failed due to unsupported Content-Type header(s).",
+               "Consider disabling Content-Type detection with:",
+               "",
+               "  $ git config lfs.contenttype false",
+       }
+)
+
 // Wait waits for the queue to finish processing all transfers. Once Wait is
 // called, Add will no longer add transfers to the queue. Any failed
 // transfers will be automatically retried once.
@@ -781,6 +803,12 @@ func (q *TransferQueue) Wait() {
 
        q.meter.Flush()
        q.errorwait.Wait()
+
+       if q.unsupportedContentType {
+               for _, line := range contentTypeWarning {
+                       fmt.Fprintf(os.Stderr, "info: %s\n", line)
+               }
+       }
 }
 
 // Watch returns a channel where the queue will write the value of each transfer
index a32767e..c181d24 100644 (file)
@@ -4,7 +4,7 @@
                "FileVersion": {
                        "Major": 2,
                        "Minor": 5,
-                       "Patch": 0,
+                       "Patch": 1,
                        "Build": 0
                }
        },
@@ -13,7 +13,7 @@
                "FileDescription": "Git LFS",
                "LegalCopyright": "GitHub, Inc. and Git LFS contributors",
                "ProductName": "Git Large File Storage (LFS)",
-               "ProductVersion": "2.5.0"
+               "ProductVersion": "2.5.1"
        },
        "IconPath": "script/windows-installer/git-lfs-logo.ico"
 }