Support SHA-256 repositories

Git will start to support SHA-256 as a hash for repositories in the near
future.  Let's update gitobj to version 2 to support SHA-256
repositories properly.  We initialize the repository based on the
extensions.objectFormat value, if one is provided, since this is the
configuration key that represents the hash algorithm.

Vendor the proper dependencies in place.
This commit is contained in:
brian m. carlson 2020-06-30 16:21:08 +00:00
parent 3d0efde544
commit da4fdef00b
No known key found for this signature in database
GPG Key ID: 2D0C9BC12F82B3A1
61 changed files with 240 additions and 147 deletions

@ -10,7 +10,7 @@ import (
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/git/githistory"
"github.com/git-lfs/git-lfs/tasklog"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/spf13/cobra"
)

@ -12,7 +12,7 @@ import (
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/tasklog"
"github.com/git-lfs/git-lfs/tools"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/spf13/cobra"
)

@ -17,7 +17,7 @@ import (
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/tasklog"
"github.com/git-lfs/git-lfs/tools"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/spf13/cobra"
)

@ -13,7 +13,7 @@ import (
"github.com/git-lfs/git-lfs/tasklog"
"github.com/git-lfs/git-lfs/tools"
"github.com/git-lfs/git-lfs/tools/humanize"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/spf13/cobra"
)

@ -26,7 +26,7 @@ import (
lfserrors "github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/subprocess"
"github.com/git-lfs/git-lfs/tools"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/rubyist/tracerx"
)
@ -1427,6 +1427,21 @@ func IsWorkingCopyDirty() (bool, error) {
}
func ObjectDatabase(osEnv, gitEnv Environment, gitdir, tempdir string) (*gitobj.ObjectDatabase, error) {
var options []gitobj.Option
alternates, _ := osEnv.Get("GIT_ALTERNATE_OBJECT_DIRECTORIES")
return gitobj.FromFilesystemWithAlternates(filepath.Join(gitdir, "objects"), tempdir, alternates)
if alternates != "" {
options = append(options, gitobj.Alternates(alternates))
}
hashAlgo, _ := gitEnv.Get("extensions.objectformat")
if hashAlgo != "" {
options = append(options, gitobj.ObjectFormat(gitobj.ObjectFormatAlgorithm(hashAlgo)))
}
odb, err := gitobj.FromFilesystem(filepath.Join(gitdir, "objects"), tempdir, options...)
if err != nil {
return nil, err
}
if odb.Hasher() == nil {
return nil, fmt.Errorf("unsupported repository hash algorithm %q", hashAlgo)
}
return odb, nil
}

@ -3,7 +3,7 @@ package gitattr
import (
"strings"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
)
// Tree represents the .gitattributes file at one layer of the tree in a Git

