Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 27a9f5b

Browse files
committedMar 29, 2019
storage/worktree: filesystem, support common directory/linked repositories.
Signed-off-by: Arran Walker <[email protected]>
1 parent db6c41c commit 27a9f5b

File tree

5 files changed

+162
-11
lines changed

5 files changed

+162
-11
lines changed
 

‎repository.go

+40-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ var (
4646

4747
ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch")
4848
ErrRepositoryNotExists = errors.New("repository does not exist")
49+
ErrRepositoryIncomplete = errors.New("repository's commondir path does not exist")
4950
ErrRepositoryAlreadyExists = errors.New("repository already exists")
5051
ErrRemoteNotFound = errors.New("remote not found")
5152
ErrRemoteExists = errors.New("remote already exists")
@@ -251,7 +252,13 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error)
251252
return nil, err
252253
}
253254

254-
s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
255+
options := filesystem.Options{}
256+
options.CommonDir, err = dotGitCommonDirectory(dot)
257+
if err != nil {
258+
return nil, err
259+
}
260+
261+
s := filesystem.NewStorageWithOptions(dot, cache.NewObjectLRUDefault(), options)
255262

256263
return Open(s, wt)
257264
}
@@ -326,6 +333,38 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files
326333
return osfs.New(fs.Join(path, gitdir)), nil
327334
}
328335

336+
func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err error) {
337+
f, err := fs.Open("commondir")
338+
if os.IsNotExist(err) {
339+
return nil, nil
340+
}
341+
if err != nil {
342+
return nil, err
343+
}
344+
345+
b, err := stdioutil.ReadAll(f)
346+
if err != nil {
347+
return nil, err
348+
}
349+
if len(b) > 0 {
350+
path := strings.TrimSpace(string(b))
351+
if filepath.IsAbs(path) {
352+
commonDir = osfs.New(path)
353+
} else {
354+
commonDir = osfs.New(filepath.Join(fs.Root(), path))
355+
}
356+
if _, err := commonDir.Stat(""); err != nil {
357+
if os.IsNotExist(err) {
358+
return nil, ErrRepositoryIncomplete
359+
}
360+
361+
return nil, err
362+
}
363+
}
364+
365+
return commonDir, nil
366+
}
367+
329368
// PlainClone a repository into the path with the given options, isBare defines
330369
// if the new repository will be bare or normal. If the path is not empty
331370
// ErrRepositoryAlreadyExists is returned.

‎storage/filesystem/dotgit/dotgit.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,17 @@ type Options struct {
6666
// KeepDescriptors makes the file descriptors to be reused but they will
6767
// need to be manually closed calling Close().
6868
KeepDescriptors bool
69+
// CommonDir sets the directory used for accessing non-worktree files that
70+
// would normally be taken from the root directory.
71+
CommonDir billy.Filesystem
6972
}
7073

