Skip to content

Commit 76c119b

Browse files
Apply src-d#1098
This supports worktrees; none of this code is my own.
1 parent 1a7db85 commit 76c119b

File tree

5 files changed

+159
-11
lines changed

5 files changed

+159
-11
lines changed

repository.go

+38-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")
@@ -252,7 +253,13 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error)
252253
return nil, err
253254
}
254255

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

257264
return Open(s, wt)
258265
}
@@ -327,6 +334,36 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files
327334
return osfs.New(fs.Join(path, gitdir)), nil
328335
}
329336

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

@@ -925,7 +934,8 @@ func (d *DotGit) addRefFromHEAD(refs *[]*plumbing.Reference) error {
925934

926935
func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference, err error) {
927936
path = d.fs.Join(path, d.fs.Join(strings.Split(name, "/")...))
928-
f, err := d.fs.Open(path)
937+
938+
f, err := d.fsFromRefPath(path).Open(path)
929939
if err != nil {
930940
return nil, err
931941
}
@@ -934,6 +944,15 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference,
934944
return d.readReferenceFrom(f, name)
935945
}
936946

947+
func (d *DotGit) fsFromRefPath(path string) billy.Filesystem {
948+
// In general, all pseudo refs are per working tree and all refs starting
949+
// with "refs/" are shared.
950+
if strings.HasPrefix(path, "refs/") {
951+
return d.fs
952+
}
953+
return d.localfs
954+
}
955+
937956
func (d *DotGit) CountLooseRefs() (int, error) {
938957
var refs []*plumbing.Reference
939958
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

+19-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
@@ -34,6 +35,9 @@ type Options struct {
3435
// MaxOpenDescriptors is the max number of file descriptors to keep
3536
// open. If KeepDescriptors is true, all file descriptors will remain open.
3637
MaxOpenDescriptors int
38+
// CommonDir sets the directory used for accessing non-worktree files that
39+
// would normally be taken from the root directory.
40+
CommonDir billy.Filesystem
3741
}
3842

3943
// NewStorage returns a new Storage backed by a given `fs.Filesystem` and cache.
@@ -46,12 +50,17 @@ func NewStorage(fs billy.Filesystem, cache cache.Object) *Storage {
4650
func NewStorageWithOptions(fs billy.Filesystem, cache cache.Object, ops Options) *Storage {
4751
dirOps := dotgit.Options{
4852
ExclusiveAccess: ops.ExclusiveAccess,
53+
CommonDir: ops.CommonDir,
4954
}
5055
dir := dotgit.NewWithOptions(fs, dirOps)
56+
if ops.CommonDir == nil {
57+
ops.CommonDir = fs
58+
}
5159

5260
return &Storage{
53-
fs: fs,
54-
dir: dir,
61+
fs: fs,
62+
commonfs: ops.CommonDir,
63+
dir: dir,
5564

5665
ObjectStorage: *NewObjectStorageWithOptions(dir, cache, ops),
5766
ReferenceStorage: ReferenceStorage{dir: dir},
@@ -67,6 +76,12 @@ func (s *Storage) Filesystem() billy.Filesystem {
6776
return s.fs
6877
}
6978

79+
// MainFilesystem returns the undderlying filesystem for the main
80+
// working-tree/common git directory
81+
func (s *Storage) MainFilesystem() billy.Filesystem {
82+
return s.commonfs
83+
}
84+
7085
// Init initializes .git directory
7186
func (s *Storage) Init() error {
7287
return s.dir.Initialize()

worktree_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -2006,3 +2006,78 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) {
20062006
})
20072007
c.Assert(err, IsNil)
20082008
}
2009+
2010+
func (s *WorktreeSuite) TestLinkedWorktree(c *C) {
2011+
fs := fixtures.ByTag("linked-worktree").One().Worktree()
2012+
2013+
// Open main repo.
2014+
{
2015+
fs, err := fs.Chroot("main")
2016+
c.Assert(err, IsNil)
2017+
repo, err := PlainOpen(fs.Root())
2018+
c.Assert(err, IsNil)
2019+
2020+
wt, err := repo.Worktree()
2021+
c.Assert(err, IsNil)
2022+
2023+
status, err := wt.Status()
2024+
c.Assert(err, IsNil)
2025+
c.Assert(len(status), Equals, 2) // 2 files
2026+
2027+
head, err := repo.Head()
2028+
c.Assert(err, IsNil)
2029+
c.Assert(string(head.Name()), Equals, "refs/heads/master")
2030+
}
2031+
2032+
// Open linked-worktree #1.
2033+
{
2034+
fs, err := fs.Chroot("linked-worktree-1")
2035+
c.Assert(err, IsNil)
2036+
repo, err := PlainOpen(fs.Root())
2037+
c.Assert(err, IsNil)
2038+
2039+
wt, err := repo.Worktree()
2040+
c.Assert(err, IsNil)
2041+
2042+
status, err := wt.Status()
2043+
c.Assert(err, IsNil)
2044+
c.Assert(len(status), Equals, 3) // 3 files
2045+
2046+
_, ok := status["linked-worktree-1-unique-file.txt"]
2047+
c.Assert(ok, Equals, true)
2048+
2049+
head, err := repo.Head()
2050+
c.Assert(err, IsNil)
2051+
c.Assert(string(head.Name()), Equals, "refs/heads/linked-worktree-1")
2052+
}
2053+
2054+
// Open linked-worktree #2.
2055+
{
2056+
fs, err := fs.Chroot("linked-worktree-2")
2057+
c.Assert(err, IsNil)
2058+
repo, err := PlainOpen(fs.Root())
2059+
c.Assert(err, IsNil)
2060+
2061+
wt, err := repo.Worktree()
2062+
c.Assert(err, IsNil)
2063+
2064+
status, err := wt.Status()
2065+
c.Assert(err, IsNil)
2066+
c.Assert(len(status), Equals, 3) // 3 files
2067+
2068+
_, ok := status["linked-worktree-2-unique-file.txt"]
2069+
c.Assert(ok, Equals, true)
2070+
2071+
head, err := repo.Head()
2072+
c.Assert(err, IsNil)
2073+
c.Assert(string(head.Name()), Equals, "refs/heads/branch-with-different-name")
2074+
}
2075+
2076+
// Open linked-worktree #2.
2077+
{
2078+
fs, err := fs.Chroot("linked-worktree-invalid-commondir")
2079+
c.Assert(err, IsNil)
2080+
_, err = PlainOpen(fs.Root())
2081+
c.Assert(err, Equals, ErrRepositoryIncomplete)
2082+
}
2083+
}

0 commit comments

Comments
 (0)