@@ -5,13 +5,24 @@ import (
55 "io"
66 "os"
77 "path"
8+ "regexp"
9+ "strconv"
10+ "strings"
811
912 "github.com/git-lfs/gitobj/pack"
1013 "github.com/git-lfs/gitobj/storage"
1114)
1215
1316// NewFilesystemBackend initializes a new filesystem-based backend.
1417func NewFilesystemBackend (root , tmp string ) (storage.Backend , error ) {
18+ return NewFilesystemBackendWithAlternates (root , tmp , "" )
19+ }
20+
21+ // NewFilesystemBackendWithAlternates initializes a new filesystem-based
22+ // backend, optionally with additional alternates as specified in the
23+ // `alternates` variable. The syntax is that of the Git environment variable
24+ // GIT_ALTERNATE_OBJECT_DIRECTORIES.
25+ func NewFilesystemBackendWithAlternates (root , tmp , alternates string ) (storage.Backend , error ) {
1526 fsobj := newFileStorer (root , tmp )
1627 packs , err := pack .NewStorage (root )
1728 if err != nil {
@@ -23,6 +34,11 @@ func NewFilesystemBackend(root, tmp string) (storage.Backend, error) {
2334 return nil , err
2435 }
2536
37+ storage , err = addAlternatesFromEnvironment (storage , alternates )
38+ if err != nil {
39+ return nil , err
40+ }
41+
2642 return & filesystemBackend {
2743 fs : fsobj ,
2844 backends : storage ,
@@ -45,12 +61,10 @@ func findAllBackends(mainLoose *fileStorer, mainPacked *pack.Storage, root strin
4561
4662 scanner := bufio .NewScanner (f )
4763 for scanner .Scan () {
48- storage = append (storage , newFileStorer (scanner .Text (), "" ))
49- pack , err := pack .NewStorage (scanner .Text ())
64+ storage , err = addAlternateDirectory (storage , scanner .Text ())
5065 if err != nil {
5166 return nil , err
5267 }
53- storage = append (storage , pack )
5468 }
5569
5670 if err := scanner .Err (); err != nil {
@@ -60,6 +74,76 @@ func findAllBackends(mainLoose *fileStorer, mainPacked *pack.Storage, root strin
6074 return storage , nil
6175}
6276
77+ func addAlternateDirectory (s []storage.Storage , dir string ) ([]storage.Storage , error ) {
78+ s = append (s , newFileStorer (dir , "" ))
79+ pack , err := pack .NewStorage (dir )
80+ if err != nil {
81+ return s , err
82+ }
83+ s = append (s , pack )
84+ return s , nil
85+ }
86+
87+ func addAlternatesFromEnvironment (s []storage.Storage , env string ) ([]storage.Storage , error ) {
88+ if len (env ) == 0 {
89+ return s , nil
90+ }
91+
92+ for _ , dir := range splitAlternateString (env , alternatesSeparator ) {
93+ var err error
94+ s , err = addAlternateDirectory (s , dir )
95+ if err != nil {
96+ return nil , err
97+ }
98+ }
99+ return s , nil
100+ }
101+
102+ var (
103+ octalEscape = regexp .MustCompile ("\\ \\ [0-7]{1,3}" )
104+ hexEscape = regexp .MustCompile ("\\ \\ x[0-9a-fA-F]{2}" )
105+ replacements = []struct {
106+ olds string
107+ news string
108+ }{
109+ {`\a` , "\a " },
110+ {`\b` , "\b " },
111+ {`\t` , "\t " },
112+ {`\n` , "\n " },
113+ {`\v` , "\v " },
114+ {`\f` , "\f " },
115+ {`\r` , "\r " },
116+ {`\\` , "\\ " },
117+ {`\"` , "\" " },
118+ {`\'` , "'" },
119+ }
120+ )
121+
122+ func splitAlternateString (env string , separator string ) []string {
123+ dirs := strings .Split (env , separator )
124+ for i , s := range dirs {
125+ if ! strings .HasPrefix (s , `"` ) || ! strings .HasSuffix (s , `"` ) {
126+ continue
127+ }
128+
129+ // Strip leading and trailing quotation marks
130+ s = s [1 : len (s )- 1 ]
131+ for _ , repl := range replacements {
132+ s = strings .Replace (s , repl .olds , repl .news , - 1 )
133+ }
134+ s = octalEscape .ReplaceAllStringFunc (s , func (inp string ) string {
135+ val , _ := strconv .ParseUint (inp [1 :], 8 , 64 )
136+ return string ([]byte {byte (val )})
137+ })
138+ s = hexEscape .ReplaceAllStringFunc (s , func (inp string ) string {
139+ val , _ := strconv .ParseUint (inp [2 :], 16 , 64 )
140+ return string ([]byte {byte (val )})
141+ })
142+ dirs [i ] = s
143+ }
144+ return dirs
145+ }
146+
63147// NewMemoryBackend initializes a new memory-based backend.
64148//
65149// A value of "nil" is acceptable and indicates that no entries should be added
0 commit comments