aboutsummaryrefslogtreecommitdiff
path: root/objectstore/loose
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-04 08:26:56 +0800
committerGravatar Runxi Yu2026-03-04 08:59:53 +0800
commitab7501be34032fb9e5c48726a68ae90a917af9eb (patch)
tree20d005647569befea8133e953c3270e8fd2a2a5b /objectstore/loose
parent*: gofumpt (diff)
signatureNo signature
*: Lint
Diffstat (limited to 'objectstore/loose')
-rw-r--r--objectstore/loose/helpers_test.go15
-rw-r--r--objectstore/loose/parse.go6
-rw-r--r--objectstore/loose/paths.go5
-rw-r--r--objectstore/loose/read_bytes.go5
-rw-r--r--objectstore/loose/read_header.go3
-rw-r--r--objectstore/loose/read_reader.go8
-rw-r--r--objectstore/loose/read_size.go1
-rw-r--r--objectstore/loose/read_test.go43
-rw-r--r--objectstore/loose/store.go1
-rw-r--r--objectstore/loose/write_reader.go17
-rw-r--r--objectstore/loose/write_test.go20
-rw-r--r--objectstore/loose/write_writer.go57
12 files changed, 155 insertions, 26 deletions
diff --git a/objectstore/loose/helpers_test.go b/objectstore/loose/helpers_test.go
index 972059e0..4b0bb60e 100644
--- a/objectstore/loose/helpers_test.go
+++ b/objectstore/loose/helpers_test.go
@@ -15,30 +15,39 @@ import (
func openLooseStore(t *testing.T, repoPath string, algo objectid.Algorithm) *loose.Store {
t.Helper()
+
objectsPath := filepath.Join(repoPath, "objects")
+
root, err := os.OpenRoot(objectsPath)
if err != nil {
t.Fatalf("OpenRoot(%q): %v", objectsPath, err)
}
+
t.Cleanup(func() { _ = root.Close() })
store, err := loose.New(root, algo)
if err != nil {
t.Fatalf("loose.New: %v", err)
}
+
return store
}
func mustReadAllAndClose(t *testing.T, reader io.ReadCloser) []byte {
t.Helper()
+
data, err := io.ReadAll(reader)
if err != nil {
_ = reader.Close()
+
t.Fatalf("ReadAll: %v", err)
}
- if err := reader.Close(); err != nil {
+
+ err = reader.Close()
+ if err != nil {
t.Fatalf("Close: %v", err)
}
+
return data
}
@@ -46,11 +55,14 @@ func expectedRawObject(t *testing.T, testRepo *testgit.TestRepo, id objectid.Obj
t.Helper()
typeName := testRepo.Run(t, "cat-file", "-t", id.String())
+
ty, ok := objecttype.ParseName(typeName)
if !ok {
t.Fatalf("ParseName(%q) failed", typeName)
}
+
body := testRepo.CatFile(t, typeName, id)
+
header, ok := objectheader.Encode(ty, int64(len(body)))
if !ok {
t.Fatalf("objectheader.Encode failed")
@@ -59,5 +71,6 @@ func expectedRawObject(t *testing.T, testRepo *testgit.TestRepo, id objectid.Obj
raw := make([]byte, len(header)+len(body))
copy(raw, header)
copy(raw[len(header):], body)
+
return ty, body, raw
}
diff --git a/objectstore/loose/parse.go b/objectstore/loose/parse.go
index 54bb2375..e88d7c6c 100644
--- a/objectstore/loose/parse.go
+++ b/objectstore/loose/parse.go
@@ -17,7 +17,9 @@ func decodeAll(file *os.File) ([]byte, error) {
if err != nil {
return nil, err
}
+
defer func() { _ = zr.Close() }()
+
return io.ReadAll(zr)
}
@@ -27,10 +29,12 @@ func parseRaw(raw []byte) (objecttype.Type, []byte, error) {
if !ok {
return objecttype.TypeInvalid, nil, errors.New("objectstore/loose: malformed object header")
}
+
content := raw[headerLen:]
if int64(len(content)) != size {
return objecttype.TypeInvalid, nil, errors.New("objectstore/loose: object header size/content mismatch")
}
+
return ty, content, nil
}
@@ -41,9 +45,11 @@ func readHeader(br *bufio.Reader) ([]byte, objecttype.Type, int64, error) {
if err != nil {
return nil, objecttype.TypeInvalid, 0, err
}
+
ty, size, _, ok := objectheader.Parse(header)
if !ok {
return nil, objecttype.TypeInvalid, 0, errors.New("objectstore/loose: malformed object header")
}
+
return header, ty, size, nil
}
diff --git a/objectstore/loose/paths.go b/objectstore/loose/paths.go
index 04730bd3..e8020d72 100644
--- a/objectstore/loose/paths.go
+++ b/objectstore/loose/paths.go
@@ -16,7 +16,9 @@ func (store *Store) objectPath(id objectid.ObjectID) (string, error) {
if id.Algorithm() != store.algo {
return "", fmt.Errorf("objectstore/loose: object id algorithm mismatch: got %s want %s", id.Algorithm(), store.algo)
}
+
hex := id.String()
+
return filepath.Join(hex[:2], hex[2:]), nil
}
@@ -27,12 +29,15 @@ func (store *Store) openObject(id objectid.ObjectID) (*os.File, error) {
if err != nil {
return nil, err
}
+
file, err := store.root.Open(relPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, objectstore.ErrObjectNotFound
}
+
return nil, err
}
+
return file, nil
}
diff --git a/objectstore/loose/read_bytes.go b/objectstore/loose/read_bytes.go
index 2f7c24bc..78e1009e 100644
--- a/objectstore/loose/read_bytes.go
+++ b/objectstore/loose/read_bytes.go
@@ -12,16 +12,19 @@ func (store *Store) readBytesParsed(id objectid.ObjectID) ([]byte, objecttype.Ty
if err != nil {
return nil, objecttype.TypeInvalid, nil, err
}
+
defer func() { _ = file.Close() }()
raw, err := decodeAll(file)
if err != nil {
return nil, objecttype.TypeInvalid, nil, err
}
+
ty, content, err := parseRaw(raw)
if err != nil {
return nil, objecttype.TypeInvalid, nil, err
}
+
return raw, ty, content, nil
}
@@ -31,6 +34,7 @@ func (store *Store) ReadBytesFull(id objectid.ObjectID) ([]byte, error) {
if err != nil {
return nil, err
}
+
return raw, nil
}
@@ -40,5 +44,6 @@ func (store *Store) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []b
if err != nil {
return objecttype.TypeInvalid, nil, err
}
+
return ty, content, nil
}
diff --git a/objectstore/loose/read_header.go b/objectstore/loose/read_header.go
index ce76600e..abfb1a02 100644
--- a/objectstore/loose/read_header.go
+++ b/objectstore/loose/read_header.go
@@ -14,17 +14,20 @@ func (store *Store) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, er
if err != nil {
return objecttype.TypeInvalid, 0, err
}
+
defer func() { _ = file.Close() }()
zr, err := zlib.NewReader(file)
if err != nil {
return objecttype.TypeInvalid, 0, err
}
+
defer func() { _ = zr.Close() }()
_, ty, size, err := readHeader(bufio.NewReader(zr))
if err != nil {
return objecttype.TypeInvalid, 0, err
}
+
return ty, size, nil
}
diff --git a/objectstore/loose/read_reader.go b/objectstore/loose/read_reader.go
index 6a377ba3..a0a51cc1 100644
--- a/objectstore/loose/read_reader.go
+++ b/objectstore/loose/read_reader.go
@@ -29,6 +29,7 @@ func (reader *objectReader) Read(dst []byte) (int, error) {
func (reader *objectReader) Close() error {
errZlib := reader.zr.Close()
errFile := reader.file.Close()
+
return errors.Join(errZlib, errFile)
}
@@ -39,11 +40,14 @@ func (store *Store) openInflated(id objectid.ObjectID) (*os.File, io.ReadCloser,
if err != nil {
return nil, nil, err
}
+
zr, err := zlib.NewReader(file)
if err != nil {
_ = file.Close()
+
return nil, nil, err
}
+
return file, zr, nil
}
@@ -56,10 +60,12 @@ func (store *Store) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error)
}
br := bufio.NewReader(zr)
+
header, _, size, err := readHeader(br)
if err != nil {
_ = zr.Close()
_ = file.Close()
+
return nil, err
}
@@ -82,10 +88,12 @@ func (store *Store) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, in
}
br := bufio.NewReader(zr)
+
_, ty, size, err := readHeader(br)
if err != nil {
_ = zr.Close()
_ = file.Close()
+
return objecttype.TypeInvalid, 0, nil, err
}
diff --git a/objectstore/loose/read_size.go b/objectstore/loose/read_size.go
index 45f1f0fe..2a1eaec9 100644
--- a/objectstore/loose/read_size.go
+++ b/objectstore/loose/read_size.go
@@ -5,5 +5,6 @@ import "codeberg.org/lindenii/furgit/objectid"
// ReadSize reads an object's declared content length.
func (store *Store) ReadSize(id objectid.ObjectID) (int64, error) {
_, size, err := store.ReadHeader(id)
+
return size, err
}
diff --git a/objectstore/loose/read_test.go b/objectstore/loose/read_test.go
index d8166c9e..1efc1682 100644
--- a/objectstore/loose/read_test.go
+++ b/objectstore/loose/read_test.go
@@ -41,6 +41,7 @@ func TestLooseStoreReadAgainstGit(t *testing.T) {
if err != nil {
t.Fatalf("ReadBytesFull: %v", err)
}
+
if !bytes.Equal(gotRaw, wantRaw) {
t.Fatalf("ReadBytesFull mismatch")
}
@@ -49,9 +50,11 @@ func TestLooseStoreReadAgainstGit(t *testing.T) {
if err != nil {
t.Fatalf("ReadBytesContent: %v", err)
}
+
if gotType != wantType {
t.Fatalf("ReadBytesContent type = %v, want %v", gotType, wantType)
}
+
if !bytes.Equal(gotBody, wantBody) {
t.Fatalf("ReadBytesContent body mismatch")
}
@@ -60,9 +63,11 @@ func TestLooseStoreReadAgainstGit(t *testing.T) {
if err != nil {
t.Fatalf("ReadHeader: %v", err)
}
+
if headType != wantType {
t.Fatalf("ReadHeader type = %v, want %v", headType, wantType)
}
+
if headSize != int64(len(wantBody)) {
t.Fatalf("ReadHeader size = %d, want %d", headSize, len(wantBody))
}
@@ -71,7 +76,9 @@ func TestLooseStoreReadAgainstGit(t *testing.T) {
if err != nil {
t.Fatalf("ReadReaderFull: %v", err)
}
- if got := mustReadAllAndClose(t, fullReader); !bytes.Equal(got, wantRaw) {
+
+ got := mustReadAllAndClose(t, fullReader)
+ if !bytes.Equal(got, wantRaw) {
t.Fatalf("ReadReaderFull stream mismatch")
}
@@ -79,13 +86,17 @@ func TestLooseStoreReadAgainstGit(t *testing.T) {
if err != nil {
t.Fatalf("ReadReaderContent: %v", err)
}
+
if contentType != wantType {
t.Fatalf("ReadReaderContent type = %v, want %v", contentType, wantType)
}
+
if contentSize != int64(len(wantBody)) {
t.Fatalf("ReadReaderContent size = %d, want %d", contentSize, len(wantBody))
}
- if got := mustReadAllAndClose(t, contentReader); !bytes.Equal(got, wantBody) {
+
+ got = mustReadAllAndClose(t, contentReader)
+ if !bytes.Equal(got, wantBody) {
t.Fatalf("ReadReaderContent stream mismatch")
}
})
@@ -104,19 +115,28 @@ func TestLooseStoreErrors(t *testing.T) {
t.Fatalf("ParseHex(notFoundID): %v", err)
}
- if _, err := store.ReadBytesFull(notFoundID); !errors.Is(err, objectstore.ErrObjectNotFound) {
+ _, err = store.ReadBytesFull(notFoundID)
+ if !errors.Is(err, objectstore.ErrObjectNotFound) {
t.Fatalf("ReadBytesFull not-found error = %v", err)
}
- if _, _, err := store.ReadBytesContent(notFoundID); !errors.Is(err, objectstore.ErrObjectNotFound) {
+
+ _, _, err = store.ReadBytesContent(notFoundID)
+ if !errors.Is(err, objectstore.ErrObjectNotFound) {
t.Fatalf("ReadBytesContent not-found error = %v", err)
}
- if _, err := store.ReadReaderFull(notFoundID); !errors.Is(err, objectstore.ErrObjectNotFound) {
+
+ _, err = store.ReadReaderFull(notFoundID)
+ if !errors.Is(err, objectstore.ErrObjectNotFound) {
t.Fatalf("ReadReaderFull not-found error = %v", err)
}
- if _, _, _, err := store.ReadReaderContent(notFoundID); !errors.Is(err, objectstore.ErrObjectNotFound) {
+
+ _, _, _, err = store.ReadReaderContent(notFoundID)
+ if !errors.Is(err, objectstore.ErrObjectNotFound) {
t.Fatalf("ReadReaderContent not-found error = %v", err)
}
- if _, _, err := store.ReadHeader(notFoundID); !errors.Is(err, objectstore.ErrObjectNotFound) {
+
+ _, _, err = store.ReadHeader(notFoundID)
+ if !errors.Is(err, objectstore.ErrObjectNotFound) {
t.Fatalf("ReadHeader not-found error = %v", err)
}
@@ -126,12 +146,14 @@ func TestLooseStoreErrors(t *testing.T) {
} else {
otherAlgo = objectid.AlgorithmSHA1
}
+
otherID, err := objectid.ParseHex(otherAlgo, strings.Repeat("1", otherAlgo.HexLen()))
if err != nil {
t.Fatalf("ParseHex(otherID): %v", err)
}
- if _, err := store.ReadBytesFull(otherID); err == nil || !strings.Contains(err.Error(), "algorithm mismatch") {
+ _, err = store.ReadBytesFull(otherID)
+ if err == nil || !strings.Contains(err.Error(), "algorithm mismatch") {
t.Fatalf("ReadBytesFull algorithm-mismatch error = %v", err)
}
})
@@ -139,13 +161,16 @@ func TestLooseStoreErrors(t *testing.T) {
func TestLooseStoreNewValidation(t *testing.T) {
t.Parallel()
+
root, err := os.OpenRoot(t.TempDir())
if err != nil {
t.Fatalf("OpenRoot: %v", err)
}
+
defer func() { _ = root.Close() }()
- if _, err := loose.New(root, objectid.AlgorithmUnknown); err == nil {
+ _, err = loose.New(root, objectid.AlgorithmUnknown)
+ if err == nil {
t.Fatalf("loose.New(root, unknown) expected error")
}
}
diff --git a/objectstore/loose/store.go b/objectstore/loose/store.go
index 05459a6c..c3ae989c 100644
--- a/objectstore/loose/store.go
+++ b/objectstore/loose/store.go
@@ -24,6 +24,7 @@ func New(root *os.Root, algo objectid.Algorithm) (*Store, error) {
if algo.Size() == 0 {
return nil, objectid.ErrInvalidAlgorithm
}
+
return &Store{
root: root,
algo: algo,
diff --git a/objectstore/loose/write_reader.go b/objectstore/loose/write_reader.go
index b2329f02..9dbf3818 100644
--- a/objectstore/loose/write_reader.go
+++ b/objectstore/loose/write_reader.go
@@ -27,12 +27,15 @@ func (store *Store) WriteReaderContent(ty objecttype.Type, size int64, src io.Re
if err != nil {
return objectid.ObjectID{}, err
}
+
writer.headerDone = true
writer.expectedContentLeft = size
- if err := writer.writeRawChunk(header); err != nil {
+ err = writer.writeRawChunk(header)
+ if err != nil {
_ = writer.Close()
_ = store.root.Remove(writer.tmpRelPath)
+
return objectid.ObjectID{}, err
}
@@ -46,25 +49,33 @@ func (store *Store) WriteReaderFull(src io.Reader) (objectid.ObjectID, error) {
if err != nil {
return objectid.ObjectID{}, err
}
+
return writeReaderIntoStreamWriter(writer, src)
}
// writeReaderIntoStreamWriter copies src into writer and publishes the object.
func writeReaderIntoStreamWriter(writer *streamWriter, src io.Reader) (objectid.ObjectID, error) {
- if _, err := io.Copy(writer, src); err != nil {
+ _, err := io.Copy(writer, src)
+ if err != nil {
_ = writer.Close()
_ = writer.store.root.Remove(writer.tmpRelPath)
+
return objectid.ObjectID{}, err
}
- if err := writer.Close(); err != nil {
+
+ err = writer.Close()
+ if err != nil {
_ = writer.store.root.Remove(writer.tmpRelPath)
+
return objectid.ObjectID{}, err
}
id, err := writer.finalize()
if err != nil {
_ = writer.store.root.Remove(writer.tmpRelPath)
+
return objectid.ObjectID{}, err
}
+
return id, nil
}
diff --git a/objectstore/loose/write_test.go b/objectstore/loose/write_test.go
index cceabe5a..5604c5b0 100644
--- a/objectstore/loose/write_test.go
+++ b/objectstore/loose/write_test.go
@@ -18,6 +18,7 @@ func TestLooseStoreWriteReaderContentAgainstGit(t *testing.T) {
content := []byte("written-by-content-reader\n")
expectedHex := testRepo.RunInput(t, content, "hash-object", "-t", "blob", "--stdin")
+
expectedID, err := objectid.ParseHex(algo, expectedHex)
if err != nil {
t.Fatalf("ParseHex(expected): %v", err)
@@ -27,6 +28,7 @@ func TestLooseStoreWriteReaderContentAgainstGit(t *testing.T) {
if err != nil {
t.Fatalf("WriteReaderContent: %v", err)
}
+
if writtenID != expectedID {
t.Fatalf("WriteReaderContent id = %s, want %s", writtenID, expectedID)
}
@@ -41,6 +43,7 @@ func TestLooseStoreWriteReaderContentAgainstGit(t *testing.T) {
if err != nil {
t.Fatalf("WriteReaderContent second: %v", err)
}
+
if writtenID2 != expectedID {
t.Fatalf("WriteReaderContent second id = %s, want %s", writtenID2, expectedID)
}
@@ -54,19 +57,23 @@ func TestLooseStoreWriteReaderFullAgainstGit(t *testing.T) {
store := openLooseStore(t, testRepo.Dir(), algo)
body := []byte("full-reader-body\n")
+
header, ok := objectheader.Encode(objecttype.TypeBlob, int64(len(body)))
if !ok {
t.Fatalf("objectheader.Encode failed")
}
+
raw := make([]byte, len(header)+len(body))
copy(raw, header)
copy(raw[len(header):], body)
wantID := algo.Sum(raw)
+
gotID, err := store.WriteReaderFull(bytes.NewReader(raw))
if err != nil {
t.Fatalf("WriteReaderFull: %v", err)
}
+
if gotID != wantID {
t.Fatalf("WriteReaderFull id = %s, want %s", gotID, wantID)
}
@@ -86,7 +93,8 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
store := openLooseStore(t, testRepo.Dir(), algo)
- if _, err := store.WriteReaderContent(objecttype.TypeBlob, 1, bytes.NewReader([]byte("hello"))); err == nil {
+ _, err := store.WriteReaderContent(objecttype.TypeBlob, 1, bytes.NewReader([]byte("hello")))
+ if err == nil {
t.Fatalf("expected error after overflow")
}
})
@@ -96,7 +104,8 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
store := openLooseStore(t, testRepo.Dir(), algo)
- if _, err := store.WriteReaderContent(objecttype.TypeBlob, 5, bytes.NewReader([]byte("x"))); err == nil {
+ _, err := store.WriteReaderContent(objecttype.TypeBlob, 5, bytes.NewReader([]byte("x")))
+ if err == nil {
t.Fatalf("expected error for short content")
}
})
@@ -106,7 +115,8 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
store := openLooseStore(t, testRepo.Dir(), algo)
- if _, err := store.WriteReaderFull(bytes.NewReader([]byte("not-a-header"))); err == nil {
+ _, err := store.WriteReaderFull(bytes.NewReader([]byte("not-a-header")))
+ if err == nil {
t.Fatalf("expected error for malformed header")
}
})
@@ -117,7 +127,9 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
store := openLooseStore(t, testRepo.Dir(), algo)
raw := []byte("blob 1\x00hello")
- if _, err := store.WriteReaderFull(bytes.NewReader(raw)); err == nil {
+
+ _, err := store.WriteReaderFull(bytes.NewReader(raw))
+ if err == nil {
t.Fatalf("expected error after mismatch")
}
})
diff --git a/objectstore/loose/write_writer.go b/objectstore/loose/write_writer.go
index c075f2ba..a0f24f2b 100644
--- a/objectstore/loose/write_writer.go
+++ b/objectstore/loose/write_writer.go
@@ -76,23 +76,28 @@ func (writer *streamWriter) Write(src []byte) (int, error) {
if writer.finalized {
return 0, errors.New("objectstore/loose: write after finalize")
}
+
if writer.closed {
return 0, errors.New("objectstore/loose: write after close")
}
if writer.fullMode {
- if err := writer.acceptFull(src); err != nil {
+ err := writer.acceptFull(src)
+ if err != nil {
return 0, err
}
} else {
- if err := writer.acceptContent(int64(len(src))); err != nil {
+ err := writer.acceptContent(int64(len(src)))
+ if err != nil {
return 0, err
}
}
- if err := writer.writeRawChunk(src); err != nil {
+ err := writer.writeRawChunk(src)
+ if err != nil {
return 0, err
}
+
return len(src), nil
}
@@ -102,12 +107,14 @@ func (writer *streamWriter) Close() error {
if writer.closed {
return nil
}
+
writer.closed = true
errZlib := writer.zw.Close()
errSync := writer.file.Sync()
errFile := writer.file.Close()
writer.file = nil
+
return errors.Join(errZlib, errSync, errFile)
}
@@ -118,84 +125,107 @@ func (writer *streamWriter) finalize() (objectid.ObjectID, error) {
if writer.finalized {
return writer.finalID, writer.finalErr
}
+
writer.finalized = true
var zero objectid.ObjectID
if !writer.closed {
- if err := writer.Close(); err != nil {
+ err := writer.Close()
+ if err != nil {
writer.finalErr = err
+
return zero, err
}
}
if writer.fullMode && !writer.headerDone {
writer.finalErr = errors.New("objectstore/loose: missing full object header")
+
return zero, writer.finalErr
}
+
if writer.expectedContentLeft != 0 {
writer.finalErr = errors.New("objectstore/loose: object content shorter than declared size")
+
return zero, writer.finalErr
}
idBytes := writer.hash.Sum(nil)
+
id, err := objectid.FromBytes(writer.store.algo, idBytes)
if err != nil {
writer.finalErr = err
+
return zero, err
}
relPath, err := writer.store.objectPath(id)
if err != nil {
writer.finalErr = err
+
return zero, err
}
dir := filepath.Dir(relPath)
- if err := writer.store.root.MkdirAll(dir, 0o755); err != nil {
+
+ err = writer.store.root.MkdirAll(dir, 0o755)
+ if err != nil {
writer.finalErr = err
+
return zero, err
}
cleanup := true
+
defer func() {
if cleanup {
_ = writer.store.root.Remove(writer.tmpRelPath)
}
}()
- if err := writer.store.root.Link(writer.tmpRelPath, relPath); err != nil {
+ err = writer.store.root.Link(writer.tmpRelPath, relPath)
+ if err != nil {
if errors.Is(err, fs.ErrExist) {
writer.finalID = id
cleanup = false
_ = writer.store.root.Remove(writer.tmpRelPath)
+
return id, nil
}
+
writer.finalErr = err
+
return zero, err
}
writer.finalID = id
cleanup = false
+
return id, nil
}
// acceptFull validates and accounts raw full-object input.
func (writer *streamWriter) acceptFull(src []byte) error {
if !writer.headerDone {
- if nul := bytes.IndexByte(src, 0); nul >= 0 {
+ nul := bytes.IndexByte(src, 0)
+ if nul >= 0 {
headerChunkLen := nul + 1
writer.headerBuf = append(writer.headerBuf, src[:headerChunkLen]...)
+
_, size, _, ok := objectheader.Parse(writer.headerBuf)
if !ok {
return errors.New("objectstore/loose: malformed object header")
}
+
writer.headerDone = true
writer.expectedContentLeft = size
+
return writer.acceptContent(int64(len(src) - headerChunkLen))
}
writer.headerBuf = append(writer.headerBuf, src...)
+
return nil
}
@@ -207,18 +237,24 @@ func (writer *streamWriter) acceptContent(n int64) error {
if n > writer.expectedContentLeft {
return errors.New("objectstore/loose: object content exceeds declared size")
}
+
writer.expectedContentLeft -= n
+
return nil
}
// writeRawChunk forwards raw bytes to the hash and deflate pipeline.
func (writer *streamWriter) writeRawChunk(src []byte) error {
- if _, err := writer.hash.Write(src); err != nil {
+ _, err := writer.hash.Write(src)
+ if err != nil {
return err
}
- if _, err := writer.zw.Write(src); err != nil {
+
+ _, err = writer.zw.Write(src)
+ if err != nil {
return err
}
+
return nil
}
@@ -227,13 +263,16 @@ func (writer *streamWriter) writeRawChunk(src []byte) error {
func (store *Store) createTempObjectFile(dir string) (string, *os.File, error) {
for range 16 {
relPath := filepath.Join(dir, tempObjectFilePrefix+rand.Text())
+
file, err := store.root.OpenFile(relPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
if err == nil {
return relPath, file, nil
}
+
if errors.Is(err, fs.ErrExist) {
continue
}
+
return "", nil, err
}