Skip to content

Commit 16918a5

Browse files
authored
Merge pull request go-git#88 from distorhead/cherryPickCommonDirSupport
storage: filesystem, support .git/commondir repository layout
2 parents 5cafe40 + 63c42e5 commit 16918a5

File tree

8 files changed

+370
-2
lines changed

8 files changed

+370
-2
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/gliderlabs/ssh v0.2.2
99
github.com/go-git/gcfg v1.5.0
1010
github.com/go-git/go-billy/v5 v5.0.0
11-
github.com/go-git/go-git-fixtures/v4 v4.0.1
11+
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12
1212
github.com/google/go-cmp v0.3.0
1313
github.com/imdario/mergo v0.3.9
1414
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agR
2020
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
2121
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
2222
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
23+
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
24+
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
2325
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
2426
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
2527
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=

options.go

+3
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,9 @@ type PlainOpenOptions struct {
602602
// DetectDotGit defines whether parent directories should be
603603
// walked until a .git directory or file is found.
604604
DetectDotGit bool
605+
// Enable .git/commondir support (see https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt).
606+
// NOTE: This option will only work with the filesystem storage.
607+
EnableDotGitCommonDir bool
605608
}
606609

607610
// Validate validates the fields and sets the default values.

repository.go

+48-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"strings"
1414
"time"
1515

16+
"github.com/go-git/go-git/v5/storage/filesystem/dotgit"
17+
1618
"github.com/go-git/go-git/v5/config"
1719
"github.com/go-git/go-git/v5/internal/revision"
1820
"github.com/go-git/go-git/v5/plumbing"
@@ -47,6 +49,7 @@ var (
4749

4850
ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch")
4951
ErrRepositoryNotExists = errors.New("repository does not exist")
52+
ErrRepositoryIncomplete = errors.New("repository's commondir path does not exist")
5053
ErrRepositoryAlreadyExists = errors.New("repository already exists")
5154
ErrRemoteNotFound = errors.New("remote not found")
5255
ErrRemoteExists = errors.New("remote already exists")
@@ -253,7 +256,19 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error)
253256
return nil, err
254257
}
255258

256-
s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
259+
var repositoryFs billy.Filesystem
260+
261+
if o.EnableDotGitCommonDir {
262+
dotGitCommon, err := dotGitCommonDirectory(dot)
263+
if err != nil {
264+
return nil, err
265+
}
266+
repositoryFs = dotgit.NewRepositoryFilesystem(dot, dotGitCommon)
267+
} else {
268+
repositoryFs = dot
269+
}
270+
271+
s := filesystem.NewStorage(repositoryFs, cache.NewObjectLRUDefault())
257272

258273
return Open(s, wt)
259274
}
@@ -328,6 +343,38 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files
328343
return osfs.New(fs.Join(path, gitdir)), nil
329344
}
330345

346+
func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err error) {
347+
f, err := fs.Open("commondir")
348+
if os.IsNotExist(err) {
349+
return nil, nil
350+
}
351+
if err != nil {
352+
return nil, err
353+
}
354+
355+
b, err := stdioutil.ReadAll(f)
356+
if err != nil {
357+
return nil, err
358+
}
359+
if len(b) > 0 {
360+
path := strings.TrimSpace(string(b))
361+
if filepath.IsAbs(path) {
362+
commonDir = osfs.New(path)
363+
} else {
364+
commonDir = osfs.New(filepath.Join(fs.Root(), path))
365+
}
366+
if _, err := commonDir.Stat(""); err != nil {
367+
if os.IsNotExist(err) {
368+
return nil, ErrRepositoryIncomplete
369+
}
370+
371+
return nil, err
372+
}
373+
}
374+
375+
return commonDir, nil
376+
}
377+
331378
// PlainClone a repository into the path with the given options, isBare defines
332379
// if the new repository will be bare or normal. If the path is not empty
333380
// ErrRepositoryAlreadyExists is returned.

