b174f1a480a3ed70574548f9b9f89e78175a185d
[scm/test.git] / fs / fs.go
1 package fs
2
3 import (
4         "fmt"
5         "io/ioutil"
6         "os"
7         "path/filepath"
8         "regexp"
9         "strings"
10         "sync"
11
12         "github.com/git-lfs/git-lfs/tools"
13 )
14
15 var oidRE = regexp.MustCompile(`\A[[:alnum:]]{64}`)
16
17 // Object represents a locally stored LFS object.
18 type Object struct {
19         Oid  string
20         Size int64
21 }
22
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)
27         lfsobjdir     string
28         tmpdir        string
29         logdir        string
30         mu            sync.Mutex
31 }
32
33 func (f *Filesystem) EachObject(fn func(Object) error) error {
34         var eachErr error
35         tools.FastWalkGitRepo(f.LFSObjectDir(), func(parentDir string, info os.FileInfo, err error) {
36                 if err != nil {
37                         eachErr = err
38                         return
39                 }
40                 if eachErr != nil || info.IsDir() {
41                         return
42                 }
43                 if oidRE.MatchString(info.Name()) {
44                         fn(Object{Oid: info.Name(), Size: info.Size()})
45                 }
46         })
47         return eachErr
48 }
49
50 func (f *Filesystem) ObjectExists(oid string, size int64) bool {
51         return tools.FileExistsOfSize(f.ObjectPathname(oid), size)
52 }
53
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)
58         }
59         return filepath.Join(dir, oid), nil
60 }
61
62 func (f *Filesystem) ObjectPathname(oid string) string {
63         return filepath.Join(f.localObjectDir(oid), oid)
64 }
65
66 func (f *Filesystem) localObjectDir(oid string) string {
67         return filepath.Join(f.LFSObjectDir(), oid[0:2], oid[2:4])
68 }
69
70 func (f *Filesystem) ObjectReferencePath(oid string) string {
71         if len(f.ReferenceDir) == 0 {
72                 return f.ReferenceDir
73         }
74
75         return filepath.Join(f.ReferenceDir, oid[0:2], oid[2:4], oid)
76 }
77
78 func (f *Filesystem) LFSObjectDir() string {
79         f.mu.Lock()
80         defer f.mu.Unlock()
81
82         if len(f.lfsobjdir) == 0 {
83                 f.lfsobjdir = filepath.Join(f.LFSStorageDir, "objects")
84                 os.MkdirAll(f.lfsobjdir, 0755)
85         }
86
87         return f.lfsobjdir
88 }
89
90 func (f *Filesystem) LogDir() string {
91         f.mu.Lock()
92         defer f.mu.Unlock()
93
94         if len(f.logdir) == 0 {
95                 f.logdir = filepath.Join(f.LFSStorageDir, "logs")
96                 os.MkdirAll(f.logdir, 0755)
97         }
98
99         return f.logdir
100 }
101
102 func (f *Filesystem) TempDir() string {
103         f.mu.Lock()
104         defer f.mu.Unlock()
105
106         if len(f.tmpdir) == 0 {
107                 f.tmpdir = filepath.Join(f.LFSStorageDir, "tmp")
108                 os.MkdirAll(f.tmpdir, 0755)
109         }
110
111         return f.tmpdir
112 }
113
114 func (f *Filesystem) Cleanup() error {
115         if f == nil {
116                 return nil
117         }
118         return f.cleanupTmp()
119 }
120
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 {
125         fs := &Filesystem{
126                 GitStorageDir: resolveGitStorageDir(gitdir),
127         }
128
129         fs.ReferenceDir = resolveReferenceDir(fs.GitStorageDir)
130
131         if len(lfsdir) == 0 {
132                 lfsdir = "lfs"
133         }
134
135         if filepath.IsAbs(lfsdir) {
136                 fs.LFSStorageDir = lfsdir
137         } else {
138                 fs.LFSStorageDir = filepath.Join(fs.GitStorageDir, lfsdir)
139         }
140
141         return fs
142 }
143
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)
148                 if err == nil {
149                         path := strings.TrimSpace(string(buffer[:]))
150                         referenceLfsStoragePath := filepath.Join(filepath.Dir(path), "lfs", "objects")
151                         if tools.DirExists(referenceLfsStoragePath) {
152                                 return referenceLfsStoragePath
153                         }
154                 }
155         }
156         return ""
157 }
158
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, "")
169                 if err == nil {
170                         return storage
171                 }
172         }
173         return gitDir
174 }
175
176 func processGitRedirectFile(file, prefix string) (string, error) {
177         data, err := ioutil.ReadFile(file)
178         if err != nil {
179                 return "", err
180         }
181
182         contents := string(data)
183         var dir string
184         if len(prefix) > 0 {
185                 if !strings.HasPrefix(contents, prefix) {
186                         // Prefix required & not found
187                         return "", nil
188                 }
189                 dir = strings.TrimSpace(contents[len(prefix):])
190         } else {
191                 dir = strings.TrimSpace(contents)
192         }
193
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)
198         }
199
200         return dir, nil
201 }