Skip to content
This repository has been archived by the owner on Sep 11, 2020. It is now read-only.

storage/worktree: filesystem, support common directory/linked repositories #1098

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var (

ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch")
ErrRepositoryNotExists = errors.New("repository does not exist")
ErrRepositoryIncomplete = errors.New("repository's commondir path does not exist")
ErrRepositoryAlreadyExists = errors.New("repository already exists")
ErrRemoteNotFound = errors.New("remote not found")
ErrRemoteExists = errors.New("remote already exists")
Expand Down Expand Up @@ -252,7 +253,13 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error)
return nil, err
}

s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
options := filesystem.Options{}
options.CommonDir, err = dotGitCommonDirectory(dot)
if err != nil {
return nil, err
}

s := filesystem.NewStorageWithOptions(dot, cache.NewObjectLRUDefault(), options)

return Open(s, wt)
}
Expand Down Expand Up @@ -327,6 +334,38 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files
return osfs.New(fs.Join(path, gitdir)), nil
}

func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err error) {
f, err := fs.Open("commondir")
if os.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, err
}

b, err := stdioutil.ReadAll(f)
if err != nil {
return nil, err
}
if len(b) > 0 {
path := strings.TrimSpace(string(b))
if filepath.IsAbs(path) {
commonDir = osfs.New(path)
} else {
commonDir = osfs.New(filepath.Join(fs.Root(), path))
}
if _, err := commonDir.Stat(""); err != nil {
if os.IsNotExist(err) {
return nil, ErrRepositoryIncomplete
}

return nil, err
}
}

return commonDir, nil
}

// PlainClone a repository into the path with the given options, isBare defines
// if the new repository will be bare or normal. If the path is not empty
// ErrRepositoryAlreadyExists is returned.
Expand Down
23 changes: 21 additions & 2 deletions storage/filesystem/dotgit/dotgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,17 @@ type Options struct {
// KeepDescriptors makes the file descriptors to be reused but they will
// need to be manually closed calling Close().
KeepDescriptors bool
// CommonDir sets the directory used for accessing non-worktree files that
// would normally be taken from the root directory.
CommonDir billy.Filesystem
}

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

// incoming object directory information
incomingChecked bool
Expand All @@ -96,9 +100,14 @@ func New(fs billy.Filesystem) *DotGit {
// NewWithOptions sets non default configuration options.
// See New for complete help.
func NewWithOptions(fs billy.Filesystem, o Options) *DotGit {
if o.CommonDir == nil {
o.CommonDir = fs
}

return &DotGit{
options: o,
fs: fs,
fs: o.CommonDir,
localfs: fs,
}
}

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

func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference, err error) {
path = d.fs.Join(path, d.fs.Join(strings.Split(name, "/")...))
f, err := d.fs.Open(path)

f, err := d.fsFromRefPath(path).Open(path)
if err != nil {
return nil, err
}
Expand All @@ -932,6 +942,15 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference,
return d.readReferenceFrom(f, name)
}

func (d *DotGit) fsFromRefPath(path string) billy.Filesystem {
// In general, all pseudo refs are per working tree and all refs starting
// with "refs/" are shared.
if strings.HasPrefix(path, "refs/") {
return d.fs
}
return d.localfs
}

