package index
import (
"bytes"
"encoding/binary"
"errors"
"io"
"io/ioutil"
"strconv"
"time"
"gopkg.in/src-d/go-git.v4/core"
)
var (
// IndexVersionSupported is the range of supported index versions
IndexVersionSupported = struct{ Min, Max uint32 }{Min: 2, Max: 4}
// ErrUnsupportedVersion is returned by Decode when the idxindex file
// version is not supported.
ErrUnsupportedVersion = errors.New("Unsuported version")
// ErrMalformedSignature is returned by Decode when the index header file is
// malformed
ErrMalformedSignature = errors.New("Malformed index signature file")
indexSignature = []byte{'D', 'I', 'R', 'C'}
treeExtSignature = []byte{'T', 'R', 'E', 'E'}
resolveUndoExtSignature = []byte{'R', 'E', 'U', 'C'}
)
const (
EntryExtended = 0x4000
EntryValid = 0x8000
nameMask = 0xfff
intentToAddMask = 1 << 13
skipWorkTreeMask = 1 << 14
)
type Decoder struct {
r io.Reader
lastEntry *Entry
}
// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// 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 {
version, err := validateHeader(d.r)
if err != nil {
return err
}
idx.Version = version
if err := binary.Read(d.r, binary.BigEndian, &idx.EntryCount); err != nil {
return err
}
if err := d.readEntries(idx); err != nil {
return err
}
return d.readExtensions(idx)
}
func (d *Decoder) readEntries(idx *Index) error {
for i := 0; i < int(idx.EntryCount); 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
flowSize := 62
flow := []interface{}{
&msec, &mnsec,
&sec, &nsec,
&e.Dev,
&e.Inode,
&e.Mode,
&e.UID,
&e.GID,
&e.Size,
&e.Hash,
&e.Flags,
}
if err := readBinary(d.r, flow...); err != nil {
return nil, err
}
read := flowSize
e.CreatedAt = time.Unix(int64(msec), int64(mnsec))
e.ModifiedAt = time.Unix(int64(sec), int64(nsec))
e.Stage = Stage(e.Flags>>12) & 0x3
if e.Flags&EntryExtended != 0 {
var extended uint16
if err := readBinary(d.r, &extended); err != nil {
return nil, err
}
read += 2
e.IntentToAdd = extended&intentToAddMask != 0
e.SkipWorktree = extended&skipWorkTreeMask != 0
}
if err := d.readEntryName(idx, e); err != nil {
return nil, err
}
return e, d.padEntry(idx, e, read)
}
func (d *Decoder) readEntryName(idx *Index, e *Entry) error {
var name string
var err error
switch idx.Version {
case 2, 3:
name, err = d.doReadEntryName(e)
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 := 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 := readUntil(d.r, '\x00')
if err != nil {
return "", err
}
return base + string(name), nil
}
func (d *Decoder) doReadEntryName(e *Entry) (string, error) {
pLen := e.Flags & nameMask
name := make([]byte, int64(pLen))
if err := binary.Read(d.r, binary.BigEndian, &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
if _, err := io.CopyN(ioutil.Discard, d.r, int64(padLen)); err != nil {
return err
}
return nil
}
func (d *Decoder) readExtensions(idx *Index) error {
var err error
for {
err = d.readExtension(idx)
if err != nil {
break
}
}
if err == io.EOF {
return nil
}
return err
}
func (d *Decoder) readExtension(idx *Index) error {
var s = make([]byte, 4)
if _, err := io.ReadFull(d.r, s); err != nil {
return err
}
var len uint32
if err := binary.Read(d.r, binary.BigEndian, &len); err != nil {
return err
}
switch {
case bytes.Equal(s, treeExtSignature):
t := &Tree{}
td := &treeExtensionDecoder{&io.LimitedReader{R: d.r, N: int64(len)}}
if err := td.Decode(t); err != nil {
return err
}
idx.Cache = t
case bytes.Equal(s, resolveUndoExtSignature):
ru := &ResolveUndo{}
rud := &resolveUndoDecoder{&io.LimitedReader{R: d.r, N: int64(len)}}
if err := rud.Decode(ru); err != nil {
return err
}
idx.ResolveUndo = ru
}
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
}
if err := binary.Read(r, binary.BigEndian, &version); err != nil {
return 0, err
}
if version < IndexVersionSupported.Min || version > IndexVersionSupported.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 := readUntil(d.r, '\x00')
if err != nil {
return nil, err
}
e.Path = string(path)
count, err := 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 := 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, binary.BigEndian, &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]core.Hash, 0),
}
path, err := 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 core.Hash
if err := binary.Read(d.r, binary.BigEndian, hash[:]); err != nil {
return nil, err
}
e.Stages[s] = hash
}
return e, nil
}
func (d *resolveUndoDecoder) readStage(e *ResolveUndoEntry, s Stage) error {
ascii, err := 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] = core.ZeroHash
}
return nil
}
func readBinary(r io.Reader, data ...interface{}) error {
for _, v := range data {
err := binary.Read(r, binary.BigEndian, v)
if err != nil {
return err
}
}
return nil
}
func readUntil(r io.Reader, delim byte) ([]byte, error) {
var buf [1]byte
value := make([]byte, 0, 16)
for {
if _, err := r.Read(buf[:]); err != nil {
if err == io.EOF {
return nil, err
}
return nil, err
}
if buf[0] == delim {
return value, nil
}
value = append(value, buf[0])
}
}
// dheader[pos] = ofs & 127;
// while (ofs >>= 7)
// dheader[--pos] = 128 | (--ofs & 127);
//
func readVariableWidthInt(r io.Reader) (int64, error) {
var c byte
if err := readBinary(r, &c); err != nil {
return 0, err
}
var v = int64(c & maskLength)
for moreBytesInLength(c) {
v++
if err := readBinary(r, &c); err != nil {
return 0, err
}
v = (v << lengthBits) + int64(c&maskLength)
}
return v, nil
}
const (
maskContinue = uint8(128) // 1000 000
maskLength = uint8(127) // 0111 1111
lengthBits = uint8(7) // subsequent bytes has 7 bits to store the length
)
func moreBytesInLength(c byte) bool {
return c&maskContinue > 0
}