pkg/blob: add ref.HasPrefix method

Continuation of 504604e22d

Needed by #972

Change-Id: Ia4873aba5d1b1be7774ce38eeff15761961b41d7
This commit is contained in:
mpl 2018-01-01 18:57:13 +01:00
parent bf4b74b5d6
commit b71aa74f31
2 changed files with 139 additions and 0 deletions

View File

@ -66,6 +66,7 @@ type digestType interface {
digestName() string
newHash() hash.Hash
equalString(string) bool
hasPrefix(string) bool
}
func (r Ref) String() string {
@ -97,6 +98,12 @@ func (r Ref) StringMinusOne() string {
// It does not allocate.
func (r Ref) EqualString(s string) bool { return r.digest.equalString(s) }
// HasPrefix reports whether s is a prefix of r.String(). It returns false if s
// does not contain at least the digest name prefix (e.g. "sha1-") and one byte of
// digest.
// It does not allocate.
func (r Ref) HasPrefix(s string) bool { return r.digest.hasPrefix(s) }
func (r Ref) appendString(buf []byte) []byte {
dname := r.digest.digestName()
bs := r.digest.bytes()
@ -425,6 +432,40 @@ func (d sha1Digest) equalString(s string) bool {
return true
}
func (d sha1Digest) hasPrefix(s string) bool {
if len(s) > 45 {
return false
}
if len(s) == 45 {
return d.equalString(s)
}
if !strings.HasPrefix(s, "sha1-") {
return false
}
s = s[len("sha1-"):]
if len(s) == 0 {
// we want at least one digest char to match on
return false
}
for i, b := range d[:] {
even := i * 2
if even == len(s) {
break
}
if s[even] != hexDigit[b>>4] {
return false
}
odd := i*2 + 1
if odd == len(s) {
break
}
if s[odd] != hexDigit[b&0xf] {
return false
}
}
return true
}
const maxOtherDigestLen = 128
type otherDigest struct {
@ -460,6 +501,44 @@ func (d otherDigest) equalString(s string) bool {
return true
}
func (d otherDigest) hasPrefix(s string) bool {
maxLen := len(d.name) + len("-") + 2*d.sumLen
if d.odd {
maxLen--
}
if len(s) > maxLen || !strings.HasPrefix(s, d.name) || s[len(d.name)] != '-' {
return false
}
if len(s) == maxLen {
return d.equalString(s)
}
s = s[len(d.name)+1:]
if len(s) == 0 {
// we want at least one digest char to match on
return false
}
for i, b := range d.sum[:d.sumLen] {
even := i * 2
if even == len(s) {
break
}
if s[even] != hexDigit[b>>4] {
return false
}
odd := i*2 + 1
if odd == len(s) {
break
}
if i == d.sumLen-1 && d.odd {
break
}
if s[odd] != hexDigit[b&0xf] {
return false
}
}
return true
}
var sha1Meta = &digestMeta{
ctor: sha1FromBinary,
ctors: sha1FromHexString,

View File

@ -334,3 +334,63 @@ func BenchmarkEqualString(b *testing.B) {
}
}
}
var hasPrefixTests = []struct {
ref Ref
str string
want bool
}{
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659", true},
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9ed", true},
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9e", true},
// last digit wrong:
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9ee", false},
// second to last digit wrong:
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9f", false},
// hyphen wrong:
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1xce284c167558a9ef22df04390c87a6d0c9ed", false},
// truncated:
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-c", true},
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-", false},
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1", false},
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "", false},
// wrong hash:
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha2-ce284c167558a9ef22df04390c87a6d0c9ed96", false},
// Other hashes:
{MustParse("foo-cafe"), "foo-cafe", true},
{MustParse("foo-cafe"), "foo-caf", true},
{MustParse("foo-cafe"), "foo-ca", true},
{MustParse("foo-cafe"), "foo-c", true},
{MustParse("foo-cafe"), "foo-", false},
{MustParse("foo-cafe"), "", false},
{MustParse("foo-cafe"), "foo-beef", false},
{MustParse("foo-cafe"), "foo-bee", false},
{MustParse("foo-cafe"), "bar-cafe", false},
{MustParse("foo-cafe"), "fooxbe", false},
{MustParse("foo-cafe"), "foo-c", true},
{MustParse("foo-caf"), "foo-cae", false},
{MustParse("foo-caf"), "foo-cb", false},
}
func TestHasPrefix(t *testing.T) {
for _, tt := range hasPrefixTests {
got := tt.ref.HasPrefix(tt.str)
if got != tt.want {
t.Errorf("ref %q HasPrefix(%q) = %v; want %v", tt.ref, tt.str, got, tt.want)
}
}
}
func BenchmarkHasPrefix(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, tt := range hasPrefixTests {
got := tt.ref.HasPrefix(tt.str)
if got != tt.want {
b.Fatalf("ref %q HasPrefix(%q) = %v; want %v", tt.ref, tt.str, got, tt.want)
}
}
}
}