diff options
Diffstat (limited to 'storage/filesystem/internal/gitdir/refs.go')
-rw-r--r-- | storage/filesystem/internal/gitdir/refs.go | 152 |
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 +} |