Skip to content

Commit f4a1d43

Browse files
committed
Add support for alternates from the environment
Add support for alternates in the environment by allowing users to specify the contents of the environment variable GIT_ALTERNATE_OBJECT_DIRECTORIES and parsing that to discover additional alternates. Add tests for the handling of the paths, which is reasonably complex.
1 parent 94f66b1 commit f4a1d43

5 files changed

+120
-1
lines changed

backend.go

+76
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,
@@ -68,6 +84,66 @@ func addAlternateDirectory(s []storage.Storage, dir string) ([]storage.Storage,
6884
return s, nil
6985
}
7086

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+
71147
// NewMemoryBackend initializes a new memory-based backend.
72148
//
73149
// A value of "nil" is acceptable and indicates that no entries should be added

backend_nix.go

+5
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

+24
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

+5
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

+10-1
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)