diff --git a/repository.go b/repository.go
index e5b12b0c5..7cb05a2ca 100644
--- a/repository.go
+++ b/repository.go
@@ -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.
diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go
index ba9667e65..8f8b5446c 100644
--- a/storage/filesystem/dotgit/dotgit.go
+++ b/storage/filesystem/dotgit/dotgit.go
@@ -66,6 +66,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
 }
 
 // The DotGit type represents a local git repository on disk. This
@@ -73,6 +76,7 @@ type Options struct {
 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)
diff --git a/storage/filesystem/dotgit/dotgit_setref.go b/storage/filesystem/dotgit/dotgit_setref.go
index 9da2f31e8..5ea98c0e5 100644
--- a/storage/filesystem/dotgit/dotgit_setref.go
+++ b/storage/filesystem/dotgit/dotgit_setref.go
@@ -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
 	}
diff --git a/storage/filesystem/storage.go b/storage/filesystem/storage.go
index 370f7bd34..0712b412f 100644
--- a/storage/filesystem/storage.go
+++ b/storage/filesystem/storage.go
@@ -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()
diff --git a/worktree_test.go b/worktree_test.go
index afedc9195..a47eb5126 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -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)
+	}
+}