aboutsummaryrefslogtreecommitdiffstats
path: root/storage/filesystem/internal/gitdir/refs.go
diff options
context:
space:
mode:
Diffstat (limited to 'storage/filesystem/internal/gitdir/refs.go')
-rw-r--r--storage/filesystem/internal/gitdir/refs.go152
1 files changed, 152 insertions, 0 deletions
diff --git a/storage/filesystem/internal/gitdir/refs.go b/storage/filesystem/internal/gitdir/refs.go
new file mode 100644
index 0000000..cfd42fd
--- /dev/null
+++ b/storage/filesystem/internal/gitdir/refs.go
@@ -0,0 +1,152 @@
+package gitdir
+
+import (
+ "bufio"
+ "errors"
+ "io/ioutil"
+ "os"
+ "strings"
+
+ "gopkg.in/src-d/go-git.v4/core"
+)
+
+var (
+ // ErrPackedRefsDuplicatedRef is returned when a duplicated
+ // reference is found in the packed-ref file. This is usually the
+ // case for corrupted git repositories.
+ ErrPackedRefsDuplicatedRef = errors.New("duplicated ref found in packed-ref file")
+ // ErrPackedRefsBadFormat is returned when the packed-ref file
+ // corrupt.
+ ErrPackedRefsBadFormat = errors.New("malformed packed-ref")
+ // ErrSymRefTargetNotFound is returned when a symbolic reference is
+ // targeting a non-existing object. This usually means the
+ // repository is corrupt.
+ ErrSymRefTargetNotFound = errors.New("symbolic reference target not found")
+)
+
+const (
+ symRefPrefix = "ref: "
+)
+
+func (d *GitDir) addRefsFromPackedRefs() (err error) {
+ path := d.fs.Join(d.path, packedRefsPath)
+ f, err := d.fs.Open(path)
+ if err != nil {
+ if err == os.ErrNotExist {
+ return nil
+ }
+ return err
+ }
+ defer func() {
+ errClose := f.Close()
+ if err == nil {
+ err = errClose
+ }
+ }()
+
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ line := s.Text()
+ if err = d.processLine(line); err != nil {
+ return err
+ }
+ }
+
+ return s.Err()
+}
+
+// process lines from a packed-refs file
+func (d *GitDir) processLine(line string) error {
+ switch line[0] {
+ case '#': // comment - ignore
+ return nil
+ case '^': // annotated tag commit of the previous line - ignore
+ return nil
+ default:
+ ws := strings.Split(line, " ") // hash then ref
+ if len(ws) != 2 {
+ return ErrPackedRefsBadFormat
+ }
+ h, r := ws[0], ws[1]
+
+ if _, ok := d.refs[r]; ok {
+ return ErrPackedRefsDuplicatedRef
+ }
+ d.refs[r] = core.NewHash(h)
+ }
+
+ return nil
+}
+
+func (d *GitDir) addRefsFromRefDir() error {
+ return d.walkTree("refs")
+}
+
+func (d *GitDir) walkTree(relPath string) error {
+ files, err := d.fs.ReadDir(d.fs.Join(d.path, relPath))
+ if err != nil {
+ return err
+ }
+
+ for _, f := range files {
+ newRelPath := d.fs.Join(relPath, f.Name())
+
+ if f.IsDir() {
+ if err = d.walkTree(newRelPath); err != nil {
+ return err
+ }
+ } else {
+ filePath := d.fs.Join(d.path, newRelPath)
+ h, err := d.readHashFile(filePath)
+ if err != nil {
+ return err
+ }
+ d.refs[newRelPath] = h
+ }
+ }
+
+ return nil
+}
+
+// ReadHashFile reads a single hash from a file. If a symbolic
+// reference is found instead of a hash, the reference is resolved and
+// the proper hash is returned.
+func (d *GitDir) readHashFile(path string) (h core.Hash, err error) {
+ f, err := d.fs.Open(path)
+ if err != nil {
+ return core.ZeroHash, err
+ }
+ defer func() {
+ errClose := f.Close()
+ if err == nil {
+ err = errClose
+ }
+ }()
+
+ b, err := ioutil.ReadAll(f)
+ if err != nil {
+ return core.ZeroHash, err
+ }
+ line := strings.TrimSpace(string(b))
+
+ if isSymRef(line) {
+ return d.resolveSymRef(line)
+ }
+
+ return core.NewHash(line), nil
+}
+
+func isSymRef(contents string) bool {
+ return strings.HasPrefix(contents, symRefPrefix)
+}
+
+func (d *GitDir) resolveSymRef(symRef string) (core.Hash, error) {
+ ref := strings.TrimPrefix(symRef, symRefPrefix)
+
+ hash, ok := d.refs[ref]
+ if !ok {
+ return core.ZeroHash, ErrSymRefTargetNotFound
+ }
+
+ return hash, nil
+}