mirror of https://github.com/perkeep/perkeep.git
blob: add Ref.EqualString method
Adds allocation-free way to check if a blob ref is equal to its stringified form. For #972 (doesn't fix it, as that bug is about a pending CL) Change-Id: I49c6dee162698d38bb12314623b1507ee7bb246e
This commit is contained in:
parent
36f351ff50
commit
504604e22d
|
@ -65,13 +65,13 @@ type digestType interface {
|
||||||
bytes() []byte
|
bytes() []byte
|
||||||
digestName() string
|
digestName() string
|
||||||
newHash() hash.Hash
|
newHash() hash.Hash
|
||||||
|
equalString(string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Ref) String() string {
|
func (r Ref) String() string {
|
||||||
if r.digest == nil {
|
if r.digest == nil {
|
||||||
return "<invalid-blob.Ref>"
|
return "<invalid-blob.Ref>"
|
||||||
}
|
}
|
||||||
// TODO: maybe memoize this.
|
|
||||||
dname := r.digest.digestName()
|
dname := r.digest.digestName()
|
||||||
bs := r.digest.bytes()
|
bs := r.digest.bytes()
|
||||||
buf := getBuf(len(dname) + 1 + len(bs)*2)[:0]
|
buf := getBuf(len(dname) + 1 + len(bs)*2)[:0]
|
||||||
|
@ -84,7 +84,6 @@ func (r Ref) StringMinusOne() string {
|
||||||
if r.digest == nil {
|
if r.digest == nil {
|
||||||
return "<invalid-blob.Ref>"
|
return "<invalid-blob.Ref>"
|
||||||
}
|
}
|
||||||
// TODO: maybe memoize this.
|
|
||||||
dname := r.digest.digestName()
|
dname := r.digest.digestName()
|
||||||
bs := r.digest.bytes()
|
bs := r.digest.bytes()
|
||||||
buf := getBuf(len(dname) + 1 + len(bs)*2)[:0]
|
buf := getBuf(len(dname) + 1 + len(bs)*2)[:0]
|
||||||
|
@ -94,6 +93,10 @@ func (r Ref) StringMinusOne() string {
|
||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EqualString reports whether r.String() is equal to s.
|
||||||
|
// It does not allocate.
|
||||||
|
func (r Ref) EqualString(s string) bool { return r.digest.equalString(s) }
|
||||||
|
|
||||||
func (r Ref) appendString(buf []byte) []byte {
|
func (r Ref) appendString(buf []byte) []byte {
|
||||||
dname := r.digest.digestName()
|
dname := r.digest.digestName()
|
||||||
bs := r.digest.bytes()
|
bs := r.digest.bytes()
|
||||||
|
@ -403,9 +406,24 @@ func SHA1FromBytes(b []byte) Ref {
|
||||||
|
|
||||||
type sha1Digest [20]byte
|
type sha1Digest [20]byte
|
||||||
|
|
||||||
func (s sha1Digest) digestName() string { return "sha1" }
|
func (d sha1Digest) digestName() string { return "sha1" }
|
||||||
func (s sha1Digest) bytes() []byte { return s[:] }
|
func (d sha1Digest) bytes() []byte { return d[:] }
|
||||||
func (s sha1Digest) newHash() hash.Hash { return sha1.New() }
|
func (d sha1Digest) newHash() hash.Hash { return sha1.New() }
|
||||||
|
func (d sha1Digest) equalString(s string) bool {
|
||||||
|
if len(s) != 45 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(s, "sha1-") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s = s[len("sha1-"):]
|
||||||
|
for i, b := range d[:] {
|
||||||
|
if s[i*2] != hexDigit[b>>4] || s[i*2+1] != hexDigit[b&0xf] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const maxOtherDigestLen = 128
|
const maxOtherDigestLen = 128
|
||||||
|
|
||||||
|
@ -419,6 +437,28 @@ type otherDigest struct {
|
||||||
func (d otherDigest) digestName() string { return d.name }
|
func (d otherDigest) digestName() string { return d.name }
|
||||||
func (d otherDigest) bytes() []byte { return d.sum[:d.sumLen] }
|
func (d otherDigest) bytes() []byte { return d.sum[:d.sumLen] }
|
||||||
func (d otherDigest) newHash() hash.Hash { return nil }
|
func (d otherDigest) newHash() hash.Hash { return nil }
|
||||||
|
func (d otherDigest) equalString(s string) bool {
|
||||||
|
wantLen := len(d.name) + len("-") + 2*d.sumLen
|
||||||
|
if d.odd {
|
||||||
|
wantLen--
|
||||||
|
}
|
||||||
|
if len(s) != wantLen || !strings.HasPrefix(s, d.name) || s[len(d.name)] != '-' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s = s[len(d.name)+1:]
|
||||||
|
for i, b := range d.sum[:d.sumLen] {
|
||||||
|
if s[i*2] != hexDigit[b>>4] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if i == d.sumLen-1 && d.odd {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s[i*2+1] != hexDigit[b&0xf] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
var sha1Meta = &digestMeta{
|
var sha1Meta = &digestMeta{
|
||||||
ctor: sha1FromBinary,
|
ctor: sha1FromBinary,
|
||||||
|
|
|
@ -283,3 +283,54 @@ func TestJSONMarshalSized(t *testing.T) {
|
||||||
t.Fatalf("got %q, want %q", g, e)
|
t.Fatalf("got %q, want %q", g, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var equalStringTests = []struct {
|
||||||
|
ref Ref
|
||||||
|
str string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659", true},
|
||||||
|
// last digit wrong:
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9ed9658", false},
|
||||||
|
// second to last digit wrong:
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-ce284c167558a9ef22df04390c87a6d0c9ed9669", false},
|
||||||
|
// hyphen wrong:
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1xce284c167558a9ef22df04390c87a6d0c9ed9659", false},
|
||||||
|
// truncated:
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1-", false},
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha1", false},
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "", false},
|
||||||
|
// right length, wrong hash:
|
||||||
|
{MustParse("sha1-ce284c167558a9ef22df04390c87a6d0c9ed9659"), "sha2-ce284c167558a9ef22df04390c87a6d0c9ed9659", false},
|
||||||
|
|
||||||
|
// Other hashes:
|
||||||
|
{MustParse("foo-cafe"), "foo-cafe", true},
|
||||||
|
{MustParse("foo-caf"), "foo-caf", true},
|
||||||
|
|
||||||
|
{MustParse("foo-cafe"), "foo-beef", false},
|
||||||
|
{MustParse("foo-cafe"), "bar-cafe", false},
|
||||||
|
{MustParse("foo-cafe"), "fooxbeef", false},
|
||||||
|
{MustParse("foo-caf"), "foo-cae", false},
|
||||||
|
{MustParse("foo-caf"), "foo-ca", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualString(t *testing.T) {
|
||||||
|
for _, tt := range equalStringTests {
|
||||||
|
got := tt.ref.EqualString(tt.str)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("ref %q EqualString(%q) = %v; want %v", tt.ref, tt.str, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEqualString(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, tt := range equalStringTests {
|
||||||
|
got := tt.ref.EqualString(tt.str)
|
||||||
|
if got != tt.want {
|
||||||
|
b.Fatalf("ref %q EqualString(%q) = %v; want %v", tt.ref, tt.str, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue