From 3e8d4879fa91ab26fa6306f989a82de42fad98a6 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Fri, 14 Jul 2017 17:46:21 -0600 Subject: [PATCH 1/3] git/odb/pack: assign V1Width --- git/odb/pack/index_decode.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/git/odb/pack/index_decode.go b/git/odb/pack/index_decode.go index 19722bb0..41b72e60 100644 --- a/git/odb/pack/index_decode.go +++ b/git/odb/pack/index_decode.go @@ -7,6 +7,11 @@ import ( "io" ) +const ( + // V1Width is the total width of the header in V1. + V1Width = 0 +) + var ( // ErrShortFanout is an error representing situations where the entire // fanout table could not be read, and is thus too short. From 8bda7245ea55a534098f25028d3da5c5bc4888d1 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Fri, 14 Jul 2017 17:46:58 -0600 Subject: [PATCH 2/3] git/odb/pack: introduce v1 index format support --- git/odb/pack/index_decode.go | 26 +++++++- git/odb/pack/index_decode_test.go | 15 +++++ git/odb/pack/index_v1.go | 52 ++++++++++++++++ git/odb/pack/index_v1_test.go | 98 ++++++++++++++++++++++++++++++ git/odb/pack/index_version.go | 29 +++++++++ git/odb/pack/index_version_test.go | 4 ++ 6 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 git/odb/pack/index_v1.go create mode 100644 git/odb/pack/index_v1_test.go diff --git a/git/odb/pack/index_decode.go b/git/odb/pack/index_decode.go index 41b72e60..d89dc6ca 100644 --- a/git/odb/pack/index_decode.go +++ b/git/odb/pack/index_decode.go @@ -10,6 +10,26 @@ import ( const ( // V1Width is the total width of the header in V1. V1Width = 0 + + // FanoutEntries is the number of entries in the fanout table. + FanoutEntries = 256 + // FanoutEntryWidth is the width of each entry in the fanout table. + FanoutEntryWidth = 4 + // FanoutWidth is the width of the entire fanout table. + FanoutWidth = FanoutEntries * FanoutEntryWidth + + // OffsetV1Start is the location of the first object outside of the V1 + // header. + OffsetV1Start = V1Width + FanoutWidth + + // ObjectNameWidth is the width of a SHA1 object name. + ObjectNameWidth = 20 + // ObjectSmallOffsetWidth is the width of the small offset encoded into + // each object. + ObjectSmallOffsetWidth = 4 + + // ObjectEntryV1Width is the width of one contiguous object entry in V1. + ObjectEntryV1Width = ObjectNameWidth + ObjectSmallOffsetWidth ) var ( @@ -61,10 +81,14 @@ func decodeIndexHeader(r io.ReaderAt) (IndexVersion, error) { } version := IndexVersion(binary.BigEndian.Uint32(vb)) + switch version { + case V1: + return version, nil + } return version, &UnsupportedVersionErr{uint32(version)} } - return IndexVersion(0), nil + return V1, nil } // decodeIndexFanout decodes the fanout table given by "r" and beginning at the diff --git a/git/odb/pack/index_decode_test.go b/git/odb/pack/index_decode_test.go index 8f441a0f..aa2fec51 100644 --- a/git/odb/pack/index_decode_test.go +++ b/git/odb/pack/index_decode_test.go @@ -8,6 +8,21 @@ import ( "github.com/stretchr/testify/assert" ) +func TestDecodeIndexV1(t *testing.T) { + idx, err := DecodeIndex(bytes.NewReader(make([]byte, FanoutWidth))) + + assert.NoError(t, err) + assert.Equal(t, V1, idx.version) + assert.EqualValues(t, 0, idx.Count()) +} + +func TestDecodeIndexV1InvalidFanout(t *testing.T) { + idx, err := DecodeIndex(bytes.NewReader(make([]byte, FanoutWidth-1))) + + assert.Equal(t, ErrShortFanout, err) + assert.Nil(t, idx) +} + func TestDecodeIndexUnsupportedVersion(t *testing.T) { buf := make([]byte, 0, 4+4) buf = append(buf, 0xff, 0x74, 0x4f, 0x63) diff --git a/git/odb/pack/index_v1.go b/git/odb/pack/index_v1.go new file mode 100644 index 00000000..fdb3e6ec --- /dev/null +++ b/git/odb/pack/index_v1.go @@ -0,0 +1,52 @@ +package pack + +import ( + "bytes" + "encoding/binary" +) + +const ( + // V1 is an instance of IndexVersion corresponding to the V1 index file + // format. + V1 IndexVersion = 1 +) + +// v1Search implements the IndexVersion.Search method for V1 packfiles. +func v1Search(idx *Index, name []byte, at int64) (*IndexEntry, int, error) { + var sha [20]byte + if _, err := idx.readAt(sha[:], v1ShaOffset(at)); err != nil { + return nil, 0, err + } + + cmp := bytes.Compare(name, sha[:]) + if cmp != 0 { + return nil, cmp, nil + } + + var offs [4]byte + if _, err := idx.readAt(offs[:], v1EntryOffset(at)); err != nil { + return nil, 0, err + } + + return &IndexEntry{ + PackOffset: uint64(binary.BigEndian.Uint32(offs[:])), + }, 0, nil +} + +// v1ShaOffset returns the location of the SHA1 of an object given at "at". +func v1ShaOffset(at int64) int64 { + // Skip forward until the desired entry. + return v1EntryOffset(at) + + // Skip past the 4-byte object offset in the desired entry to + // the SHA1. + ObjectSmallOffsetWidth +} + +// v1EntryOffset returns the location of the packfile offset for the object +// given at "at". +func v1EntryOffset(at int64) int64 { + // Skip the L1 fanout table + return OffsetV1Start + + // Skip the object entries before the one located at "at" + (ObjectEntryV1Width * at) +} diff --git a/git/odb/pack/index_v1_test.go b/git/odb/pack/index_v1_test.go new file mode 100644 index 00000000..a741e5e3 --- /dev/null +++ b/git/odb/pack/index_v1_test.go @@ -0,0 +1,98 @@ +package pack + +import ( + "bytes" + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + V1IndexFanout = make([]uint32, FanoutEntries) + + V1IndexSmallEntry = []byte{ + 0x0, 0x0, 0x0, 0x1, + + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + } + V1IndexSmallSha = V1IndexSmallEntry[4:] + + V1IndexMediumEntry = []byte{ + 0x0, 0x0, 0x0, 0x2, + + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + } + V1IndexMediumSha = V1IndexMediumEntry[4:] + + V1IndexLargeEntry = []byte{ + 0x0, 0x0, 0x0, 0x3, + + 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, + } + V1IndexLargeSha = V1IndexLargeEntry[4:] + + V1Index = &Index{ + fanout: V1IndexFanout, + version: V1, + } +) + +func TestIndexV1SearchExact(t *testing.T) { + e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 1) + + assert.NoError(t, err) + assert.Equal(t, 0, cmp) + assert.EqualValues(t, 2, e.PackOffset) +} + +func TestIndexV1SearchSmall(t *testing.T) { + e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 0) + + assert.NoError(t, err) + assert.Equal(t, 1, cmp) + assert.Nil(t, e) +} + +func TestIndexV1SearchBig(t *testing.T) { + e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 2) + + assert.NoError(t, err) + assert.Equal(t, -1, cmp) + assert.Nil(t, e) +} + +func TestIndexV1SearchOutOfBounds(t *testing.T) { + e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 10) + + assert.Nil(t, e) + assert.Equal(t, 0, cmp) + assert.Equal(t, ErrIndexOutOfBounds, err) +} + +func init() { + V1IndexFanout[1] = 1 + V1IndexFanout[2] = 2 + V1IndexFanout[3] = 3 + + for i := 3; i < len(V1IndexFanout); i++ { + V1IndexFanout[i] = 3 + } + + fanout := make([]byte, FanoutWidth) + for i, n := range V1IndexFanout { + binary.BigEndian.PutUint32(fanout[i*FanoutEntryWidth:], n) + } + + buf := make([]byte, 0, OffsetV1Start+(3*ObjectEntryV1Width)) + + buf = append(buf, fanout...) + buf = append(buf, V1IndexSmallEntry...) + buf = append(buf, V1IndexMediumEntry...) + buf = append(buf, V1IndexLargeEntry...) + + V1Index.f = bytes.NewReader(buf) +} diff --git a/git/odb/pack/index_version.go b/git/odb/pack/index_version.go index 27656740..27b7b8ca 100644 --- a/git/odb/pack/index_version.go +++ b/git/odb/pack/index_version.go @@ -1,6 +1,7 @@ package pack import ( + "errors" "fmt" ) @@ -16,5 +17,33 @@ const ( // Width returns the width of the header given in the respective version. func (v IndexVersion) Width() int64 { + switch v { + case V1: + return V1Width + } panic(fmt.Sprintf("git/odb/pack: width unknown for pack version %d", v)) } + +var ( + // ErrIndexOutOfBounds is an error returned when the object lookup "at" + // (see: Search() below) is out of bounds. + ErrIndexOutOfBounds = errors.New("git/odb/pack: index is out of bounds") +) + +// Search searches index "idx" for an object given by "name" at location "at". +// +// If will return the object if it was found, or a comparison determining +// whether to search above or below next. +// +// Otherwise, it will return an error. +func (v IndexVersion) Search(idx *Index, name []byte, at int64) (*IndexEntry, int, error) { + if at > int64(idx.Count()) { + return nil, 0, ErrIndexOutOfBounds + } + + switch v { + case V1: + return v1Search(idx, name, at) + } + return nil, 0, &UnsupportedVersionErr{Got: uint32(v)} +} diff --git a/git/odb/pack/index_version_test.go b/git/odb/pack/index_version_test.go index b41b6f95..531c4da9 100644 --- a/git/odb/pack/index_version_test.go +++ b/git/odb/pack/index_version_test.go @@ -6,6 +6,10 @@ import ( "github.com/stretchr/testify/assert" ) +func TestIndexVersionWidthV1(t *testing.T) { + assert.EqualValues(t, 0, V1.Width()) +} + func TestIndexVersionWidthPanicsOnUnknownVersion(t *testing.T) { v := IndexVersion(5) From 9f96b558ffdeee78c507b8c5ac4862b210ef630d Mon Sep 17 00:00:00 2001 From: Stephen Gelman Date: Mon, 17 Jul 2017 19:55:12 +0000 Subject: [PATCH 3/3] Update gitignore to add some temp files that get created when building debs --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b215777e..525c4e01 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ debian/git-lfs/ debian/*.log debian/files debian/*.substvars +debian/debhelper-build-stamp +debian/.debhelper +/.pc obj-* rpm/BUILD*