@ -5,7 +5,7 @@ import (
"os"
"testing"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/git-lfs/wildmatch"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

@ -11,7 +11,7 @@ import (
"strings"
"testing"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/stretchr/testify/assert"
)

@ -9,7 +9,7 @@ import (
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/tasklog"
"github.com/git-lfs/git-lfs/tools"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
)
// refUpdater is a type responsible for moving references from one point in the

@ -12,7 +12,7 @@ import (
"github.com/git-lfs/git-lfs/filepathfilter"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/tasklog"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
)
// Rewriter allows rewriting topologically equivalent Git histories

@ -12,7 +12,7 @@ import (
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/filepathfilter"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/stretchr/testify/assert"
)

@ -5,8 +5,8 @@ import (
"fmt"
"io"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/errors"
"github.com/git-lfs/gitobj/v2"
"github.com/git-lfs/gitobj/v2/errors"
)
// object represents a generic Git object of any type.

2
go.mod

@ -4,7 +4,7 @@ require (
github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858
github.com/avast/retry-go v2.4.2+incompatible
github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0
github.com/git-lfs/gitobj v1.4.1
github.com/git-lfs/gitobj/v2 v2.0.0
github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18
github.com/git-lfs/go-ntlm v0.0.0-20190401175752-c5056e7fa066
github.com/git-lfs/wildmatch v1.0.4

2
go.sum

@ -9,6 +9,8 @@ github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0 h1:Hhh7nu7CfFVl
github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
github.com/git-lfs/gitobj v1.4.1 h1:6nH5d1QP7GJjZfBqaBXpS7mDzT4plXQLqUjPbcbtRpw=
github.com/git-lfs/gitobj v1.4.1/go.mod h1:B+djgKTnUoJHbg4uDvnC/+6xPcfEJNFbZd/YunEJRtA=
github.com/git-lfs/gitobj/v2 v2.0.0 h1:2Nm6MQo6coYxv1yYptgBQfny9HFRLHHdbYetBDIkJyg=
github.com/git-lfs/gitobj/v2 v2.0.0/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18 h1:7Th0eBA4rT8WJNiM1vppjaIv9W5WJinhpbCJvRJxloI=
github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
github.com/git-lfs/go-ntlm v0.0.0-20190401175752-c5056e7fa066 h1:f5UyyCnv3o2EHy+zsqOyYa8jB5bZR/N9ZEideqeDYag=

@ -10,7 +10,7 @@ import (
"testing"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/gitobj"
"github.com/git-lfs/gitobj/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

@ -1,9 +0,0 @@
language: go
go: 1.11
before_install:
- >
mkdir -p ~/src;
mv "$TRAVIS_BUILD_DIR" ~/src/gitobj;
export TRAVIS_BUILD_DIR=~/src/gitobj;
notifications:
email: false

@ -2,6 +2,7 @@ package gitobj
import (
"bufio"
"hash"
"io"
"os"
"path"
@ -9,32 +10,28 @@ import (
"strconv"
"strings"
"github.com/git-lfs/gitobj/pack"
"github.com/git-lfs/gitobj/storage"
"github.com/git-lfs/gitobj/v2/pack"
"github.com/git-lfs/gitobj/v2/storage"
)
// NewFilesystemBackend initializes a new filesystem-based backend.
func NewFilesystemBackend(root, tmp string) (storage.Backend, error) {
return NewFilesystemBackendWithAlternates(root, tmp, "")
}
// NewFilesystemBackendWithAlternates initializes a new filesystem-based
// backend, optionally with additional alternates as specified in the
// NewFilesystemBackend initializes a new filesystem-based backend,
// optionally with additional alternates as specified in the
// `alternates` variable. The syntax is that of the Git environment variable
// GIT_ALTERNATE_OBJECT_DIRECTORIES.
func NewFilesystemBackendWithAlternates(root, tmp, alternates string) (storage.Backend, error) {
// GIT_ALTERNATE_OBJECT_DIRECTORIES. The hash algorithm used is specified by
// the algo parameter.
func NewFilesystemBackend(root, tmp, alternates string, algo hash.Hash) (storage.Backend, error) {
fsobj := newFileStorer(root, tmp)
packs, err := pack.NewStorage(root)
packs, err := pack.NewStorage(root, algo)
if err != nil {
return nil, err
}
storage, err := findAllBackends(fsobj, packs, root)
storage, err := findAllBackends(fsobj, packs, root, algo)
if err != nil {
return nil, err
}
storage, err = addAlternatesFromEnvironment(storage, alternates)
storage, err = addAlternatesFromEnvironment(storage, alternates, algo)
if err != nil {
return nil, err
}
@ -45,7 +42,7 @@ func NewFilesystemBackendWithAlternates(root, tmp, alternates string) (storage.B
}, nil
}
func findAllBackends(mainLoose *fileStorer, mainPacked *pack.Storage, root string) ([]storage.Storage, error) {
func findAllBackends(mainLoose *fileStorer, mainPacked *pack.Storage, root string, algo hash.Hash) ([]storage.Storage, error) {
storage := make([]storage.Storage, 2)
storage[0] = mainLoose
storage[1] = mainPacked
@ -61,7 +58,7 @@ func findAllBackends(mainLoose *fileStorer, mainPacked *pack.Storage, root strin
scanner := bufio.NewScanner(f)
for scanner.Scan() {
storage, err = addAlternateDirectory(storage, scanner.Text())
storage, err = addAlternateDirectory(storage, scanner.Text(), algo)
if err != nil {
return nil, err
}
@ -74,9 +71,9 @@ func findAllBackends(mainLoose *fileStorer, mainPacked *pack.Storage, root strin
return storage, nil
}
func addAlternateDirectory(s []storage.Storage, dir string) ([]storage.Storage, error) {
func addAlternateDirectory(s []storage.Storage, dir string, algo hash.Hash) ([]storage.Storage, error) {
s = append(s, newFileStorer(dir, ""))
pack, err := pack.NewStorage(dir)
pack, err := pack.NewStorage(dir, algo)
if err != nil {
return s, err
}
@ -84,14 +81,14 @@ func addAlternateDirectory(s []storage.Storage, dir string) ([]storage.Storage,
return s, nil
}
func addAlternatesFromEnvironment(s []storage.Storage, env string) ([]storage.Storage, error) {
func addAlternatesFromEnvironment(s []storage.Storage, env string, algo hash.Hash) ([]storage.Storage, error) {
if len(env) == 0 {
return s, nil
}
for _, dir := range splitAlternateString(env, alternatesSeparator) {
var err error
s, err = addAlternateDirectory(s, dir)
s, err = addAlternateDirectory(s, dir, algo)
if err != nil {
return nil, err
}

@ -3,6 +3,7 @@ package gitobj
import (
"bytes"
"fmt"
"hash"
"io"
"os"
)
@ -75,7 +76,7 @@ func (b *Blob) Type() ObjectType { return BlobObjectType }
// stream, which is always zero.
//
// If any errors are encountered while reading the blob, they will be returned.
func (b *Blob) Decode(r io.Reader, size int64) (n int, err error) {
func (b *Blob) Decode(hash hash.Hash, r io.Reader, size int64) (n int, err error) {
b.Size = size
b.Contents = io.LimitReader(r, size)

@ -5,6 +5,7 @@ import (
"bytes"
"encoding/hex"
"fmt"
"hash"
"io"
"strings"
"time"
@ -91,11 +92,12 @@ func (c *Commit) Type() ObjectType { return CommitObjectType }
//
// If any error was encountered along the way, that will be returned, along with
// the number of bytes read up to that point.
func (c *Commit) Decode(from io.Reader, size int64) (n int, err error) {
func (c *Commit) Decode(hash hash.Hash, from io.Reader, size int64) (n int, err error) {
var finishedHeaders bool
var messageParts []string
s := bufio.NewScanner(from)
s.Buffer(nil, 1024*1024)
for s.Scan() {
text := s.Text()
n = n + len(text+"\n")
@ -169,7 +171,7 @@ func (c *Commit) Decode(from io.Reader, size int64) (n int, err error) {
c.Message = strings.Join(messageParts, "\n")
if err = s.Err(); err != nil {
return n, err
return n, fmt.Errorf("failed to parse commit buffer: %s", err)
}
return n, err
}

@ -8,7 +8,7 @@ import (
"os"
"path/filepath"
"github.com/git-lfs/gitobj/errors"
"github.com/git-lfs/gitobj/v2/errors"
)
// fileStorer implements the storer interface by writing to the .git/objects

@ -1,4 +1,4 @@
module github.com/git-lfs/gitobj
module github.com/git-lfs/gitobj/v2
require (
github.com/davecgh/go-spew v1.1.1 // indirect

@ -6,7 +6,7 @@ import (
"io"
"sync"
"github.com/git-lfs/gitobj/errors"
"github.com/git-lfs/gitobj/v2/errors"
)
// memoryStorer is an implementation of the storer interface that holds data for

@ -1,6 +1,9 @@
package gitobj
import "io"
import (
"hash"
"io"
)
// Object is an interface satisfied by any concrete type that represents a loose
// Git object.
@ -29,7 +32,7 @@ type Object interface {
//
// If an(y) error was encountered, it should be returned immediately,
// along with the number of bytes read up to that point.
Decode(from io.Reader, size int64) (n int, err error)
Decode(hash hash.Hash, from io.Reader, size int64) (n int, err error)
// Type returns the ObjectType constant that represents an instance of
// the implementing type.

@ -2,13 +2,16 @@ package gitobj
import (
"bytes"
"crypto/sha1"
"crypto/sha256"
"fmt"
"hash"
"io"
"io/ioutil"
"os"
"sync/atomic"
"github.com/git-lfs/gitobj/storage"
"github.com/git-lfs/gitobj/v2/storage"
)
// ObjectDatabase enables the reading and writing of objects against a storage
@ -29,41 +32,80 @@ type ObjectDatabase struct {
// temp directory, defaults to os.TempDir
tmp string
// objectFormat is the object format (hash algorithm)
objectFormat ObjectFormatAlgorithm
}
type options struct {
alternates string
objectFormat ObjectFormatAlgorithm
}
type Option func(*options)
type ObjectFormatAlgorithm string
const (
ObjectFormatSHA1 = ObjectFormatAlgorithm("sha1")
ObjectFormatSHA256 = ObjectFormatAlgorithm("sha256")
)
// Alternates is an Option to specify the string of alternate repositories that
// are searched for objects. The format is the same as for
// GIT_ALTERNATE_OBJECT_DIRECTORIES.
func Alternates(alternates string) Option {
return func(args *options) {
args.alternates = alternates
}
}
// ObjectFormat is an Option to specify the hash algorithm (object format) in
// use in Git. If not specified, it defaults to ObjectFormatSHA1.
func ObjectFormat(algo ObjectFormatAlgorithm) Option {
return func(args *options) {
args.objectFormat = algo
}
}
// FromFilesystem constructs an *ObjectDatabase instance that is backed by a
// directory on the filesystem. Specifically, this should point to:
//
// /absolute/repo/path/.git/objects
func FromFilesystem(root, tmp string) (*ObjectDatabase, error) {
return FromFilesystemWithAlternates(root, tmp, "")
}
func FromFilesystem(root, tmp string, setters ...Option) (*ObjectDatabase, error) {
args := &options{objectFormat: ObjectFormatSHA1}
// FromFilesystemWithAlternates constructs an *ObjectDatabase instance that is
// backed by a directory on the filesystem, optionally with one or more
// alternates. Specifically, this should point to:
//
// /absolute/repo/path/.git/objects
func FromFilesystemWithAlternates(root, tmp, alternates string) (*ObjectDatabase, error) {
b, err := NewFilesystemBackendWithAlternates(root, tmp, alternates)
for _, setter := range setters {
setter(args)
}
b, err := NewFilesystemBackend(root, tmp, args.alternates, hasher(args.objectFormat))
if err != nil {
return nil, err
}
ro, rw := b.Storage()
return &ObjectDatabase{
tmp: tmp,
ro: ro,
rw: rw,
}, nil
odb, err := FromBackend(b, setters...)
if err != nil {
return nil, err
}
odb.tmp = tmp
return odb, nil
}
func FromBackend(b storage.Backend) (*ObjectDatabase, error) {
func FromBackend(b storage.Backend, setters ...Option) (*ObjectDatabase, error) {
args := &options{objectFormat: ObjectFormatSHA1}
for _, setter := range setters {
setter(args)
}
ro, rw := b.Storage()
return &ObjectDatabase{
ro: ro,
rw: rw,
}, nil
odb := &ObjectDatabase{
ro: ro,
rw: rw,
objectFormat: args.objectFormat,
}
return odb, nil
}
// Close closes the *ObjectDatabase, freeing any open resources (namely: the
@ -227,6 +269,11 @@ func (o *ObjectDatabase) Root() (string, bool) {
return "", false
}
// Hasher returns a new hash instance suitable for this object database.
func (o *ObjectDatabase) Hasher() hash.Hash {
return hasher(o.objectFormat)
}
// encode encodes and saves an object to the storage backend and uses an
// in-memory buffer to calculate the object's encoded body.
func (d *ObjectDatabase) encode(object Object) (sha []byte, n int64, err error) {
@ -247,7 +294,7 @@ func (d *ObjectDatabase) encodeBuffer(object Object, buf io.ReadWriter) (sha []b
}
defer d.cleanup(tmp)
to := NewObjectWriter(tmp)
to := NewObjectWriter(tmp, d.Hasher())
if _, err = to.WriteHeader(object.Type(), int64(cn)); err != nil {
return nil, 0, err
}
@ -323,7 +370,7 @@ func (o *ObjectDatabase) decode(r *ObjectReader, into Object) error {
return &UnexpectedObjectType{Got: typ, Wanted: into.Type()}
}
if _, err = into.Decode(r, size); err != nil {
if _, err = into.Decode(o.Hasher(), r, size); err != nil {
return err
}
@ -337,3 +384,14 @@ func (o *ObjectDatabase) cleanup(f *os.File) {
f.Close()
os.Remove(f.Name())
}
func hasher(algo ObjectFormatAlgorithm) hash.Hash {
switch algo {
case ObjectFormatSHA1:
return sha1.New()
case ObjectFormatSHA256:
return sha256.New()
default:
return nil
}
}

@ -2,7 +2,6 @@ package gitobj
import (
"compress/zlib"
"crypto/sha1"
"fmt"
"hash"
"io"
@ -50,18 +49,20 @@ func (n *nopCloser) Close() error {
}
// NewObjectWriter returns a new *ObjectWriter instance that drains incoming
// writes into the io.Writer given, "w".
func NewObjectWriter(w io.Writer) *ObjectWriter {
return NewObjectWriteCloser(&nopCloser{w})
// writes into the io.Writer given, "w". "hash" is a hash instance from the
// ObjectDatabase'e Hash method.
func NewObjectWriter(w io.Writer, hash hash.Hash) *ObjectWriter {
return NewObjectWriteCloser(&nopCloser{w}, hash)
}
// NewObjectWriter returns a new *ObjectWriter instance that drains incoming
// writes into the io.Writer given, "w".
// writes into the io.Writer given, "w". "sum" is a hash instance from the
// ObjectDatabase'e Hash method.
//
// Upon closing, it calls the given Close() function of the io.WriteCloser.
func NewObjectWriteCloser(w io.WriteCloser) *ObjectWriter {
func NewObjectWriteCloser(w io.WriteCloser, sum hash.Hash) *ObjectWriter {
zw := zlib.NewWriter(w)
sum := sha1.New()
sum.Reset()
return &ObjectWriter{
w: io.MultiWriter(zw, sum),

@ -2,10 +2,13 @@ package pack
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
)
const maxHashSize = sha256.Size
// Index stores information about the location of objects in a corresponding
// packfile.
type Index struct {

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"fmt"
"hash"
"io"
)
@ -33,8 +34,6 @@ const (
// V2 header.
indexOffsetV2Start = indexV2Width + indexFanoutWidth
// indexObjectNameWidth is the width of a SHA1 object name.
indexObjectNameWidth = 20
// indexObjectCRCWidth is the width of the CRC accompanying each object
// in V2.
indexObjectCRCWidth = 4
@ -44,13 +43,6 @@ const (
// indexObjectLargeOffsetWidth is the width of the optional large offset
// encoded into the small offset.
indexObjectLargeOffsetWidth = 8
// indexObjectEntryV1Width is the width of one contiguous object entry
// in V1.
indexObjectEntryV1Width = indexObjectNameWidth + indexObjectSmallOffsetWidth
// indexObjectEntryV2Width is the width of one non-contiguous object
// entry in V2.
indexObjectEntryV2Width = indexObjectNameWidth + indexObjectCRCWidth + indexObjectSmallOffsetWidth
)
var (
@ -69,8 +61,8 @@ var (
// parse index entries.
//
// If there was an error parsing, it will be returned immediately.
func DecodeIndex(r io.ReaderAt) (*Index, error) {
version, err := decodeIndexHeader(r)
func DecodeIndex(r io.ReaderAt, hash hash.Hash) (*Index, error) {
version, err := decodeIndexHeader(r, hash)
if err != nil {
return nil, err
}
@ -89,7 +81,7 @@ func DecodeIndex(r io.ReaderAt) (*Index, error) {
}
// decodeIndexHeader determines which version the index given by "r" is.
func decodeIndexHeader(r io.ReaderAt) (IndexVersion, error) {
func decodeIndexHeader(r io.ReaderAt, hash hash.Hash) (IndexVersion, error) {
hdr := make([]byte, 4)
if _, err := r.ReadAt(hdr, 0); err != nil {
return nil, err
@ -104,13 +96,13 @@ func decodeIndexHeader(r io.ReaderAt) (IndexVersion, error) {
version := binary.BigEndian.Uint32(vb)
switch version {
case 1:
return new(V1), nil
return &V1{hash: hash}, nil
case 2:
return new(V2), nil
return &V2{hash: hash}, nil
}
return nil, &UnsupportedVersionErr{uint32(version)}
}
return new(V1), nil
return &V1{hash: hash}, nil
}
// decodeIndexFanout decodes the fanout table given by "r" and beginning at the

@ -2,27 +2,33 @@ package pack
import (
"encoding/binary"
"hash"
)
// V1 implements IndexVersion for v1 packfiles.
type V1 struct{}
type V1 struct {
hash hash.Hash
}
// Name implements IndexVersion.Name by returning the 20 byte SHA-1 object name
// for the given entry at offset "at" in the v1 index file "idx".
func (v *V1) Name(idx *Index, at int64) ([]byte, error) {
var sha [20]byte
if _, err := idx.readAt(sha[:], v1ShaOffset(at)); err != nil {
var sha [maxHashSize]byte
hashlen := v.hash.Size()
if _, err := idx.readAt(sha[:hashlen], v1ShaOffset(at, int64(hashlen))); err != nil {
return nil, err
}
return sha[:], nil
return sha[:hashlen], nil
}
// Entry implements IndexVersion.Entry for v1 packfiles by parsing and returning
// the IndexEntry specified at the offset "at" in the given index file.
func (v *V1) Entry(idx *Index, at int64) (*IndexEntry, error) {
var offs [4]byte
if _, err := idx.readAt(offs[:], v1EntryOffset(at)); err != nil {
if _, err := idx.readAt(offs[:], v1EntryOffset(at, int64(v.hash.Size()))); err != nil {
return nil, err
}
@ -38,9 +44,9 @@ func (v *V1) Width() int64 {
}
// v1ShaOffset returns the location of the SHA1 of an object given at "at".
func v1ShaOffset(at int64) int64 {
func v1ShaOffset(at int64, hashlen int64) int64 {
// Skip forward until the desired entry.
return v1EntryOffset(at) +
return v1EntryOffset(at, hashlen) +
// Skip past the 4-byte object offset in the desired entry to
// the SHA1.
indexObjectSmallOffsetWidth
@ -48,9 +54,9 @@ func v1ShaOffset(at int64) int64 {
// v1EntryOffset returns the location of the packfile offset for the object
// given at "at".
func v1EntryOffset(at int64) int64 {
func v1EntryOffset(at int64, hashlen int64) int64 {
// Skip the L1 fanout table
return indexOffsetV1Start +
// Skip the object entries before the one located at "at"
(indexObjectEntryV1Width * at)
((hashlen + indexObjectSmallOffsetWidth) * at)
}

@ -2,27 +2,36 @@ package pack
import (
"encoding/binary"
"hash"
)
// V2 implements IndexVersion for v2 packfiles.
type V2 struct{}
type V2 struct {
hash hash.Hash
}
// Name implements IndexVersion.Name by returning the 20 byte SHA-1 object name
// for the given entry at offset "at" in the v2 index file "idx".
func (v *V2) Name(idx *Index, at int64) ([]byte, error) {
var sha [20]byte
if _, err := idx.readAt(sha[:], v2ShaOffset(at)); err != nil {
var sha [maxHashSize]byte
hashlen := v.hash.Size()
if _, err := idx.readAt(sha[:hashlen], v2ShaOffset(at, int64(hashlen))); err != nil {
return nil, err
}
return sha[:], nil
return sha[:hashlen], nil
}
// Entry implements IndexVersion.Entry for v2 packfiles by parsing and returning
// the IndexEntry specified at the offset "at" in the given index file.
func (v *V2) Entry(idx *Index, at int64) (*IndexEntry, error) {
var offs [4]byte
if _, err := idx.readAt(offs[:], v2SmallOffsetOffset(at, int64(idx.Count()))); err != nil {
hashlen := v.hash.Size()
if _, err := idx.readAt(offs[:], v2SmallOffsetOffset(at, int64(idx.Count()), int64(hashlen))); err != nil {
return nil, err
}
@ -33,7 +42,7 @@ func (v *V2) Entry(idx *Index, at int64) (*IndexEntry, error) {
//
// Mask away (offs&0x7fffffff) the MSB to use as an index to
// find the offset of the 8-byte pack offset.
lo := v2LargeOffsetOffset(int64(loc&0x7fffffff), int64(idx.Count()))
lo := v2LargeOffsetOffset(int64(loc&0x7fffffff), int64(idx.Count()), int64(hashlen))
var offs [8]byte
if _, err := idx.readAt(offs[:], lo); err != nil {
@ -52,20 +61,20 @@ func (v *V2) Width() int64 {
}
// v2ShaOffset returns the offset of a SHA1 given at "at" in the V2 index file.
func v2ShaOffset(at int64) int64 {
func v2ShaOffset(at int64, hashlen int64) int64 {
// Skip the packfile index header and the L1 fanout table.
return indexOffsetV2Start +
// Skip until the desired name in the sorted names table.
(indexObjectNameWidth * at)
(hashlen * at)
}
// v2SmallOffsetOffset returns the offset of an object's small (4-byte) offset
// given by "at".
func v2SmallOffsetOffset(at, total int64) int64 {
func v2SmallOffsetOffset(at, total, hashlen int64) int64 {
// Skip the packfile index header and the L1 fanout table.
return indexOffsetV2Start +
// Skip the name table.
(indexObjectNameWidth * total) +
(hashlen * total) +
// Skip the CRC table.
(indexObjectCRCWidth * total) +
// Skip until the desired index in the small offsets table.
@ -74,11 +83,11 @@ func v2SmallOffsetOffset(at, total int64) int64 {
// v2LargeOffsetOffset returns the offset of an object's large (4-byte) offset,
// given by the index "at".
func v2LargeOffsetOffset(at, total int64) int64 {
func v2LargeOffsetOffset(at, total, hashlen int64) int64 {
// Skip the packfile index header and the L1 fanout table.
return indexOffsetV2Start +
// Skip the name table.
(indexObjectNameWidth * total) +
(hashlen * total) +
// Skip the CRC table.
(indexObjectCRCWidth * total) +
// Skip the small offsets table.

@ -3,6 +3,7 @@ package pack
import (
"compress/zlib"
"fmt"
"hash"
"io"
"io/ioutil"
)
@ -18,6 +19,9 @@ type Packfile struct {
// objects in this packfile.
idx *Index
// hash is the hash algorithm used in this pack.
hash hash.Hash
// r is an io.ReaderAt that allows read access to the packfile itself.
r io.ReaderAt
}
@ -181,11 +185,13 @@ func (p *Packfile) find(offset int64) (Chain, error) {
func (p *Packfile) findBase(typ PackedObjectType, offset, objOffset int64) (Chain, int64, error) {
var baseOffset int64
// We assume that we have to read at least 20 bytes (the SHA-1 length in
// the case of a OBJ_REF_DELTA, or greater than the length of the base
// offset encoded in an OBJ_OFS_DELTA).
var sha [20]byte
if _, err := p.r.ReadAt(sha[:], offset); err != nil {
hashlen := p.hash.Size()
// We assume that we have to read at least an object ID's worth (the
// hash length in the case of a OBJ_REF_DELTA, or greater than the
// length of the base offset encoded in an OBJ_OFS_DELTA).
var sha [32]byte
if _, err := p.r.ReadAt(sha[:hashlen], offset); err != nil {
return nil, baseOffset, err
}
@ -213,13 +219,13 @@ func (p *Packfile) findBase(typ PackedObjectType, offset, objOffset int64) (Chai
// If the delta is an OBJ_REFS_DELTA, find the location of its
// base by reading the SHA-1 name and looking it up in the
// corresponding pack index file.
e, err := p.idx.Entry(sha[:])
e, err := p.idx.Entry(sha[:hashlen])
if err != nil {
return nil, baseOffset, err
}
baseOffset = int64(e.PackOffset)
offset += 20
offset += int64(hashlen)
default:
// If we did not receive an OBJ_OFS_DELTA, or OBJ_REF_DELTA, the
// type given is not a delta-fied type. Return an error.

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"errors"
"hash"
"io"
)
@ -22,7 +23,7 @@ var (
//
// If the header is malformed, or otherwise cannot be read, an error will be
// returned without a corresponding packfile.
func DecodePackfile(r io.ReaderAt) (*Packfile, error) {
func DecodePackfile(r io.ReaderAt, hash hash.Hash) (*Packfile, error) {
header := make([]byte, 12)
if _, err := r.ReadAt(header[:], 0); err != nil {
return nil, err
@ -40,5 +41,6 @@ func DecodePackfile(r io.ReaderAt) (*Packfile, error) {
Objects: objects,
r: r,
hash: hash,
}, nil
}

@ -2,13 +2,14 @@ package pack
import (
"fmt"
"hash"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/git-lfs/gitobj/errors"
"github.com/git-lfs/gitobj/v2/errors"
)
// Set allows access of objects stored across a set of packfiles.
@ -38,7 +39,7 @@ var (
// containing them. If there was an error parsing the packfiles in that
// directory, or the directory was otherwise unable to be observed, NewSet
// returns that error.
func NewSet(db string) (*Set, error) {
func NewSet(db string, algo hash.Hash) (*Set, error) {
pd := filepath.Join(db, "pack")
paths, err := filepath.Glob(filepath.Join(escapeGlobPattern(pd), "*.pack"))
@ -75,12 +76,12 @@ func NewSet(db string) (*Set, error) {
return nil, err
}
pack, err := DecodePackfile(packf)
pack, err := DecodePackfile(packf, algo)
if err != nil {
return nil, err
}
idx, err := DecodeIndex(idxf)
idx, err := DecodeIndex(idxf, algo)
if err != nil {
return nil, err
}
@ -138,7 +139,7 @@ func NewSetPacks(packs ...*Packfile) *Set {
}
return &Set{
m: m,
m: m,
closeFn: func() error {
for _, pack := range packs {
if err := pack.Close(); err != nil {

@ -1,6 +1,7 @@
package pack
import (
"hash"
"io"
)
@ -10,8 +11,8 @@ type Storage struct {
}
// NewStorage returns a new storage object based on a pack set.
func NewStorage(root string) (*Storage, error) {
packs, err := NewSet(root)
func NewStorage(root string, algo hash.Hash) (*Storage, error) {
packs, err := NewSet(root, algo)
if err != nil {
return nil, err
}

@ -3,7 +3,7 @@ package storage
import (
"io"
"github.com/git-lfs/gitobj/errors"
"github.com/git-lfs/gitobj/v2/errors"
)
// Storage implements an interface for reading, but not writing, objects in an

@ -5,6 +5,7 @@ import (
"bytes"
"encoding/hex"
"fmt"
"hash"
"io"
"strings"
)
@ -24,7 +25,7 @@ type Tag struct {
//
// If any error was encountered along the way it will be returned, and the
// receiving *Tag is considered invalid.
func (t *Tag) Decode(r io.Reader, size int64) (int, error) {
func (t *Tag) Decode(hash hash.Hash, r io.Reader, size int64) (int, error) {
scanner := bufio.NewScanner(io.LimitReader(r, size))
var (

@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"fmt"
"hash"
"io"
"sort"
"strconv"
@ -27,7 +28,8 @@ func (t *Tree) Type() ObjectType { return TreeObjectType }
//
// If any error was encountered along the way, that will be returned, along with
// the number of bytes read up to that point.
func (t *Tree) Decode(from io.Reader, size int64) (n int, err error) {
func (t *Tree) Decode(hash hash.Hash, from io.Reader, size int64) (n int, err error) {
hashlen := hash.Size()
buf := bufio.NewReader(from)
var entries []*TreeEntry
@ -51,15 +53,15 @@ func (t *Tree) Decode(from io.Reader, size int64) (n int, err error) {
n += len(fname)
fname = strings.TrimSuffix(fname, "\x00")
var sha [20]byte
if _, err = io.ReadFull(buf, sha[:]); err != nil {
var sha [32]byte
if _, err = io.ReadFull(buf, sha[:hashlen]); err != nil {
return n, err
}
n += 20
n += hashlen
entries = append(entries, &TreeEntry{
Name: fname,
Oid: sha[:],
Oid: sha[:hashlen],
Filemode: int32(mode),
})
}

10
vendor/modules.txt vendored

@ -8,11 +8,11 @@ github.com/avast/retry-go
github.com/davecgh/go-spew/spew
# github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0
github.com/dpotapov/go-spnego
# github.com/git-lfs/gitobj v1.4.1
github.com/git-lfs/gitobj
github.com/git-lfs/gitobj/errors
github.com/git-lfs/gitobj/pack
github.com/git-lfs/gitobj/storage
# github.com/git-lfs/gitobj/v2 v2.0.0
github.com/git-lfs/gitobj/v2
github.com/git-lfs/gitobj/v2/errors
github.com/git-lfs/gitobj/v2/pack
github.com/git-lfs/gitobj/v2/storage
# github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18
github.com/git-lfs/go-netrc/netrc
# github.com/git-lfs/go-ntlm v0.0.0-20190401175752-c5056e7fa066