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  }