shithub: furgit

Download patch

ref: 1fedf54fa79d5bdf67971d89924653e3bac7179e
parent: 83c03b605df05ee4c3b60f1aea0411a0636ca0b5
author: Runxi Yu <me@runxiyu.org>
date: Sat Feb 21 13:18:43 EST 2026

objectstore/{loose,packed}: Use iolimit

--- a/objectstore/loose/parse.go
+++ b/objectstore/loose/parse.go
@@ -34,16 +34,16 @@
 	return ty, content, nil
 }
 
-// readHeader reads and parses a loose object header from br.
-// br must be positioned at the start of decoded loose object bytes.
-func readHeader(br *bufio.Reader) (objecttype.Type, int64, error) {
+// readHeader reads and parses a loose object header from br, and returns
+// the raw header bytes including the trailing NUL.
+func readHeader(br *bufio.Reader) ([]byte, objecttype.Type, int64, error) {
 	header, err := br.ReadSlice(0)
 	if err != nil {
-		return objecttype.TypeInvalid, 0, err
+		return nil, objecttype.TypeInvalid, 0, err
 	}
 	ty, size, _, ok := objectheader.Parse(header)
 	if !ok {
-		return objecttype.TypeInvalid, 0, errors.New("objectstore/loose: malformed object header")
+		return nil, objecttype.TypeInvalid, 0, errors.New("objectstore/loose: malformed object header")
 	}
-	return ty, size, nil
+	return header, ty, size, nil
 }
--- a/objectstore/loose/read_header.go
+++ b/objectstore/loose/read_header.go
@@ -22,7 +22,7 @@
 	}
 	defer func() { _ = zr.Close() }()
 
-	ty, size, err := readHeader(bufio.NewReader(zr))
+	_, ty, size, err := readHeader(bufio.NewReader(zr))
 	if err != nil {
 		return objecttype.TypeInvalid, 0, err
 	}
--- a/objectstore/loose/read_reader.go
+++ b/objectstore/loose/read_reader.go
@@ -2,18 +2,19 @@
 
 import (
 	"bufio"
+	"bytes"
 	"compress/zlib"
 	"errors"
 	"io"
 	"os"
 
+	"codeberg.org/lindenii/furgit/internal/iolimit"
 	"codeberg.org/lindenii/furgit/objectid"
 	"codeberg.org/lindenii/furgit/objecttype"
 )
 
 type objectReader struct {
-	// reader is the stream exposed by Read. It may be the raw zlib reader
-	// (full object) or a buffered reader positioned at content bytes only.
+	// reader is the stream exposed by Read.
 	reader io.Reader
 	// file is the underlying loose object file and is closed by Close.
 	file *os.File
@@ -53,10 +54,22 @@
 	if err != nil {
 		return nil, err
 	}
+
+	br := bufio.NewReader(zr)
+	header, _, size, err := readHeader(br)
+	if err != nil {
+		_ = zr.Close()
+		_ = file.Close()
+		return nil, err
+	}
+
 	return &objectReader{
-		reader: zr,
-		file:   file,
-		zr:     zr,
+		reader: io.MultiReader(
+			bytes.NewReader(header),
+			iolimit.ExpectLengthReader(br, size),
+		),
+		file: file,
+		zr:   zr,
 	}, nil
 }
 
@@ -69,7 +82,7 @@
 	}
 
 	br := bufio.NewReader(zr)
-	ty, size, err := readHeader(br)
+	_, ty, size, err := readHeader(br)
 	if err != nil {
 		_ = zr.Close()
 		_ = file.Close()
@@ -77,7 +90,7 @@
 	}
 
 	return ty, size, &objectReader{
-		reader: br,
+		reader: iolimit.ExpectLengthReader(br, size),
 		file:   file,
 		zr:     zr,
 	}, nil
--- a/objectstore/packed/read_reader.go
+++ b/objectstore/packed/read_reader.go
@@ -5,6 +5,7 @@
 	"fmt"
 	"io"
 
+	"codeberg.org/lindenii/furgit/internal/iolimit"
 	"codeberg.org/lindenii/furgit/objectheader"
 	"codeberg.org/lindenii/furgit/objectid"
 	"codeberg.org/lindenii/furgit/objecttype"
@@ -45,7 +46,7 @@
 			return objecttype.TypeInvalid, 0, nil, err
 		}
 		return meta.ty, meta.size, &readCloser{
-			reader: io.LimitReader(zr, meta.size),
+			reader: iolimit.ExpectLengthReader(zr, meta.size),
 			closer: zr,
 		}, nil
 	}
@@ -80,7 +81,7 @@
 			return nil, err
 		}
 		return &readCloser{
-			reader: io.MultiReader(bytes.NewReader(header), io.LimitReader(zr, meta.size)),
+			reader: io.MultiReader(bytes.NewReader(header), iolimit.ExpectLengthReader(zr, meta.size)),
 			closer: zr,
 		}, nil
 	}
--