From: hyokeun Date: Fri, 21 Dec 2018 05:52:32 +0000 (+0900) Subject: Imported Upstream version 2.5.1 X-Git-Tag: upstream/2.5.1^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9c4312c52abad638fc6da7f42abcfa637270b83e;p=scm%2Ftest.git Imported Upstream version 2.5.1 --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c24d01..b98e9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/LICENSE.md b/LICENSE.md index b3e3c5d..e216fd3 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -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 diff --git a/Makefile b/Makefile index 59555d4..64ae0a5 100644 --- 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 diff --git a/commands/command_env.go b/commands/command_env.go index d463318..6d76528 100644 --- a/commands/command_env.go +++ b/commands/command_env.go @@ -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) diff --git a/config/config.go b/config/config.go index 0f0c5a2..b6945b4 100644 --- a/config/config.go +++ b/config/config.go @@ -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) } diff --git a/config/version.go b/config/version.go index e11dfe7..16b6410 100644 --- a/config/version.go +++ b/config/version.go @@ -12,7 +12,7 @@ var ( ) const ( - Version = "2.5.0" + Version = "2.5.1" ) func init() { diff --git a/debian/changelog b/debian/changelog index ecd7ae4..1bb2e38 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +git-lfs (2.5.1) stable; urgency=low + + * New upstream version + + -- Taylor Blau Thu, 2 Aug 2018 14:29:00 +0000 + git-lfs (2.5.0) stable; urgency=low * New upstream version diff --git a/docs/man/git-lfs-config.5.ronn b/docs/man/git-lfs-config.5.ronn index 579742f..5ad6375 100644 --- a/docs/man/git-lfs-config.5.ronn +++ b/docs/man/git-lfs-config.5.ronn @@ -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..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 diff --git a/docs/man/mangen.go b/docs/man/mangen.go index ccf651d..b1cda4f 100644 --- a/docs/man/mangen.go +++ b/docs/man/mangen.go @@ -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) } diff --git a/errors/types.go b/errors/types.go index b7253fa..d032454 100644 --- a/errors/types.go +++ b/errors/types.go @@ -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 { diff --git a/lfsapi/errors.go b/lfsapi/errors.go index dd9b64e..916633b 100644 --- a/lfsapi/errors.go +++ b/lfsapi/errors.go @@ -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", diff --git a/rpm/SPECS/git-lfs.spec b/rpm/SPECS/git-lfs.spec index 58a05d1..c8cebdb 100644 --- a/rpm/SPECS/git-lfs.spec +++ b/rpm/SPECS/git-lfs.spec @@ -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 diff --git a/script/install.sh b/script/install.sh old mode 100644 new mode 100755 diff --git a/t/t-content-type.sh b/t/t-content-type.sh new file mode 100755 index 0000000..3b0faea --- /dev/null +++ b/t/t-content-type.sh @@ -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 diff --git a/t/t-env.sh b/t/t-env.sh index 5145a3f..e121309 100755 --- a/t/t-env.sh +++ b/t/t-env.sh @@ -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 diff --git a/t/t-pull.sh b/t/t-pull.sh index b46eaa3..33607e7 100755 --- a/t/t-pull.sh +++ b/t/t-pull.sh @@ -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 diff --git a/t/t-push-failures-remote.sh b/t/t-push-failures-remote.sh index b5b6c8a..358e72c 100755 --- a/t/t-push-failures-remote.sh +++ b/t/t-push-failures-remote.sh @@ -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 diff --git a/tq/basic_upload.go b/tq/basic_upload.go index 8e7a3d3..8557565 100644 --- a/tq/basic_upload.go +++ b/tq/basic_upload.go @@ -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 -} diff --git a/tq/transfer_queue.go b/tq/transfer_queue.go index 81367ce..eba9f67 100644 --- a/tq/transfer_queue.go +++ b/tq/transfer_queue.go @@ -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 diff --git a/versioninfo.json b/versioninfo.json index a32767e..c181d24 100644 --- a/versioninfo.json +++ b/versioninfo.json @@ -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" }