12 "github.com/git-lfs/git-lfs/tools"
15 var oidRE = regexp.MustCompile(`\A[[:alnum:]]{64}`)
17 // Object represents a locally stored LFS object.
23 type Filesystem struct {
24 GitStorageDir string // parent of objects/lfs (may be same as GitDir but may not)
25 LFSStorageDir string // parent of lfs objects and tmp dirs. Default: ".git/lfs"
26 ReferenceDir string // alternative local media dir (relative to clone reference repo)
33 func (f *Filesystem) EachObject(fn func(Object) error) error {
35 tools.FastWalkGitRepo(f.LFSObjectDir(), func(parentDir string, info os.FileInfo, err error) {
40 if eachErr != nil || info.IsDir() {
43 if oidRE.MatchString(info.Name()) {
44 fn(Object{Oid: info.Name(), Size: info.Size()})
50 func (f *Filesystem) ObjectExists(oid string, size int64) bool {
51 return tools.FileExistsOfSize(f.ObjectPathname(oid), size)
54 func (f *Filesystem) ObjectPath(oid string) (string, error) {
55 dir := f.localObjectDir(oid)
56 if err := os.MkdirAll(dir, 0755); err != nil {
57 return "", fmt.Errorf("Error trying to create local storage directory in %q: %s", dir, err)
59 return filepath.Join(dir, oid), nil
62 func (f *Filesystem) ObjectPathname(oid string) string {
63 return filepath.Join(f.localObjectDir(oid), oid)
66 func (f *Filesystem) localObjectDir(oid string) string {
67 return filepath.Join(f.LFSObjectDir(), oid[0:2], oid[2:4])
70 func (f *Filesystem) ObjectReferencePath(oid string) string {
71 if len(f.ReferenceDir) == 0 {
75 return filepath.Join(f.ReferenceDir, oid[0:2], oid[2:4], oid)
78 func (f *Filesystem) LFSObjectDir() string {
82 if len(f.lfsobjdir) == 0 {
83 f.lfsobjdir = filepath.Join(f.LFSStorageDir, "objects")
84 os.MkdirAll(f.lfsobjdir, 0755)
90 func (f *Filesystem) LogDir() string {
94 if len(f.logdir) == 0 {
95 f.logdir = filepath.Join(f.LFSStorageDir, "logs")
96 os.MkdirAll(f.logdir, 0755)
102 func (f *Filesystem) TempDir() string {
106 if len(f.tmpdir) == 0 {
107 f.tmpdir = filepath.Join(f.LFSStorageDir, "tmp")
108 os.MkdirAll(f.tmpdir, 0755)
114 func (f *Filesystem) Cleanup() error {
118 return f.cleanupTmp()
121 // New initializes a new *Filesystem with the given directories. gitdir is the
122 // path to the bare repo, workdir is the path to the repository working
123 // directory, and lfsdir is the optional path to the `.git/lfs` directory.
124 func New(gitdir, workdir, lfsdir string) *Filesystem {
126 GitStorageDir: resolveGitStorageDir(gitdir),
129 fs.ReferenceDir = resolveReferenceDir(fs.GitStorageDir)
131 if len(lfsdir) == 0 {
135 if filepath.IsAbs(lfsdir) {
136 fs.LFSStorageDir = lfsdir
138 fs.LFSStorageDir = filepath.Join(fs.GitStorageDir, lfsdir)
144 func resolveReferenceDir(gitStorageDir string) string {
145 cloneReferencePath := filepath.Join(gitStorageDir, "objects", "info", "alternates")
146 if tools.FileExists(cloneReferencePath) {
147 buffer, err := ioutil.ReadFile(cloneReferencePath)
149 path := strings.TrimSpace(string(buffer[:]))
150 referenceLfsStoragePath := filepath.Join(filepath.Dir(path), "lfs", "objects")
151 if tools.DirExists(referenceLfsStoragePath) {
152 return referenceLfsStoragePath
159 // From a git dir, get the location that objects are to be stored (we will store lfs alongside)
160 // Sometimes there is an additional level of redirect on the .git folder by way of a commondir file
161 // before you find object storage, e.g. 'git worktree' uses this. It redirects to gitdir either by GIT_DIR
162 // (during setup) or .git/git-dir: (during use), but this only contains the index etc, the objects
163 // are found in another git dir via 'commondir'.
164 func resolveGitStorageDir(gitDir string) string {
165 commondirpath := filepath.Join(gitDir, "commondir")
166 if tools.FileExists(commondirpath) && !tools.DirExists(filepath.Join(gitDir, "objects")) {
167 // no git-dir: prefix in commondir
168 storage, err := processGitRedirectFile(commondirpath, "")
176 func processGitRedirectFile(file, prefix string) (string, error) {
177 data, err := ioutil.ReadFile(file)
182 contents := string(data)
185 if !strings.HasPrefix(contents, prefix) {
186 // Prefix required & not found
189 dir = strings.TrimSpace(contents[len(prefix):])
191 dir = strings.TrimSpace(contents)
194 if !filepath.IsAbs(dir) {
195 // The .git file contains a relative path.
196 // Create an absolute path based on the directory the .git file is located in.
197 dir = filepath.Join(filepath.Dir(file), dir)