func (d *DotGit) Alternates() ([]*DotGit, error) {
	altpath := d.fs.Join("objects", "info", "alternates")
	f, err := d.fs.Open(altpath)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	var alternates []*DotGit
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		path := scanner.Text()
		if !filepath.IsAbs(path) {
			slashPath := filepath.ToSlash(path)
			relpath := filepath.Join(strings.Split(slashPath, "/")[1:]...)
			normalPath := filepath.FromSlash(relpath)
			path = filepath.Join(d.fs.Root(), normalPath)
		}
		fs := osfs.New(filepath.Dir(path))
		alternates = append(alternates, New(fs))
	}
	if err = scanner.Err(); err != nil {
		return nil, err
	}
	return alternates, nil
}
Alternates returns DotGit(s) based off paths in objects/info/alternates if
available. This can be used to checks if it's a shared repository.
package dotgit
import (
	"bufio"
	"errors"
	"fmt"
	"io"
	stdioutil "io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"
	"gopkg.in/src-d/go-billy.v4/osfs"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/utils/ioutil"
	"gopkg.in/src-d/go-billy.v4"
)
const (
	suffix         = ".git"
	packedRefsPath = "packed-refs"
	configPath     = "config"
	indexPath      = "index"
	shallowPath    = "shallow"
	modulePath     = "modules"
	objectsPath    = "objects"
	packPath       = "pack"
	refsPath       = "refs"
	tmpPackedRefsPrefix = "._packed-refs"
	packExt = ".pack"
	idxExt  = ".idx"
)
var (
	ErrNotFound = errors.New("path not found")
	ErrIdxNotFound = errors.New("idx file not found")
	ErrPackfileNotFound = errors.New("packfile not found")
	ErrConfigNotFound = errors.New("config file not found")
	ErrPackedRefsDuplicatedRef = errors.New("duplicated ref found in packed-ref file")
	ErrPackedRefsBadFormat = errors.New("malformed packed-ref")
	ErrSymRefTargetNotFound = errors.New("symbolic reference target not found")
)
type DotGit struct {
	fs billy.Filesystem
}
func New(fs billy.Filesystem) *DotGit {
	return &DotGit{fs: fs}
}
func (d *DotGit) Initialize() error {
	mustExists := []string{
		d.fs.Join("objects", "info"),
		d.fs.Join("objects", "pack"),
		d.fs.Join("refs", "heads"),
		d.fs.Join("refs", "tags"),
	}
	for _, path := range mustExists {
		_, err := d.fs.Stat(path)
		if err == nil {
			continue
		}
		if !os.IsNotExist(err) {
			return err
		}
		if err := d.fs.MkdirAll(path, os.ModeDir|os.ModePerm); err != nil {
			return err
		}
	}
	return nil
}
func (d *DotGit) ConfigWriter() (billy.File, error) {
	return d.fs.Create(configPath)
}
func (d *DotGit) Config() (billy.File, error) {
	return d.fs.Open(configPath)
}
func (d *DotGit) IndexWriter() (billy.File, error) {
	return d.fs.Create(indexPath)
}
func (d *DotGit) Index() (billy.File, error) {
	return d.fs.Open(indexPath)
}
func (d *DotGit) ShallowWriter() (billy.File, error) {
	return d.fs.Create(shallowPath)
}
func (d *DotGit) Shallow() (billy.File, error) {
	f, err := d.fs.Open(shallowPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	return f, nil
}
func (d *DotGit) NewObjectPack() (*PackWriter, error) {
	return newPackWrite(d.fs)
}
func (d *DotGit) ObjectPacks() ([]plumbing.Hash, error) {
	packDir := d.fs.Join(objectsPath, packPath)
	files, err := d.fs.ReadDir(packDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	var packs []plumbing.Hash
	for _, f := range files {
		if !strings.HasSuffix(f.Name(), packExt) {
			continue
		}
		n := f.Name()
		h := plumbing.NewHash(n[5 : len(n)-5])  
		packs = append(packs, h)
	}
	return packs, nil
}
func (d *DotGit) objectPackPath(hash plumbing.Hash, extension string) string {
	return d.fs.Join(objectsPath, packPath, fmt.Sprintf("pack-%s.%s", hash.String(), extension))
}
func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.File, error) {
	pack, err := d.fs.Open(d.objectPackPath(hash, extension))
	if err != nil {
		if os.IsNotExist(err) {
			return nil, ErrPackfileNotFound
		}
		return nil, err
	}
	return pack, nil
}
func (d *DotGit) ObjectPack(hash plumbing.Hash) (billy.File, error) {
	return d.objectPackOpen(hash, `pack`)
}
func (d *DotGit) ObjectPackIdx(hash plumbing.Hash) (billy.File, error) {
	return d.objectPackOpen(hash, `idx`)
}
func (d *DotGit) DeleteOldObjectPackAndIndex(hash plumbing.Hash, t time.Time) error {
	path := d.objectPackPath(hash, `pack`)
	if !t.IsZero() {
		fi, err := d.fs.Stat(path)
		if err != nil {
			return err
		}
		if !fi.ModTime().Before(t) {
			return nil
		}
	}
	err := d.fs.Remove(path)
	if err != nil {
		return err
	}
	err = d.fs.Remove(d.objectPackPath(hash, `idx`))
	if err != nil {
		return err
	}
	return nil
}
func (d *DotGit) NewObject() (*ObjectWriter, error) {
	return newObjectWriter(d.fs)
}
func (d *DotGit) Objects() ([]plumbing.Hash, error) {
	var objects []plumbing.Hash
	err := d.ForEachObjectHash(func(hash plumbing.Hash) error {
		objects = append(objects, hash)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return objects, nil
}
func (d *DotGit) ForEachObjectHash(fun func(plumbing.Hash) error) error {
	files, err := d.fs.ReadDir(objectsPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	for _, f := range files {
		if f.IsDir() && len(f.Name()) == 2 && isHex(f.Name()) {
			base := f.Name()
			d, err := d.fs.ReadDir(d.fs.Join(objectsPath, base))
			if err != nil {
				return err
			}
			for _, o := range d {
				err = fun(plumbing.NewHash(base + o.Name()))
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}
func (d *DotGit) objectPath(h plumbing.Hash) string {
	hash := h.String()
	return d.fs.Join(objectsPath, hash[0:2], hash[2:40])
}
func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
	return d.fs.Open(d.objectPath(h))
}
func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
	return d.fs.Stat(d.objectPath(h))
}
func (d *DotGit) ObjectDelete(h plumbing.Hash) error {
	return d.fs.Remove(d.objectPath(h))
}
func (d *DotGit) readReferenceFrom(rd io.Reader, name string) (ref *plumbing.Reference, err error) {
	b, err := stdioutil.ReadAll(rd)
	if err != nil {
		return nil, err
	}
	line := strings.TrimSpace(string(b))
	return plumbing.NewReferenceFromStrings(name, line), nil
}
func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference) error {
	if old == nil {
		return nil
	}
	ref, err := d.readReferenceFrom(f, old.Name().String())
	if err != nil {
		return err
	}
	if ref.Hash() != old.Hash() {
		return fmt.Errorf("reference has changed concurrently")
	}
	_, err = f.Seek(0, io.SeekStart)
	if err != nil {
		return err
	}
	return f.Truncate(0)
}
func (d *DotGit) SetRef(r, old *plumbing.Reference) error {
	var content string
	switch r.Type() {
	case plumbing.SymbolicReference:
		content = fmt.Sprintf("ref: %s\n", r.Target())
	case plumbing.HashReference:
		content = fmt.Sprintln(r.Hash().String())
	}
	mode := os.O_RDWR | os.O_CREATE
	if old == nil {
		mode |= os.O_TRUNC
	}
	f, err := d.fs.OpenFile(r.Name().String(), mode, 0666)
	if err != nil {
		return err
	}
	defer ioutil.CheckClose(f, &err)
	err = f.Lock()
	if err != nil {
		return err
	}
	err = d.checkReferenceAndTruncate(f, old)
	if err != nil {
		return err
	}
	_, err = f.Write([]byte(content))
	return err
}
func (d *DotGit) Refs() ([]*plumbing.Reference, error) {
	var refs []*plumbing.Reference
	var seen = make(map[plumbing.ReferenceName]bool)
	if err := d.addRefsFromRefDir(&refs, seen); err != nil {
		return nil, err
	}
	if err := d.addRefsFromPackedRefs(&refs, seen); err != nil {
		return nil, err
	}
	if err := d.addRefFromHEAD(&refs); err != nil {
		return nil, err
	}
	return refs, nil
}
func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) {
	ref, err := d.readReferenceFile(".", name.String())
	if err == nil {
		return ref, nil
	}
	return d.packedRef(name)
}
func (d *DotGit) findPackedRefsInFile(f billy.File) ([]*plumbing.Reference, error) {
	s := bufio.NewScanner(f)
	var refs []*plumbing.Reference
	for s.Scan() {
		ref, err := d.processLine(s.Text())
		if err != nil {
			return nil, err
		}
		if ref != nil {
			refs = append(refs, ref)
		}
	}
	return refs, s.Err()
}
func (d *DotGit) findPackedRefs() ([]*plumbing.Reference, error) {
	f, err := d.fs.Open(packedRefsPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer ioutil.CheckClose(f, &err)
	return d.findPackedRefsInFile(f)
}
func (d *DotGit) packedRef(name plumbing.ReferenceName) (*plumbing.Reference, error) {
	refs, err := d.findPackedRefs()
	if err != nil {
		return nil, err
	}
	for _, ref := range refs {
		if ref.Name() == name {
			return ref, nil
		}
	}
	return nil, plumbing.ErrReferenceNotFound
}
func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error {
	path := d.fs.Join(".", name.String())
	_, err := d.fs.Stat(path)
	if err == nil {
		err = d.fs.Remove(path)
	}
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	return d.rewritePackedRefsWithoutRef(name)
}
func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) (err error) {
	packedRefs, err := d.findPackedRefs()
	if err != nil {
		return err
	}
	for _, ref := range packedRefs {
		if !seen[ref.Name()] {
			*refs = append(*refs, ref)
			seen[ref.Name()] = true
		}
	}
	return nil
}
func (d *DotGit) addRefsFromPackedRefsFile(refs *[]*plumbing.Reference, f billy.File, seen map[plumbing.ReferenceName]bool) (err error) {
	packedRefs, err := d.findPackedRefsInFile(f)
	if err != nil {
		return err
	}
	for _, ref := range packedRefs {
		if !seen[ref.Name()] {
			*refs = append(*refs, ref)
			seen[ref.Name()] = true
		}
	}
	return nil
}
func (d *DotGit) openAndLockPackedRefs(doCreate bool) (
	pr billy.File, err error) {
	var f billy.File
	defer func() {
		if err != nil && f != nil {
			ioutil.CheckClose(f, &err)
		}
	}()
	openFlags := os.O_RDWR
	if doCreate {
		openFlags |= os.O_CREATE
	}
	for {
		f, err = d.fs.OpenFile(packedRefsPath, openFlags, 0600)
		if err != nil {
			if os.IsNotExist(err) && !doCreate {
				return nil, nil
			}
			return nil, err
		}
		fi, err := d.fs.Stat(packedRefsPath)
		if err != nil {
			return nil, err
		}
		mtime := fi.ModTime()
		err = f.Lock()
		if err != nil {
			return nil, err
		}
		fi, err = d.fs.Stat(packedRefsPath)
		if err != nil {
			return nil, err
		}
		if mtime.Equal(fi.ModTime()) {
			break
		}
		err = f.Close()
		if err != nil {
			return nil, err
		}
	}
	return f, nil
}
func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err error) {
	pr, err := d.openAndLockPackedRefs(false)
	if err != nil {
		return err
	}
	if pr == nil {
		return nil
	}
	defer ioutil.CheckClose(pr, &err)
	tmp, err := d.fs.TempFile("", tmpPackedRefsPrefix)
	if err != nil {
		return err
	}
	tmpName := tmp.Name()
	defer func() {
		ioutil.CheckClose(tmp, &err)
		_ = d.fs.Remove(tmpName)  
	}()
	s := bufio.NewScanner(pr)
	found := false
	for s.Scan() {
		line := s.Text()
		ref, err := d.processLine(line)
		if err != nil {
			return err
		}
		if ref != nil && ref.Name() == name {
			found = true
			continue
		}
		if _, err := fmt.Fprintln(tmp, line); err != nil {
			return err
		}
	}
	if err := s.Err(); err != nil {
		return err
	}
	if !found {
		return nil
	}
	return d.rewritePackedRefsWhileLocked(tmp, pr)
}
func (d *DotGit) processLine(line string) (*plumbing.Reference, error) {
	if len(line) == 0 {
		return nil, nil
	}
	switch line[0] {
	case '#':  
		return nil, nil
	case '^':  
		return nil, nil
	default:
		ws := strings.Split(line, " ")  
		if len(ws) != 2 {
			return nil, ErrPackedRefsBadFormat
		}
		return plumbing.NewReferenceFromStrings(ws[1], ws[0]), nil
	}
}
func (d *DotGit) addRefsFromRefDir(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) error {
	return d.walkReferencesTree(refs, []string{refsPath}, seen)
}
func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []string, seen map[plumbing.ReferenceName]bool) error {
	files, err := d.fs.ReadDir(d.fs.Join(relPath...))
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	for _, f := range files {
		newRelPath := append(append([]string(nil), relPath...), f.Name())
		if f.IsDir() {
			if err = d.walkReferencesTree(refs, newRelPath, seen); err != nil {
				return err
			}
			continue
		}
		ref, err := d.readReferenceFile(".", strings.Join(newRelPath, "/"))
		if err != nil {
			return err
		}
		if ref != nil && !seen[ref.Name()] {
			*refs = append(*refs, ref)
			seen[ref.Name()] = true
		}
	}
	return nil
}
func (d *DotGit) addRefFromHEAD(refs *[]*plumbing.Reference) error {
	ref, err := d.readReferenceFile(".", "HEAD")
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	*refs = append(*refs, ref)
	return nil
}
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)
	if err != nil {
		return nil, err
	}
	defer ioutil.CheckClose(f, &err)
	return d.readReferenceFrom(f, name)
}
func (d *DotGit) CountLooseRefs() (int, error) {
	var refs []*plumbing.Reference
	var seen = make(map[plumbing.ReferenceName]bool)
	if err := d.addRefsFromRefDir(&refs, seen); err != nil {
		return 0, err
	}
	return len(refs), nil
}
func (d *DotGit) PackRefs() (err error) {
	f, err := d.openAndLockPackedRefs(true)
	if err != nil {
		return err
	}
	defer ioutil.CheckClose(f, &err)
	var refs []*plumbing.Reference
	seen := make(map[plumbing.ReferenceName]bool)
	if err := d.addRefsFromRefDir(&refs, seen); err != nil {
		return err
	}
	if len(refs) == 0 {
		return nil
	}
	numLooseRefs := len(refs)
	if err := d.addRefsFromPackedRefsFile(&refs, f, seen); err != nil {
		return err
	}
	tmp, err := d.fs.TempFile("", tmpPackedRefsPrefix)
	if err != nil {
		return err
	}
	tmpName := tmp.Name()
	defer func() {
		ioutil.CheckClose(tmp, &err)
		_ = d.fs.Remove(tmpName)  
	}()
	w := bufio.NewWriter(tmp)
	for _, ref := range refs {
		_, err := w.WriteString(ref.String() + "\n")
		if err != nil {
			return err
		}
	}
	err = w.Flush()
	if err != nil {
		return err
	}
	err = d.rewritePackedRefsWhileLocked(tmp, f)
	if err != nil {
		return err
	}
	for _, ref := range refs[:numLooseRefs] {
		path := d.fs.Join(".", ref.Name().String())
		err = d.fs.Remove(path)
		if err != nil && !os.IsNotExist(err) {
			return err
		}
	}
	return nil
}
func (d *DotGit) Module(name string) (billy.Filesystem, error) {
	return d.fs.Chroot(d.fs.Join(modulePath, name))
}