12 "github.com/git-lfs/git-lfs/config"
13 "github.com/git-lfs/git-lfs/errors"
14 "github.com/git-lfs/git-lfs/lfsapi"
15 "github.com/git-lfs/git-lfs/tools"
19 BasicAdapterName = "basic"
20 defaultContentType = "application/octet-stream"
23 // Adapter for basic uploads (non resumable)
24 type basicUploadAdapter struct {
28 func (a *basicUploadAdapter) ClearTempStorage() error {
29 // Should be empty already but also remove dir
30 return os.RemoveAll(a.tempDir())
33 func (a *basicUploadAdapter) tempDir() string {
34 // Must be dedicated to this adapter as deleted by ClearTempStorage
35 d := filepath.Join(os.TempDir(), "git-lfs-basic-temp")
36 if err := os.MkdirAll(d, 0755); err != nil {
42 func (a *basicUploadAdapter) WorkerStarting(workerNum int) (interface{}, error) {
45 func (a *basicUploadAdapter) WorkerEnding(workerNum int, ctx interface{}) {
48 func (a *basicUploadAdapter) DoTransfer(ctx interface{}, t *Transfer, cb ProgressCallback, authOkFunc func()) error {
49 rel, err := t.Rel("upload")
54 return errors.Errorf("No upload action for object: %s", t.Oid)
57 req, err := a.newHTTPRequest("PUT", rel)
62 if req.Header.Get("Transfer-Encoding") == "chunked" {
63 req.TransferEncoding = []string{"chunked"}
65 req.Header.Set("Content-Length", strconv.FormatInt(t.Size, 10))
68 req.ContentLength = t.Size
70 f, err := os.OpenFile(t.Path, os.O_RDONLY, 0644)
72 return errors.Wrap(err, "basic upload")
76 if err := a.setContentTypeFor(req, f); err != nil {
80 // Ensure progress callbacks made while uploading
81 // Wrap callback to give name context
82 ccb := func(totalSize int64, readSoFar int64, readSinceLast int) error {
84 return cb(t.Name, totalSize, readSoFar, readSinceLast)
89 cbr := tools.NewBodyWithCallback(f, t.Size, ccb)
90 var reader lfsapi.ReadSeekCloser = cbr
92 // Signal auth was ok on first read; this frees up other workers to start
93 if authOkFunc != nil {
94 reader = newStartCallbackReader(reader, func() error {
102 req = a.apiClient.LogRequest(req, "lfs.data.upload")
103 res, err := a.doHTTP(t, req)
105 if errors.IsUnprocessableEntityError(err) {
106 // If we got an HTTP 422, we do _not_ want to retry the
107 // request later below, because it is likely that the
108 // implementing server does not support non-standard
109 // Content-Type headers.
111 // Instead, return immediately and wait for the
112 // *tq.TransferQueue to report an error message.
116 // We're about to return a retriable error, meaning that this
117 // transfer will either be retried, or it will fail.
119 // Either way, let's decrement the number of bytes that we've
120 // read _so far_, so that the next iteration doesn't re-transfer
121 // those bytes, according to the progress meter.
122 if perr := cbr.ResetProgress(); perr != nil {
123 err = errors.Wrap(err, perr.Error())
126 return errors.NewRetriableError(err)
129 // A status code of 403 likely means that an authentication token for the
130 // upload has expired. This can be safely retried.
131 if res.StatusCode == 403 {
132 err = errors.New("http: received status 403")
133 return errors.NewRetriableError(err)
136 if res.StatusCode > 299 {
137 return errors.Wrapf(nil, "Invalid status for %s %s: %d",
139 strings.SplitN(req.URL.String(), "?", 2)[0],
144 io.Copy(ioutil.Discard, res.Body)
147 return verifyUpload(a.apiClient, a.remote, t)
150 func (a *adapterBase) setContentTypeFor(req *http.Request, r io.ReadSeeker) error {
151 uc := config.NewURLConfig(a.apiClient.GitEnv())
152 disabled := !uc.Bool("lfs", req.URL.String(), "contenttype", true)
153 if len(req.Header.Get("Content-Type")) != 0 || disabled {
157 buffer := make([]byte, 512)
158 n, err := r.Read(buffer)
159 if err != nil && err != io.EOF {
160 return errors.Wrap(err, "content type detect")
163 contentType := http.DetectContentType(buffer[:n])
164 if _, err := r.Seek(0, 0); err != nil {
165 return errors.Wrap(err, "content type rewind")
168 if contentType == "" {
169 contentType = defaultContentType
172 req.Header.Set("Content-Type", contentType)
176 // startCallbackReader is a reader wrapper which calls a function as soon as the
177 // first Read() call is made. This callback is only made once
178 type startCallbackReader struct {
181 lfsapi.ReadSeekCloser
184 func (s *startCallbackReader) Read(p []byte) (n int, err error) {
185 if !s.cbDone && s.cb != nil {
186 if err := s.cb(); err != nil {
191 return s.ReadSeekCloser.Read(p)
193 func newStartCallbackReader(r lfsapi.ReadSeekCloser, cb func() error) *startCallbackReader {
194 return &startCallbackReader{
200 func configureBasicUploadAdapter(m *Manifest) {
201 m.RegisterNewAdapterFunc(BasicAdapterName, Upload, func(name string, dir Direction) Adapter {
204 bu := &basicUploadAdapter{newAdapterBase(m.fs, name, dir, nil)}
205 // self implements impl
209 panic("Should never ask this func for basic download")