08bf443016
* Inital routes to git refs api * Git refs API implementation * Update swagger * Fix copyright * Make swagger happy add basic test * Fix test * Fix test again :)
476 lines
8.7 KiB
Go
476 lines
8.7 KiB
Go
package index
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"errors"
|
|
"hash"
|
|
"io"
|
|
"io/ioutil"
|
|
"strconv"
|
|
"time"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
"gopkg.in/src-d/go-git.v4/utils/binary"
|
|
)
|
|
|
|
var (
|
|
// DecodeVersionSupported is the range of supported index versions
|
|
DecodeVersionSupported = struct{ Min, Max uint32 }{Min: 2, Max: 4}
|
|
|
|
// ErrMalformedSignature is returned by Decode when the index header file is
|
|
// malformed
|
|
ErrMalformedSignature = errors.New("malformed index signature file")
|
|
// ErrInvalidChecksum is returned by Decode if the SHA1 hash mismatch with
|
|
// the read content
|
|
ErrInvalidChecksum = errors.New("invalid checksum")
|
|
|
|
errUnknownExtension = errors.New("unknown extension")
|
|
)
|
|
|
|
const (
|
|
entryHeaderLength = 62
|
|
entryExtended = 0x4000
|
|
entryValid = 0x8000
|
|
nameMask = 0xfff
|
|
intentToAddMask = 1 << 13
|
|
skipWorkTreeMask = 1 << 14
|
|
)
|
|
|
|
// A Decoder reads and decodes index files from an input stream.
|
|
type Decoder struct {
|
|
r io.Reader
|
|
hash hash.Hash
|
|
lastEntry *Entry
|
|
}
|
|
|
|
// NewDecoder returns a new decoder that reads from r.
|
|
func NewDecoder(r io.Reader) *Decoder {
|
|
h := sha1.New()
|
|
return &Decoder{
|
|
r: io.TeeReader(r, h),
|
|
hash: h,
|
|
}
|
|
}
|
|
|
|
// Decode reads the whole index object from its input and stores it in the
|
|
// value pointed to by idx.
|
|
func (d *Decoder) Decode(idx *Index) error {
|
|
var err error
|
|
idx.Version, err = validateHeader(d.r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
entryCount, err := binary.ReadUint32(d.r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := d.readEntries(idx, int(entryCount)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return d.readExtensions(idx)
|
|
}
|
|
|
|
func (d *Decoder) readEntries(idx *Index, count int) error {
|
|
for i := 0; i < count; i++ {
|
|
e, err := d.readEntry(idx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.lastEntry = e
|
|
idx.Entries = append(idx.Entries, e)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Decoder) readEntry(idx *Index) (*Entry, error) {
|
|
e := &Entry{}
|
|
|
|
var msec, mnsec, sec, nsec uint32
|
|
var flags uint16
|
|
|
|
flow := []interface{}{
|
|
&sec, &nsec,
|
|
&msec, &mnsec,
|
|
&e.Dev,
|
|
&e.Inode,
|
|
&e.Mode,
|
|
&e.UID,
|
|
&e.GID,
|
|
&e.Size,
|
|
&e.Hash,
|
|
&flags,
|
|
}
|
|
|
|
if err := binary.Read(d.r, flow...); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
read := entryHeaderLength
|
|
|
|
if sec != 0 || nsec != 0 {
|
|
e.CreatedAt = time.Unix(int64(sec), int64(nsec))
|
|
}
|
|
|
|
if msec != 0 || mnsec != 0 {
|
|
e.ModifiedAt = time.Unix(int64(msec), int64(mnsec))
|
|
}
|
|
|
|
e.Stage = Stage(flags>>12) & 0x3
|
|
|
|
if flags&entryExtended != 0 {
|
|
extended, err := binary.ReadUint16(d.r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
read += 2
|
|
e.IntentToAdd = extended&intentToAddMask != 0
|
|
e.SkipWorktree = extended&skipWorkTreeMask != 0
|
|
}
|
|
|
|
if err := d.readEntryName(idx, e, flags); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return e, d.padEntry(idx, e, read)
|
|
}
|
|
|
|
func (d *Decoder) readEntryName(idx *Index, e *Entry, flags uint16) error {
|
|
var name string
|
|
var err error
|
|
|
|
switch idx.Version {
|
|
case 2, 3:
|
|
len := flags & nameMask
|
|
name, err = d.doReadEntryName(len)
|
|
case 4:
|
|
name, err = d.doReadEntryNameV4()
|
|
default:
|
|
return ErrUnsupportedVersion
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e.Name = name
|
|
return nil
|
|
}
|
|
|
|
func (d *Decoder) doReadEntryNameV4() (string, error) {
|
|
l, err := binary.ReadVariableWidthInt(d.r)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var base string
|
|
if d.lastEntry != nil {
|
|
base = d.lastEntry.Name[:len(d.lastEntry.Name)-int(l)]
|
|
}
|
|
|
|
name, err := binary.ReadUntil(d.r, '\x00')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return base + string(name), nil
|
|
}
|
|
|
|
func (d *Decoder) doReadEntryName(len uint16) (string, error) {
|
|
name := make([]byte, len)
|
|
if err := binary.Read(d.r, &name); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(name), nil
|
|
}
|
|
|
|
// Index entries are padded out to the next 8 byte alignment
|
|
// for historical reasons related to how C Git read the files.
|
|
func (d *Decoder) padEntry(idx *Index, e *Entry, read int) error {
|
|
if idx.Version == 4 {
|
|
return nil
|
|
}
|
|
|
|
entrySize := read + len(e.Name)
|
|
padLen := 8 - entrySize%8
|
|
_, err := io.CopyN(ioutil.Discard, d.r, int64(padLen))
|
|
return err
|
|
}
|
|
|
|
func (d *Decoder) readExtensions(idx *Index) error {
|
|
// TODO: support 'Split index' and 'Untracked cache' extensions, take in
|
|
// count that they are not supported by jgit or libgit
|
|
|
|
var expected []byte
|
|
var err error
|
|
|
|
var header [4]byte
|
|
for {
|
|
expected = d.hash.Sum(nil)
|
|
|
|
var n int
|
|
if n, err = io.ReadFull(d.r, header[:]); err != nil {
|
|
if n == 0 {
|
|
err = io.EOF
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
err = d.readExtension(idx, header[:])
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if err != errUnknownExtension {
|
|
return err
|
|
}
|
|
|
|
return d.readChecksum(expected, header)
|
|
}
|
|
|
|
func (d *Decoder) readExtension(idx *Index, header []byte) error {
|
|
switch {
|
|
case bytes.Equal(header, treeExtSignature):
|
|
r, err := d.getExtensionReader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
idx.Cache = &Tree{}
|
|
d := &treeExtensionDecoder{r}
|
|
if err := d.Decode(idx.Cache); err != nil {
|
|
return err
|
|
}
|
|
case bytes.Equal(header, resolveUndoExtSignature):
|
|
r, err := d.getExtensionReader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
idx.ResolveUndo = &ResolveUndo{}
|
|
d := &resolveUndoDecoder{r}
|
|
if err := d.Decode(idx.ResolveUndo); err != nil {
|
|
return err
|
|
}
|
|
case bytes.Equal(header, endOfIndexEntryExtSignature):
|
|
r, err := d.getExtensionReader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
idx.EndOfIndexEntry = &EndOfIndexEntry{}
|
|
d := &endOfIndexEntryDecoder{r}
|
|
if err := d.Decode(idx.EndOfIndexEntry); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return errUnknownExtension
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Decoder) getExtensionReader() (io.Reader, error) {
|
|
len, err := binary.ReadUint32(d.r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &io.LimitedReader{R: d.r, N: int64(len)}, nil
|
|
}
|
|
|
|
func (d *Decoder) readChecksum(expected []byte, alreadyRead [4]byte) error {
|
|
var h plumbing.Hash
|
|
copy(h[:4], alreadyRead[:])
|
|
|
|
if err := binary.Read(d.r, h[4:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !bytes.Equal(h[:], expected) {
|
|
return ErrInvalidChecksum
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateHeader(r io.Reader) (version uint32, err error) {
|
|
var s = make([]byte, 4)
|
|
if _, err := io.ReadFull(r, s); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if !bytes.Equal(s, indexSignature) {
|
|
return 0, ErrMalformedSignature
|
|
}
|
|
|
|
version, err = binary.ReadUint32(r)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if version < DecodeVersionSupported.Min || version > DecodeVersionSupported.Max {
|
|
return 0, ErrUnsupportedVersion
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type treeExtensionDecoder struct {
|
|
r io.Reader
|
|
}
|
|
|
|
func (d *treeExtensionDecoder) Decode(t *Tree) error {
|
|
for {
|
|
e, err := d.readEntry()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
if e == nil {
|
|
continue
|
|
}
|
|
|
|
t.Entries = append(t.Entries, *e)
|
|
}
|
|
}
|
|
|
|
func (d *treeExtensionDecoder) readEntry() (*TreeEntry, error) {
|
|
e := &TreeEntry{}
|
|
|
|
path, err := binary.ReadUntil(d.r, '\x00')
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
e.Path = string(path)
|
|
|
|
count, err := binary.ReadUntil(d.r, ' ')
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
i, err := strconv.Atoi(string(count))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// An entry can be in an invalidated state and is represented by having a
|
|
// negative number in the entry_count field.
|
|
if i == -1 {
|
|
return nil, nil
|
|
}
|
|
|
|
e.Entries = i
|
|
trees, err := binary.ReadUntil(d.r, '\n')
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
i, err = strconv.Atoi(string(trees))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
e.Trees = i
|
|
|
|
if err := binary.Read(d.r, &e.Hash); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type resolveUndoDecoder struct {
|
|
r io.Reader
|
|
}
|
|
|
|
func (d *resolveUndoDecoder) Decode(ru *ResolveUndo) error {
|
|
for {
|
|
e, err := d.readEntry()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
ru.Entries = append(ru.Entries, *e)
|
|
}
|
|
}
|
|
|
|
func (d *resolveUndoDecoder) readEntry() (*ResolveUndoEntry, error) {
|
|
e := &ResolveUndoEntry{
|
|
Stages: make(map[Stage]plumbing.Hash),
|
|
}
|
|
|
|
path, err := binary.ReadUntil(d.r, '\x00')
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
e.Path = string(path)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
if err := d.readStage(e, Stage(i+1)); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for s := range e.Stages {
|
|
var hash plumbing.Hash
|
|
if err := binary.Read(d.r, hash[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
e.Stages[s] = hash
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
func (d *resolveUndoDecoder) readStage(e *ResolveUndoEntry, s Stage) error {
|
|
ascii, err := binary.ReadUntil(d.r, '\x00')
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stage, err := strconv.ParseInt(string(ascii), 8, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if stage != 0 {
|
|
e.Stages[s] = plumbing.ZeroHash
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type endOfIndexEntryDecoder struct {
|
|
r io.Reader
|
|
}
|
|
|
|
func (d *endOfIndexEntryDecoder) Decode(e *EndOfIndexEntry) error {
|
|
var err error
|
|
e.Offset, err = binary.ReadUint32(d.r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return binary.Read(d.r, &e.Hash)
|
|
}
|