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

Commit

Permalink
storage/worktree: filesystem, support common directory/linked reposit…
Browse files Browse the repository at this point in the history
…ories.

Signed-off-by: Arran Walker <arran.walker@fiveturns.org>
  • Loading branch information
saracen committed Apr 17, 2019
1 parent 923642a commit 3932973
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 11 deletions.
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)
}
}

0 comments on commit 3932973

Please sign in to comment.