Skip to content

Commit 415b398

Browse files
authored
Merge pull request #15 from bk2204/alternate-object-directories
Alternate object directories
2 parents f17ed8d + f4a1d43 commit 415b398

File tree

5 files changed

+131
-4
lines changed

5 files changed

+131
-4
lines changed

backend.go

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
1417
func 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

backend_nix.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// +build !windows
2+
3+
package gitobj
4+
5+
const alternatesSeparator = ":"

backend_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/hex"
66
"io"
77
"io/ioutil"
8+
"reflect"
89
"testing"
910

1011
"github.com/stretchr/testify/assert"
@@ -62,3 +63,26 @@ func TestNewMemoryBackendWithWritableData(t *testing.T) {
6263
assert.NoError(t, err)
6364
assert.Equal(t, []byte{0x1}, contents)
6465
}
66+
67+
func TestSplitAlternatesString(t *testing.T) {
68+
testCases := []struct {
69+
input string
70+
expected []string
71+
}{
72+
{"abc", []string{"abc"}},
73+
{"abc:def", []string{"abc", "def"}},
74+
{`"abc":def`, []string{"abc", "def"}},
75+
{`"i\alike\bcomplicated\tstrings":def`, []string{"i\alike\bcomplicated\tstrings", "def"}},
76+
{`abc:"i\nlike\vcomplicated\fstrings\r":def`, []string{"abc", "i\nlike\vcomplicated\fstrings\r", "def"}},
77+
{`abc:"uni\xc2\xa9ode":def`, []string{"abc", "uni©ode", "def"}},
78+
{`abc:"uni\302\251ode\10\0":def`, []string{"abc", "uni©ode\x08\x00", "def"}},
79+
{`abc:"cookie\\monster\"":def`, []string{"abc", "cookie\\monster\"", "def"}},
80+
}
81+
82+
for _, test := range testCases {
83+
actual := splitAlternateString(test.input, ":")
84+
if !reflect.DeepEqual(actual, test.expected) {
85+
t.Errorf("unexpected output for %q: got %v, expected %v", test.input, actual, test.expected)
86+
}
87+
}
88+
}

backend_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// +build windows
2+
3+
package gitobj
4+
5+
const alternatesSeparator = ";"

object_db.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@ type ObjectDatabase struct {
3636
//
3737
// /absolute/repo/path/.git/objects
3838
func FromFilesystem(root, tmp string) (*ObjectDatabase, error) {
39-
b, err := NewFilesystemBackend(root, tmp)
39+
return FromFilesystemWithAlternates(root, tmp, "")
40+
}
41+
42+
// FromFilesystemWithAlternates constructs an *ObjectDatabase instance that is
43+
// backed by a directory on the filesystem, optionally with one or more
44+
// alternates. Specifically, this should point to:
45+
//
46+
// /absolute/repo/path/.git/objects
47+
func FromFilesystemWithAlternates(root, tmp, alternates string) (*ObjectDatabase, error) {
48+
b, err := NewFilesystemBackendWithAlternates(root, tmp, alternates)
4049
if err != nil {
4150
return nil, err
4251
}

0 commit comments

Comments
 (0)