aboutsummaryrefslogtreecommitdiffstats
path: root/storage/seekable/internal/gitdir/gitdir.go
blob: eea980bfbaf2cab905885561e8b396c9d4cdd8f6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package gitdir

import (
	"errors"
	"io/ioutil"
	"os"
	"strings"

	"gopkg.in/src-d/go-git.v3/clients/common"
	"gopkg.in/src-d/go-git.v3/core"
	"gopkg.in/src-d/go-git.v3/utils/fs"
)

const (
	suffix         = ".git"
	packedRefsPath = "packed-refs"
)

var (
	// ErrNotFound is returned by New when the path is not found.
	ErrNotFound = errors.New("path not found")
	// ErrIdxNotFound is returned by Idxfile when the idx file is not found on the
	// repository.
	ErrIdxNotFound = errors.New("idx file not found")
	// ErrPackfileNotFound is returned by Packfile when the packfile is not found
	// on the repository.
	ErrPackfileNotFound = errors.New("packfile not found")
	// ErrHeadfileNotFound is returned by Headfile when the HEAD file is not found
	// on the repository.
	ErrHeadfileNotFound = errors.New("headfile not found")
)

// The GitDir type represents a local git repository on disk. This
// type is not zero-value-safe, use the New function to initialize it.
type GitDir struct {
	fs      fs.FS
	path    string
	refs    map[string]core.Hash
	packDir string
}

// New returns a GitDir value ready to be used. The path argument must
// be the absolute path of a git repository directory (e.g.
// "/foo/bar/.git").
func New(fs fs.FS, path string) (*GitDir, error) {
	d := &GitDir{}
	d.fs = fs
	d.path = path
	d.packDir = d.fs.Join(d.path, "objects", "pack")

	if _, err := fs.Stat(path); err != nil {
		if os.IsNotExist(err) {
			return nil, ErrNotFound
		}
		return nil, err
	}

	return d, nil
}

// Refs scans the git directory collecting references, which it returns.
// Symbolic references are resolved and included in the output.
func (d *GitDir) Refs() (map[string]core.Hash, error) {
	var err error

	d.refs = make(map[string]core.Hash)

	if err = d.addRefsFromPackedRefs(); err != nil {
		return nil, err
	}

	if err = d.addRefsFromRefDir(); err != nil {
		return nil, err
	}

	return d.refs, err
}

// Capabilities scans the git directory collection capabilities, which it returns.
func (d *GitDir) Capabilities() (*common.Capabilities, error) {
	c := common.NewCapabilities()

	err := d.addSymRefCapability(c)

	return c, err
}

func (d *GitDir) addSymRefCapability(cap *common.Capabilities) (err error) {
	f, err := d.fs.Open(d.fs.Join(d.path, "HEAD"))
	if err != nil {
		return err
	}

	defer func() {
		errClose := f.Close()
		if err == nil {
			err = errClose
		}
	}()

	b, err := ioutil.ReadAll(f)
	if err != nil {
		return err
	}
	data := strings.TrimSpace(string(b))

	c := "symref"
	ref := strings.TrimPrefix(data, symRefPrefix)
	cap.Set(c, "HEAD:"+ref)

	return nil
}

// Packfile returns the path of the packfile (really, it returns the
// path of the first file in the "objects/pack/" directory with a
// ".pack" extension.
func (d *GitDir) Packfile() (fs.FS, string, error) {
	files, err := d.fs.ReadDir(d.packDir)
	if err != nil {
		return nil, "", err
	}

	for _, f := range files {
		if strings.HasSuffix(f.Name(), ".pack") {
			return d.fs, d.fs.Join(d.packDir, f.Name()), nil
		}
	}

	return nil, "", ErrPackfileNotFound
}

// Idxfile returns the path of the idx file (really, it returns the
// path of the first file in the "objects/pack/" directory with an
// ".idx" extension.
func (d *GitDir) Idxfile() (fs.FS, string, error) {
	files, err := d.fs.ReadDir(d.packDir)
	if err != nil {
		return nil, "", err
	}

	for _, f := range files {
		if strings.HasSuffix(f.Name(), ".idx") {
			return d.fs, d.fs.Join(d.packDir, f.Name()), nil
		}
	}

	return nil, "", ErrIdxNotFound
}