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) }