func (d *DotGit) CountLooseRefs() (int, error) {
var refs []*plumbing.Reference
var seen = make(map[plumbing.ReferenceName]bool)
Expand Down
10 changes: 6 additions & 4 deletions storage/filesystem/dotgit/dotgit_setref.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (d *DotGit) setRefRwfs(fileName, content string, old *plumbing.Reference) (
mode |= os.O_TRUNC
}

f, err := d.fs.OpenFile(fileName, mode, 0666)
f, err := d.fsFromRefPath(fileName).OpenFile(fileName, mode, 0666)
if err != nil {
return err
}
Expand Down Expand Up @@ -59,9 +59,11 @@ func (d *DotGit) setRefRwfs(fileName, content string, old *plumbing.Reference) (
// making it compatible with these simple filesystems. This is usually not
// a problem as they should be accessed by only one process at a time.
func (d *DotGit) setRefNorwfs(fileName, content string, old *plumbing.Reference) error {
_, err := d.fs.Stat(fileName)
fs := d.fsFromRefPath(fileName)

_, err := fs.Stat(fileName)
if err == nil && old != nil {
fRead, err := d.fs.Open(fileName)
fRead, err := fs.Open(fileName)
if err != nil {
return err
}
Expand All @@ -78,7 +80,7 @@ func (d *DotGit) setRefNorwfs(fileName, content string, old *plumbing.Reference)
}
}

f, err := d.fs.Create(fileName)
f, err := fs.Create(fileName)
if err != nil {
return err
}
Expand Down
24 changes: 20 additions & 4 deletions storage/filesystem/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
// standard git format (this is, the .git directory). Zero values of this type
// are not safe to use, see the NewStorage function below.
type Storage struct {
fs billy.Filesystem
dir *dotgit.DotGit
fs billy.Filesystem
commonfs billy.Filesystem
dir *dotgit.DotGit

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

// NewStorage returns a new Storage backed by a given `fs.Filesystem` and cache.
Expand All @@ -44,12 +48,18 @@ func NewStorageWithOptions(fs billy.Filesystem, cache cache.Object, ops Options)
dirOps := dotgit.Options{
ExclusiveAccess: ops.ExclusiveAccess,
KeepDescriptors: ops.KeepDescriptors,
CommonDir: ops.CommonDir,
}

dir := dotgit.NewWithOptions(fs, dirOps)
if ops.CommonDir == nil {
ops.CommonDir = fs
}

return &Storage{
fs: fs,
dir: dir,
fs: fs,
commonfs: ops.CommonDir,
dir: dir,

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

// MainFilesystem returns the underlying filesystem for the main
// working-tree/common git directory
func (s *Storage) MainFilesystem() billy.Filesystem {
return s.commonfs
}

// Init initializes .git directory
func (s *Storage) Init() error {
return s.dir.Initialize()
Expand Down
75 changes: 75 additions & 0 deletions worktree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1965,3 +1965,78 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) {
})
c.Assert(err, IsNil)
}

func (s *WorktreeSuite) TestLinkedWorktree(c *C) {
fs := fixtures.ByTag("linked-worktree").One().Worktree()

// Open main repo.
{
fs, err := fs.Chroot("main")
c.Assert(err, IsNil)
repo, err := PlainOpen(fs.Root())
c.Assert(err, IsNil)

wt, err := repo.Worktree()
c.Assert(err, IsNil)

status, err := wt.Status()
c.Assert(err, IsNil)
c.Assert(len(status), Equals, 2) // 2 files

head, err := repo.Head()
c.Assert(err, IsNil)
c.Assert(string(head.Name()), Equals, "refs/heads/master")
}

// Open linked-worktree #1.
{
fs, err := fs.Chroot("linked-worktree-1")
c.Assert(err, IsNil)
repo, err := PlainOpen(fs.Root())
c.Assert(err, IsNil)

wt, err := repo.Worktree()
c.Assert(err, IsNil)

status, err := wt.Status()
c.Assert(err, IsNil)
c.Assert(len(status), Equals, 3) // 3 files

_, ok := status["linked-worktree-1-unique-file.txt"]
c.Assert(ok, Equals, true)

head, err := repo.Head()
c.Assert(err, IsNil)
c.Assert(string(head.Name()), Equals, "refs/heads/linked-worktree-1")
}

// Open linked-worktree #2.
{
fs, err := fs.Chroot("linked-worktree-2")
c.Assert(err, IsNil)
repo, err := PlainOpen(fs.Root())
c.Assert(err, IsNil)

wt, err := repo.Worktree()
c.Assert(err, IsNil)

status, err := wt.Status()
c.Assert(err, IsNil)
c.Assert(len(status), Equals, 3) // 3 files

_, ok := status["linked-worktree-2-unique-file.txt"]
c.Assert(ok, Equals, true)

head, err := repo.Head()
c.Assert(err, IsNil)
c.Assert(string(head.Name()), Equals, "refs/heads/branch-with-different-name")
}

// Open linked-worktree #2.
{
fs, err := fs.Chroot("linked-worktree-invalid-commondir")
c.Assert(err, IsNil)
_, err = PlainOpen(fs.Root())
c.Assert(err, Equals, ErrRepositoryIncomplete)
}
}