storage/filesystem/dotgit/dotgit.go

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ const (
3030
objectsPath = "objects"
3131
packPath = "pack"
3232
refsPath = "refs"
33+
branchesPath = "branches"
34+
hooksPath = "hooks"
35+
infoPath = "info"
36+
remotesPath = "remotes"
37+
logsPath = "logs"
38+
worktreesPath = "worktrees"
3339

3440
tmpPackedRefsPrefix = "._packed-refs"
3541

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package dotgit
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
8+
"github.com/go-git/go-billy/v5"
9+
)
10+
11+
// RepositoryFilesystem is a billy.Filesystem compatible object wrapper
12+
// which handles dot-git filesystem operations and supports commondir according to git scm layout:
13+
// https://github.com/git/git/blob/master/Documentation/gitrepository-layout.txt
14+
type RepositoryFilesystem struct {
15+
dotGitFs billy.Filesystem
16+
commonDotGitFs billy.Filesystem
17+
}
18+
19+
func NewRepositoryFilesystem(dotGitFs, commonDotGitFs billy.Filesystem) *RepositoryFilesystem {
20+
return &RepositoryFilesystem{
21+
dotGitFs: dotGitFs,
22+
commonDotGitFs: commonDotGitFs,
23+
}
24+
}
25+
26+
func (fs *RepositoryFilesystem) mapToRepositoryFsByPath(path string) billy.Filesystem {
27+
// Nothing to decide if commondir not defined
28+
if fs.commonDotGitFs == nil {
29+
return fs.dotGitFs
30+
}
31+
32+
cleanPath := filepath.Clean(path)
33+
34+
// Check exceptions for commondir (https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt)
35+
switch cleanPath {
36+
case fs.dotGitFs.Join(logsPath, "HEAD"):
37+
return fs.dotGitFs
38+
case fs.dotGitFs.Join(refsPath, "bisect"), fs.dotGitFs.Join(refsPath, "rewritten"), fs.dotGitFs.Join(refsPath, "worktree"):
39+
return fs.dotGitFs
40+
}
41+
42+
// Determine dot-git root by first path element.
43+
// There are some elements which should always use commondir when commondir defined.
44+
// Usual dot-git root will be used for the rest of files.
45+
switch strings.Split(cleanPath, string(filepath.Separator))[0] {
46+
case objectsPath, refsPath, packedRefsPath, configPath, branchesPath, hooksPath, infoPath, remotesPath, logsPath, shallowPath, worktreesPath:
47+
return fs.commonDotGitFs
48+
default:
49+
return fs.dotGitFs
50+
}
51+
}
52+
53+
func (fs *RepositoryFilesystem) Create(filename string) (billy.File, error) {
54+
return fs.mapToRepositoryFsByPath(filename).Create(filename)
55+
}
56+
57+
func (fs *RepositoryFilesystem) Open(filename string) (billy.File, error) {
58+
return fs.mapToRepositoryFsByPath(filename).Open(filename)
59+
}
60+
61+
func (fs *RepositoryFilesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
62+
return fs.mapToRepositoryFsByPath(filename).OpenFile(filename, flag, perm)
63+
}
64+
65+
func (fs *RepositoryFilesystem) Stat(filename string) (os.FileInfo, error) {
66+
return fs.mapToRepositoryFsByPath(filename).Stat(filename)
67+
}
68+
69+
func (fs *RepositoryFilesystem) Rename(oldpath, newpath string) error {
70+
return fs.mapToRepositoryFsByPath(oldpath).Rename(oldpath, newpath)
71+
}
72+
73+
func (fs *RepositoryFilesystem) Remove(filename string) error {
74+
return fs.mapToRepositoryFsByPath(filename).Remove(filename)
75+
}
76+
77+
func (fs *RepositoryFilesystem) Join(elem ...string) string {
78+
return fs.dotGitFs.Join(elem...)
79+
}
80+
81+
func (fs *RepositoryFilesystem) TempFile(dir, prefix string) (billy.File, error) {
82+
return fs.mapToRepositoryFsByPath(dir).TempFile(dir, prefix)
83+
}
84+
85+
func (fs *RepositoryFilesystem) ReadDir(path string) ([]os.FileInfo, error) {
86+
return fs.mapToRepositoryFsByPath(path).ReadDir(path)
87+
}
88+
89+
func (fs *RepositoryFilesystem) MkdirAll(filename string, perm os.FileMode) error {
90+
return fs.mapToRepositoryFsByPath(filename).MkdirAll(filename, perm)
91+
}
92+
93+
func (fs *RepositoryFilesystem) Lstat(filename string) (os.FileInfo, error) {
94+
return fs.mapToRepositoryFsByPath(filename).Lstat(filename)
95+
}
96+
97+
func (fs *RepositoryFilesystem) Symlink(target, link string) error {
98+
return fs.mapToRepositoryFsByPath(target).Symlink(target, link)
99+
}
100+
101+
func (fs *RepositoryFilesystem) Readlink(link string) (string, error) {
102+
return fs.mapToRepositoryFsByPath(link).Readlink(link)
103+
}
104+
105+
func (fs *RepositoryFilesystem) Chroot(path string) (billy.Filesystem, error) {
106+
return fs.mapToRepositoryFsByPath(path).Chroot(path)
107+
}
108+
109+
func (fs *RepositoryFilesystem) Root() string {
110+
return fs.dotGitFs.Root()
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package dotgit
2+
3+
import (
4+
"io/ioutil"
5+
"log"
6+
"os"
7+
8+
"github.com/go-git/go-billy/v5/osfs"
9+
10+
. "gopkg.in/check.v1"
11+
)
12+
13+
func (s *SuiteDotGit) TestRepositoryFilesystem(c *C) {
14+
dir, err := ioutil.TempDir("", "repository_filesystem")
15+
if err != nil {
16+
log.Fatal(err)
17+
}
18+
defer os.RemoveAll(dir)
19+
20+
fs := osfs.New(dir)
21+
22+
err = fs.MkdirAll("dotGit", 0777)
23+
c.Assert(err, IsNil)
24+
dotGitFs, err := fs.Chroot("dotGit")
25+
c.Assert(err, IsNil)
26+
27+
err = fs.MkdirAll("commonDotGit", 0777)
28+
c.Assert(err, IsNil)
29+
commonDotGitFs, err := fs.Chroot("commonDotGit")
30+
c.Assert(err, IsNil)
31+
32+
repositoryFs := NewRepositoryFilesystem(dotGitFs, commonDotGitFs)
33+
c.Assert(repositoryFs.Root(), Equals, dotGitFs.Root())
34+
35+
somedir, err := repositoryFs.Chroot("somedir")
36+
c.Assert(err, IsNil)
37+
c.Assert(somedir.Root(), Equals, repositoryFs.Join(dotGitFs.Root(), "somedir"))
38+
39+
_, err = repositoryFs.Create("somefile")
40+
c.Assert(err, IsNil)
41+
42+
_, err = repositoryFs.Stat("somefile")
43+
c.Assert(err, IsNil)
44+
45+
file, err := repositoryFs.Open("somefile")
46+
c.Assert(err, IsNil)
47+
err = file.Close()
48+
c.Assert(err, IsNil)
49+
50+
file, err = repositoryFs.OpenFile("somefile", os.O_RDONLY, 0666)
51+
c.Assert(err, IsNil)
52+
err = file.Close()
53+
c.Assert(err, IsNil)
54+
55+
file, err = repositoryFs.Create("somefile2")
56+
c.Assert(err, IsNil)
57+
err = file.Close()
58+
c.Assert(err, IsNil)
59+
_, err = repositoryFs.Stat("somefile2")
60+
c.Assert(err, IsNil)
61+
err = repositoryFs.Rename("somefile2", "newfile")
62+
c.Assert(err, IsNil)
63+
64+
tempDir, err := repositoryFs.TempFile("tmp", "myprefix")
65+
c.Assert(err, IsNil)
66+
c.Assert(repositoryFs.Join(repositoryFs.Root(), "tmp", tempDir.Name()), Equals, repositoryFs.Join(dotGitFs.Root(), "tmp", tempDir.Name()))
67+
68+
err = repositoryFs.Symlink("newfile", "somelink")
69+
c.Assert(err, IsNil)
70+
71+
_, err = repositoryFs.Lstat("somelink")
72+
c.Assert(err, IsNil)
73+
74+
link, err := repositoryFs.Readlink("somelink")
75+
c.Assert(err, IsNil)
76+
c.Assert(link, Equals, "newfile")
77+
78+
err = repositoryFs.Remove("somelink")
79+
c.Assert(err, IsNil)
80+
81+
_, err = repositoryFs.Stat("somelink")
82+
c.Assert(os.IsNotExist(err), Equals, true)
83+
84+
dirs := []string{objectsPath, refsPath, packedRefsPath, configPath, branchesPath, hooksPath, infoPath, remotesPath, logsPath, shallowPath, worktreesPath}
85+
for _, dir := range dirs {
86+
err := repositoryFs.MkdirAll(dir, 0777)
87+
c.Assert(err, IsNil)
88+
_, err = commonDotGitFs.Stat(dir)
89+
c.Assert(err, IsNil)
90+
_, err = dotGitFs.Stat(dir)
91+
c.Assert(os.IsNotExist(err), Equals, true)
92+
}
93+
94+
exceptionsPaths := []string{repositoryFs.Join(logsPath, "HEAD"), repositoryFs.Join(refsPath, "bisect"), repositoryFs.Join(refsPath, "rewritten"), repositoryFs.Join(refsPath, "worktree")}
95+
for _, path := range exceptionsPaths {
96+
_, err := repositoryFs.Create(path)
97+
c.Assert(err, IsNil)
98+
_, err = commonDotGitFs.Stat(path)
99+
c.Assert(os.IsNotExist(err), Equals, true)
100+
_, err = dotGitFs.Stat(path)
101+
c.Assert(err, IsNil)
102+
}
103+
104+
err = repositoryFs.MkdirAll("refs/heads", 0777)
105+
c.Assert(err, IsNil)
106+
_, err = commonDotGitFs.Stat("refs/heads")
107+
c.Assert(err, IsNil)
108+
_, err = dotGitFs.Stat("refs/heads")
109+
c.Assert(os.IsNotExist(err), Equals, true)
110+
111+
err = repositoryFs.MkdirAll("objects/pack", 0777)
112+
c.Assert(err, IsNil)
113+
_, err = commonDotGitFs.Stat("objects/pack")
114+
c.Assert(err, IsNil)
115+
_, err = dotGitFs.Stat("objects/pack")
116+
c.Assert(os.IsNotExist(err), Equals, true)
117+
118+
err = repositoryFs.MkdirAll("a/b/c", 0777)
119+
c.Assert(err, IsNil)
120+
_, err = commonDotGitFs.Stat("a/b/c")
121+
c.Assert(os.IsNotExist(err), Equals, true)
122+
_, err = dotGitFs.Stat("a/b/c")
123+
c.Assert(err, IsNil)
124+
}

0 commit comments

Comments
 (0)