repository: move Blob, Blobs and PackedBlob to pack package

This removes them from the public interface. The latter now only
provides the PackBlob interface, without being bound to the type used
internally by the pack package.
This commit is contained in:
Michael Eischer
2026-06-13 18:58:37 +02:00
parent 35af104749
commit c062a78dcd
27 changed files with 373 additions and 345 deletions
+32
View File
@@ -0,0 +1,32 @@
package pack
import (
"fmt"
"github.com/restic/restic/internal/crypto"
"github.com/restic/restic/internal/restic"
)
// Blob is one part of a file or a tree with pack layout information.
type Blob struct {
restic.BlobHandle
Length uint
Offset uint
UncompressedLength uint
}
func (b Blob) String() string {
return fmt.Sprintf("<Blob (%v) %v, offset %v, length %v, uncompressed length %v>",
b.Type, b.ID.Str(), b.Offset, b.Length, b.UncompressedLength)
}
func (b Blob) DataLength() uint {
if b.UncompressedLength != 0 {
return b.UncompressedLength
}
return uint(crypto.PlaintextLength(int(b.Length)))
}
func (b Blob) IsCompressed() bool {
return b.UncompressedLength != 0
}
+15
View File
@@ -0,0 +1,15 @@
package pack
import (
"cmp"
"slices"
)
// Blobs is a list of blobs with pack layout information (offset, length, ...).
type Blobs []Blob
func (b Blobs) Sort() {
slices.SortFunc(b, func(a, b Blob) int {
return cmp.Compare(a.Offset, b.Offset)
})
}
+24
View File
@@ -0,0 +1,24 @@
package pack
import (
"testing"
rtest "github.com/restic/restic/internal/test"
)
func TestBlobsSort(t *testing.T) {
blobs := Blobs{
{Offset: 100},
{Offset: 0},
{Offset: 50},
}
blobs.Sort()
rtest.Equals(t, uint(0), blobs[0].Offset)
rtest.Equals(t, uint(50), blobs[1].Offset)
rtest.Equals(t, uint(100), blobs[2].Offset)
}
func TestBlobsSortNilSlice(t *testing.T) {
var blobs Blobs
blobs.Sort()
}
+11 -11
View File
@@ -21,7 +21,7 @@ var ErrBroken = errors.New("packer cannot be used after a write error")
// Packer is used to create a new Pack.
type Packer struct {
blobs restic.Blobs
blobs []Blob
bytes uint
k *crypto.Key
@@ -56,7 +56,7 @@ func (p *Packer) Add(t restic.BlobType, id restic.ID, data []byte, uncompressedL
return n, p.err
}
c := restic.Blob{
c := Blob{
BlobHandle: restic.BlobHandle{Type: t, ID: id},
Length: uint(n),
Offset: p.bytes,
@@ -129,7 +129,7 @@ func (p *Packer) Finalize() error {
return nil
}
func verifyHeader(k *crypto.Key, header []byte, expected restic.Blobs) error {
func verifyHeader(k *crypto.Key, header []byte, expected []Blob) error {
// do not offer a way to skip the pack header verification, as pack headers are usually small enough
// to not result in a significant performance impact
@@ -157,7 +157,7 @@ func (p *Packer) HeaderOverhead() int {
}
// makeHeader constructs the header for p.
func makeHeader(blobs restic.Blobs) ([]byte, error) {
func makeHeader(blobs []Blob) ([]byte, error) {
buf := make([]byte, 0, len(blobs)*int(entrySize))
for _, b := range blobs {
@@ -232,7 +232,7 @@ func (p *Packer) HeaderFull() bool {
}
// Blobs returns the slice of blobs that have been written.
func (p *Packer) Blobs() restic.Blobs {
func (p *Packer) Blobs() Blobs {
p.m.Lock()
defer p.m.Unlock()
@@ -348,7 +348,7 @@ func (e InvalidFileError) Error() string {
// List returns the list of entries found in a pack file and the length of the
// header (including header size and crypto overhead)
func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries restic.Blobs, hdrSize uint32, err error) {
func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries Blobs, hdrSize uint32, err error) {
buf, err := readHeader(rd, size)
if err != nil {
return nil, 0, err
@@ -367,7 +367,7 @@ func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries restic.Blobs, hdrS
}
// might over allocate a bit if all blobs have EntrySize but only by a few percent
entries = make(restic.Blobs, 0, uint(len(buf))/plainEntrySize)
entries = make(Blobs, 0, uint(len(buf))/plainEntrySize)
pos := uint(0)
for len(buf) > 0 {
@@ -385,7 +385,7 @@ func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries restic.Blobs, hdrS
return entries, hdrSize, nil
}
func parseHeaderEntry(p []byte) (b restic.Blob, size uint, err error) {
func parseHeaderEntry(p []byte) (b Blob, size uint, err error) {
l := uint(len(p))
size = plainEntrySize
if l < plainEntrySize {
@@ -427,7 +427,7 @@ func CalculateEntrySize(compressed bool) int {
return int(plainEntrySize)
}
func CalculateHeaderSize(blobs restic.Blobs) int {
func CalculateHeaderSize(blobs Blobs) int {
size := headerSize
for _, blob := range blobs {
size += CalculateEntrySize(blob.IsCompressed())
@@ -439,10 +439,10 @@ func CalculateHeaderSize(blobs restic.Blobs) int {
// If onlyHdr is set to true, only the size of the header is returned
// Note that this function only gives correct sizes, if there are no
// duplicates in the index.
func Size(ctx context.Context, mi restic.ListBlobser, onlyHdr bool) (map[restic.ID]int64, error) {
func Size(ctx context.Context, idx restic.ListBlobser, onlyHdr bool) (map[restic.ID]int64, error) {
packSize := make(map[restic.ID]int64)
err := mi.ListBlobs(ctx, func(blob restic.PackBlob) {
err := idx.ListBlobs(ctx, func(blob restic.PackBlob) {
packID := blob.PackID()
size, ok := packSize[packID]
if !ok {
@@ -182,7 +182,7 @@ func TestReadRecords(t *testing.T) {
func TestUnpackedVerification(t *testing.T) {
// create random keys
k := crypto.NewRandomKey()
blobs := restic.Blobs{
blobs := []Blob{
{
BlobHandle: restic.NewRandomBlobHandle(),
Length: 42,
+19
View File
@@ -0,0 +1,19 @@
package pack
import "github.com/restic/restic/internal/restic"
// PackedBlob is one index entry for a blob in a pack (may be duplicate across indexes).
type PackedBlob struct {
Pack restic.ID
Blob Blob
}
func (pb *PackedBlob) PackID() restic.ID { return pb.Pack }
func (pb *PackedBlob) Handle() restic.BlobHandle { return pb.Blob.BlobHandle }
func (pb *PackedBlob) CiphertextLength() uint { return pb.Blob.Length }
func (pb *PackedBlob) PlaintextLength() uint { return pb.Blob.DataLength() }
func (pb *PackedBlob) IsCompressed() bool { return pb.Blob.IsCompressed() }