7174
// The DotGit type represents a local git repository on disk. This
7275
// type is not zero-value-safe, use the New function to initialize it.
7376
type DotGit struct {
7477
options Options
7578
fs billy.Filesystem
79+
localfs billy.Filesystem
7680

7781
// incoming object directory information
7882
incomingChecked bool
@@ -96,9 +100,14 @@ func New(fs billy.Filesystem) *DotGit {
96100
// NewWithOptions sets non default configuration options.
97101
// See New for complete help.
98102
func NewWithOptions(fs billy.Filesystem, o Options) *DotGit {
103+
if o.CommonDir == nil {
104+
o.CommonDir = fs
105+
}
106+
99107
return &DotGit{
100108
options: o,
101-
fs: fs,
109+
fs: o.CommonDir,
110+
localfs: fs,
102111
}
103112
}
104113

@@ -923,7 +932,8 @@ func (d *DotGit) addRefFromHEAD(refs *[]*plumbing.Reference) error {
923932

924933
func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference, err error) {
925934
path = d.fs.Join(path, d.fs.Join(strings.Split(name, "/")...))
926-
f, err := d.fs.Open(path)
935+
936+
f, err := d.fsFromRefPath(path).Open(path)
927937
if err != nil {
928938
return nil, err
929939
}
@@ -932,6 +942,15 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference,
932942
return d.readReferenceFrom(f, name)
933943
}
934944

945+
func (d *DotGit) fsFromRefPath(path string) billy.Filesystem {
946+
// In general, all pseudo refs are per working tree and all refs starting
947+
// with "refs/" are shared.
948+
if strings.HasPrefix(path, "refs/") {
949+
return d.fs
950+
}
951+
return d.localfs
952+
}
953+
935954
func (d *DotGit) CountLooseRefs() (int, error) {
936955
var refs []*plumbing.Reference
937956
var seen = make(map[plumbing.ReferenceName]bool)

‎storage/filesystem/dotgit/dotgit_setref.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (d *DotGit) setRefRwfs(fileName, content string, old *plumbing.Reference) (
2525
mode |= os.O_TRUNC
2626
}
2727

28-
f, err := d.fs.OpenFile(fileName, mode, 0666)
28+
f, err := d.fsFromRefPath(fileName).OpenFile(fileName, mode, 0666)
2929
if err != nil {
3030
return err
3131
}
@@ -59,9 +59,11 @@ func (d *DotGit) setRefRwfs(fileName, content string, old *plumbing.Reference) (
5959
// making it compatible with these simple filesystems. This is usually not
6060
// a problem as they should be accessed by only one process at a time.
6161
func (d *DotGit) setRefNorwfs(fileName, content string, old *plumbing.Reference) error {
62-
_, err := d.fs.Stat(fileName)
62+
fs := d.fsFromRefPath(fileName)
63+
64+
_, err := fs.Stat(fileName)
6365
if err == nil && old != nil {
64-
fRead, err := d.fs.Open(fileName)
66+
fRead, err := fs.Open(fileName)
6567
if err != nil {
6668
return err
6769
}
@@ -78,7 +80,7 @@ func (d *DotGit) setRefNorwfs(fileName, content string, old *plumbing.Reference)
7880
}
7981
}
8082

81-
f, err := d.fs.Create(fileName)
83+
f, err := fs.Create(fileName)
8284
if err != nil {
8385
return err
8486
}

‎storage/filesystem/storage.go

+20-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import (
1212
// standard git format (this is, the .git directory). Zero values of this type
1313
// are not safe to use, see the NewStorage function below.
1414
type Storage struct {
15-
fs billy.Filesystem
16-
dir *dotgit.DotGit
15+
fs billy.Filesystem
16+
commonfs billy.Filesystem
17+
dir *dotgit.DotGit
1718

1819
ObjectStorage
1920
ReferenceStorage
@@ -31,6 +32,9 @@ type Options struct {
3132
// KeepDescriptors makes the file descriptors to be reused but they will
3233
// need to be manually closed calling Close().
3334
KeepDescriptors bool
35+
// CommonDir sets the directory used for accessing non-worktree files that
36+
// would normally be taken from the root directory.
37+
CommonDir billy.Filesystem
3438
}
3539

3640
// NewStorage returns a new Storage backed by a given `fs.Filesystem` and cache.
@@ -44,12 +48,18 @@ func NewStorageWithOptions(fs billy.Filesystem, cache cache.Object, ops Options)
4448
dirOps := dotgit.Options{
4549
ExclusiveAccess: ops.ExclusiveAccess,
4650
KeepDescriptors: ops.KeepDescriptors,
51+
CommonDir: ops.CommonDir,
4752
}
53+
4854
dir := dotgit.NewWithOptions(fs, dirOps)
55+
if ops.CommonDir == nil {
56+
ops.CommonDir = fs
57+
}
4958

5059
return &Storage{
51-
fs: fs,
52-
dir: dir,
60+
fs: fs,
61+
commonfs: ops.CommonDir,
62+
dir: dir,
5363

5464
ObjectStorage: *NewObjectStorageWithOptions(dir, cache, ops),
5565
ReferenceStorage: ReferenceStorage{dir: dir},
@@ -65,6 +75,12 @@ func (s *Storage) Filesystem() billy.Filesystem {
6575
return s.fs
6676
}
6777

78+
// MainFilesystem returns the underlying filesystem for the main
79+
// working-tree/common git directory
80+
func (s *Storage) MainFilesystem() billy.Filesystem {
81+
return s.commonfs
82+
}
83+
6884
// Init initializes .git directory
6985
func (s *Storage) Init() error {
7086
return s.dir.Initialize()

‎worktree_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -1937,3 +1937,78 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) {
19371937
})
19381938
c.Assert(err, IsNil)
19391939
}
1940+
1941+
func (s *WorktreeSuite) TestLinkedWorktree(c *C) {
1942+
fs := fixtures.ByTag("linked-worktree").One().Worktree()
1943+
1944+
// Open main repo.
1945+
{
1946+
fs, err := fs.Chroot("main")
1947+
c.Assert(err, IsNil)
1948+
repo, err := PlainOpen(fs.Root())
1949+
c.Assert(err, IsNil)
1950+
1951+
wt, err := repo.Worktree()
1952+
c.Assert(err, IsNil)
1953+
1954+
status, err := wt.Status()
1955+
c.Assert(err, IsNil)
1956+
c.Assert(len(status), Equals, 2) // 2 files
1957+
1958+
head, err := repo.Head()
1959+
c.Assert(err, IsNil)
1960+
c.Assert(string(head.Name()), Equals, "refs/heads/master")
1961+
}
1962+
1963+
// Open linked-worktree #1.
1964+
{
1965+
fs, err := fs.Chroot("linked-worktree-1")
1966+
c.Assert(err, IsNil)
1967+
repo, err := PlainOpen(fs.Root())
1968+
c.Assert(err, IsNil)
1969+
1970+
wt, err := repo.Worktree()
1971+
c.Assert(err, IsNil)
1972+
1973+
status, err := wt.Status()
1974+
c.Assert(err, IsNil)
1975+
c.Assert(len(status), Equals, 3) // 3 files
1976+
1977+
_, ok := status["linked-worktree-1-unique-file.txt"]
1978+
c.Assert(ok, Equals, true)
1979+
1980+
head, err := repo.Head()
1981+
c.Assert(err, IsNil)
1982+
c.Assert(string(head.Name()), Equals, "refs/heads/linked-worktree-1")
1983+
}
1984+
1985+
// Open linked-worktree #2.
1986+
{
1987+
fs, err := fs.Chroot("linked-worktree-2")
1988+
c.Assert(err, IsNil)
1989+
repo, err := PlainOpen(fs.Root())
1990+
c.Assert(err, IsNil)
1991+
1992+
wt, err := repo.Worktree()
1993+
c.Assert(err, IsNil)
1994+
1995+
status, err := wt.Status()
1996+
c.Assert(err, IsNil)
1997+
c.Assert(len(status), Equals, 3) // 3 files
1998+
1999+
_, ok := status["linked-worktree-2-unique-file.txt"]
2000+
c.Assert(ok, Equals, true)
2001+
2002+
head, err := repo.Head()
2003+
c.Assert(err, IsNil)
2004+
c.Assert(string(head.Name()), Equals, "refs/heads/branch-with-different-name")
2005+
}
2006+
2007+
// Open linked-worktree #2.
2008+
{
2009+
fs, err := fs.Chroot("linked-worktree-invalid-commondir")
2010+
c.Assert(err, IsNil)
2011+
_, err = PlainOpen(fs.Root())
2012+
c.Assert(err, Equals, ErrRepositoryIncomplete)
2013+
}
2014+
}

0 commit comments

Comments
 (0)
This repository has been archived.