forked from go-git/go-git
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Paulo Gomes <paulo.gomes@suse.com>
- Loading branch information
Showing
24 changed files
with
2,502 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
package idxfile | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
format "github.com/go-git/go-git/v5/plumbing/format/config" | ||
"github.com/go-git/go-git/v5/plumbing/hasher" | ||
"github.com/go-git/go-git/v5/utils/binary" | ||
) | ||
|
||
var ( | ||
// ErrUnsupportedVersion is returned by Decode when the idx file version | ||
// is not supported. | ||
ErrUnsupportedVersion = errors.New("unsupported version") | ||
// ErrMalformedIdxFile is returned by Decode when the idx file is corrupted. | ||
ErrMalformedIdxFile = errors.New("malformed IDX file") | ||
// ErrMemoryIndexInUse is returned when an exclusive lock cannot be acquired | ||
// for a MemoryIndex. This is likely to happen when the same MemoryIndex is | ||
// being used concurrently for more than one Decode or Encode operation. | ||
ErrMemoryIndexLocked = errors.New("memory index is locked") | ||
) | ||
|
||
const ( | ||
fanout = 256 | ||
magicNumberLength = 4 | ||
) | ||
|
||
// Decoder reads and decodes idx files from an input stream. | ||
type Decoder struct { | ||
*bufio.Reader | ||
objectFormat format.ObjectFormat | ||
version Version | ||
objectNameLength int | ||
} | ||
|
||
// TODO: rename and reuse for encoding? Any specific options that would | ||
// only be for one of those ops? | ||
type DecoderOptions struct { | ||
format.ObjectFormat | ||
} | ||
|
||
// NewDecoder builds a new idx stream decoder, that reads from r. | ||
func NewDecoder(r io.Reader) (*Decoder, error) { | ||
return NewDecoderWithOptions(r, DecoderOptions{ObjectFormat: format.SHA1}) | ||
} | ||
|
||
func NewDecoderWithOptions(r io.Reader, opts DecoderOptions) (*Decoder, error) { | ||
if r == nil { | ||
return nil, fmt.Errorf("cannot create decoder: nil reader") | ||
} | ||
|
||
h, err := hasher.FromObjectFormat(opts.ObjectFormat) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &Decoder{ | ||
bufio.NewReader(r), | ||
opts.ObjectFormat, | ||
VersionNotSet, | ||
h.Size(), | ||
}, nil | ||
} | ||
|
||
var decodeFlow = []func(*MemoryIndex, io.Reader) error{ | ||
readVersion, | ||
readFanout, | ||
readObjectNames, | ||
readCRC32, | ||
readOffsets, | ||
readChecksums, | ||
} | ||
|
||
// Decode reads from the stream and decode the content into the MemoryIndex struct. | ||
// It is safe to use a previously used MemoryIndex, as it will be reset before the | ||
// decoding process. | ||
func (d *Decoder) Decode(idx *MemoryIndex) error { | ||
if idx == nil { | ||
return fmt.Errorf("failed to decode: target index is nil") | ||
} | ||
|
||
if !idx.m.TryLock() { | ||
return fmt.Errorf("failed to decode: %w", ErrMemoryIndexLocked) | ||
} | ||
defer idx.m.Unlock() | ||
idx.reset(d.objectNameLength, d.objectFormat) | ||
|
||
if err := validateHeader(idx, d); err != nil { | ||
return err | ||
} | ||
|
||
for _, f := range decodeFlow { | ||
if err := f(idx, d); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func validateHeader(idx *MemoryIndex, r io.Reader) error { | ||
if _, err := io.ReadFull(r, idx.header[:]); err != nil { | ||
return err | ||
} | ||
|
||
if !bytes.Equal(idx.header[:], idxHeader) { | ||
return ErrMalformedIdxFile | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func readVersion(idx *MemoryIndex, r io.Reader) error { | ||
v, err := binary.ReadUint32(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
switch v { | ||
case uint32(Version2): | ||
idx.Version = 2 | ||
case uint32(Version3): | ||
idx.Version = 3 | ||
default: | ||
return ErrUnsupportedVersion | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func readFanout(idx *MemoryIndex, r io.Reader) error { | ||
for k := 0; k < fanout; k++ { | ||
n, err := binary.ReadUint32(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
idx.Fanout[k] = n | ||
idx.FanoutMapping[k] = noMapping | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func readObjectNames(idx *MemoryIndex, r io.Reader) error { | ||
for k := 0; k < fanout; k++ { | ||
buckets := idx.Fanout[k] | ||
if k != 0 { | ||
buckets -= idx.Fanout[k-1] | ||
} | ||
|
||
if buckets == 0 { | ||
continue | ||
} | ||
|
||
idx.FanoutMapping[k] = len(idx.Names) | ||
|
||
nameLen := int(buckets) * idx.nameLength | ||
bin := make([]byte, nameLen) | ||
if _, err := io.ReadFull(r, bin); err != nil { | ||
return err | ||
} | ||
|
||
idx.Names = append(idx.Names, bin) | ||
idx.Offset32 = append(idx.Offset32, make([]byte, buckets*4)) | ||
idx.CRC32 = append(idx.CRC32, make([]byte, buckets*4)) | ||
} | ||
return nil | ||
} | ||
|
||
func readCRC32(idx *MemoryIndex, r io.Reader) error { | ||
for k := 0; k < fanout; k++ { | ||
if pos := idx.FanoutMapping[k]; pos != noMapping { | ||
if _, err := io.ReadFull(r, idx.CRC32[pos]); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func readOffsets(idx *MemoryIndex, r io.Reader) error { | ||
var o64cnt int | ||
for k := 0; k < fanout; k++ { | ||
if pos := idx.FanoutMapping[k]; pos != noMapping { | ||
if _, err := io.ReadFull(r, idx.Offset32[pos]); err != nil { | ||
return err | ||
} | ||
|
||
for p := 0; p < len(idx.Offset32[pos]); p += 4 { | ||
if idx.Offset32[pos][p]&(byte(1)<<7) > 0 { | ||
o64cnt++ | ||
} | ||
} | ||
} | ||
} | ||
|
||
if o64cnt > 0 { | ||
idx.Offset64 = make([]byte, o64cnt*8) | ||
if _, err := io.ReadFull(r, idx.Offset64); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func readChecksums(idx *MemoryIndex, r io.Reader) error { | ||
if len(idx.PackfileChecksum) == 0 { | ||
idx.PackfileChecksum = make([]byte, idx.nameLength) | ||
} | ||
|
||
if _, err := io.ReadFull(r, idx.PackfileChecksum[:]); err != nil { | ||
return err | ||
} | ||
|
||
if len(idx.IdxChecksum) == 0 { | ||
idx.IdxChecksum = make([]byte, idx.nameLength) | ||
} | ||
|
||
if _, err := io.ReadFull(r, idx.IdxChecksum[:]); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.