Skip to content
This repository was 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
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
@@ -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")
@@ -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)
}
@@ -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.
23 changes: 21 additions & 2 deletions storage/filesystem/dotgit/dotgit.go
Original file line number Diff line number Diff line change
@@ -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
@@ -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,
}
}

@@ -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
}
@@ -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)
10 changes: 6 additions & 4 deletions storage/filesystem/dotgit/dotgit_setref.go
Original file line number Diff line number Diff line change
@@ -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
}
@@ -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
}
@@ -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
}
24 changes: 20 additions & 4 deletions storage/filesystem/storage.go
Original file line number Diff line number Diff line change
@@ -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
@@ -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.
@@ -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},
@@ -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()
75 changes: 75 additions & 0 deletions worktree_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}