github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/file_storer.go (about) 1 package odb 2 3 import ( 4 "encoding/hex" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 10 "github.com/git-lfs/git-lfs/errors" 11 ) 12 13 // fileStorer implements the storer interface by writing to the .git/objects 14 // directory on disc. 15 type fileStorer struct { 16 // root is the top level /objects directory's path on disc. 17 root string 18 19 // temp directory, defaults to os.TempDir 20 tmp string 21 } 22 23 // NewFileStorer returns a new fileStorer instance with the given root. 24 func newFileStorer(root, tmp string) *fileStorer { 25 return &fileStorer{ 26 root: root, 27 tmp: tmp, 28 } 29 } 30 31 // Open implements the storer.Open function, and returns a io.ReadWriteCloser 32 // for the given SHA. If the file does not exist, or if there was any other 33 // error in opening the file, an error will be returned. 34 // 35 // It is the caller's responsibility to close the given file "f" after its use 36 // is complete. 37 func (fs *fileStorer) Open(sha []byte) (f io.ReadWriteCloser, err error) { 38 return fs.open(fs.path(sha), os.O_RDONLY) 39 } 40 41 // Store implements the storer.Store function and returns the number of bytes 42 // written, along with any error encountered in copying the given io.Reader, "r" 43 // into the object database on disk at a path given by "sha". 44 // 45 // If the file could not be created, or opened, an error will be returned. 46 func (fs *fileStorer) Store(sha []byte, r io.Reader) (n int64, err error) { 47 path := fs.path(sha) 48 dir := filepath.Dir(path) 49 50 if stat, err := os.Stat(path); stat != nil || os.IsExist(err) { 51 // If the file already exists, there is no work left for us to 52 // do, since the object already exists (or there is a SHA1 53 // collision). 54 _, err = io.Copy(ioutil.Discard, r) 55 if err != nil { 56 return 0, errors.Wrap(err, "discard pre-existing object data") 57 } 58 59 return 0, nil 60 } 61 62 tmp, err := ioutil.TempFile(fs.tmp, "") 63 if err != nil { 64 return 0, err 65 } 66 67 n, err = io.Copy(tmp, r) 68 if err = tmp.Close(); err != nil { 69 return n, err 70 } 71 if err != nil { 72 return n, err 73 } 74 75 // Since .git/objects partitions objects based on the first two 76 // characters of their ASCII-encoded SHA1 object ID, ensure that 77 // the directory exists before copying a file into it. 78 if err = os.MkdirAll(dir, 0755); err != nil { 79 return n, err 80 } 81 82 if err = os.Rename(tmp.Name(), path); err != nil { 83 return n, err 84 } 85 86 return n, nil 87 } 88 89 // Root gives the absolute (fully-qualified) path to the file storer on disk. 90 func (fs *fileStorer) Root() string { 91 return fs.root 92 } 93 94 // open opens a given file. 95 func (fs *fileStorer) open(path string, flag int) (*os.File, error) { 96 return os.OpenFile(path, flag, 0) 97 } 98 99 // path returns an absolute path on disk to the object given by the OID "sha". 100 func (fs *fileStorer) path(sha []byte) string { 101 encoded := hex.EncodeToString(sha) 102 103 return filepath.Join(fs.root, encoded[:2], encoded[2:]) 104 }