shithub: furgit

Download patch

ref: 53e166913d9e76b82aa53361b251390a2c0726bd
parent: 93b94c30aefe0eebb5acb5e36df5b950dec70a94
author: Runxi Yu <me@runxiyu.org>
date: Sat Feb 21 10:18:32 EST 2026

repository: Add passthrough ReadStored*; add ref convenience funcs

--- /dev/null
+++ b/repository/read_stored_passthrough.go
@@ -1,0 +1,38 @@
+package repository
+
+import (
+	"io"
+
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/objecttype"
+)
+
+// ReadStoredHeader reads an object's type and declared content length.
+func (repo *Repository) ReadStoredHeader(id objectid.ObjectID) (objecttype.Type, int64, error) {
+	return repo.objects.ReadHeader(id)
+}
+
+// ReadStoredBytesFull reads a full serialized object as "type size\\x00content".
+func (repo *Repository) ReadStoredBytesFull(id objectid.ObjectID) ([]byte, error) {
+	return repo.objects.ReadBytesFull(id)
+}
+
+// ReadStoredBytesContent reads an object's type and content bytes.
+func (repo *Repository) ReadStoredBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
+	return repo.objects.ReadBytesContent(id)
+}
+
+// ReadStoredReaderFull reads a full serialized object stream.
+//
+// Caller must close the returned reader.
+func (repo *Repository) ReadStoredReaderFull(id objectid.ObjectID) (io.ReadCloser, error) {
+	return repo.objects.ReadReaderFull(id)
+}
+
+// ReadStoredReaderContent reads an object's type, declared content length, and
+// content stream.
+//
+// Caller must close the returned reader.
+func (repo *Repository) ReadStoredReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) {
+	return repo.objects.ReadReaderContent(id)
+}
--- /dev/null
+++ b/repository/read_stored_passthrough_test.go
@@ -1,0 +1,100 @@
+package repository_test
+
+import (
+	"bytes"
+	"io"
+	"testing"
+
+	"codeberg.org/lindenii/furgit/internal/testgit"
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/objecttype"
+	"codeberg.org/lindenii/furgit/repository"
+)
+
+func TestReadStoredPassThroughs(t *testing.T) {
+	t.Parallel()
+
+	testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+		repoHarness := testgit.NewRepo(t, testgit.RepoOptions{
+			ObjectFormat: algo,
+			Bare:         true,
+			RefFormat:    "files",
+		})
+
+		_, _, commitID := repoHarness.MakeCommit(t, "pass-through")
+
+		repo, err := repository.Open(repoHarness.Dir())
+		if err != nil {
+			t.Fatalf("repository.Open: %v", err)
+		}
+		defer func() { _ = repo.Close() }()
+
+		headerTy, headerSize, err := repo.ReadStoredHeader(commitID)
+		if err != nil {
+			t.Fatalf("ReadStoredHeader: %v", err)
+		}
+		if headerTy != objecttype.TypeCommit {
+			t.Fatalf("ReadStoredHeader type = %v, want %v", headerTy, objecttype.TypeCommit)
+		}
+		if headerSize <= 0 {
+			t.Fatalf("ReadStoredHeader size = %d, want > 0", headerSize)
+		}
+
+		full, err := repo.ReadStoredBytesFull(commitID)
+		if err != nil {
+			t.Fatalf("ReadStoredBytesFull: %v", err)
+		}
+		if len(full) == 0 {
+			t.Fatalf("ReadStoredBytesFull returned empty payload")
+		}
+
+		contentTy, content, err := repo.ReadStoredBytesContent(commitID)
+		if err != nil {
+			t.Fatalf("ReadStoredBytesContent: %v", err)
+		}
+		if contentTy != objecttype.TypeCommit {
+			t.Fatalf("ReadStoredBytesContent type = %v, want %v", contentTy, objecttype.TypeCommit)
+		}
+		if len(content) == 0 {
+			t.Fatalf("ReadStoredBytesContent returned empty content")
+		}
+
+		fullReader, err := repo.ReadStoredReaderFull(commitID)
+		if err != nil {
+			t.Fatalf("ReadStoredReaderFull: %v", err)
+		}
+		fullReaderBytes, readErr := io.ReadAll(fullReader)
+		closeErr := fullReader.Close()
+		if readErr != nil {
+			t.Fatalf("ReadStoredReaderFull read: %v", readErr)
+		}
+		if closeErr != nil {
+			t.Fatalf("ReadStoredReaderFull close: %v", closeErr)
+		}
+		if !bytes.Equal(fullReaderBytes, full) {
+			t.Fatalf("ReadStoredReaderFull bytes mismatch against ReadStoredBytesFull")
+		}
+
+		readerTy, readerSize, contentReader, err := repo.ReadStoredReaderContent(commitID)
+		if err != nil {
+			t.Fatalf("ReadStoredReaderContent: %v", err)
+		}
+		if readerTy != objecttype.TypeCommit {
+			t.Fatalf("ReadStoredReaderContent type = %v, want %v", readerTy, objecttype.TypeCommit)
+		}
+		if readerSize != int64(len(content)) {
+			t.Fatalf("ReadStoredReaderContent size = %d, want %d", readerSize, len(content))
+		}
+		readerContentBytes, readErr := io.ReadAll(contentReader)
+		closeErr = contentReader.Close()
+		if readErr != nil {
+			t.Fatalf("ReadStoredReaderContent read: %v", readErr)
+		}
+		if closeErr != nil {
+			t.Fatalf("ReadStoredReaderContent close: %v", closeErr)
+		}
+		if !bytes.Equal(readerContentBytes, content) {
+			t.Fatalf("ReadStoredReaderContent bytes mismatch against ReadStoredBytesContent")
+		}
+	})
+}
--- /dev/null
+++ b/repository/refs.go
@@ -1,0 +1,25 @@
+package repository
+
+import (
+	"codeberg.org/lindenii/furgit/ref"
+)
+
+// ResolveRef resolves one reference name to symbolic or detached form.
+func (repo *Repository) ResolveRef(name string) (ref.Ref, error) {
+	return repo.refs.Resolve(name)
+}
+
+// ResolveRefFully resolves one reference name to detached form.
+func (repo *Repository) ResolveRefFully(name string) (ref.Detached, error) {
+	return repo.refs.ResolveFully(name)
+}
+
+// ListRefs lists references matching pattern.
+func (repo *Repository) ListRefs(pattern string) ([]ref.Ref, error) {
+	return repo.refs.List(pattern)
+}
+
+// ShortenRef returns the shortest unambiguous shorthand for a full reference name.
+func (repo *Repository) ShortenRef(name string) (string, error) {
+	return repo.refs.Shorten(name)
+}
--- /dev/null
+++ b/repository/refs_test.go
@@ -1,0 +1,96 @@
+package repository_test
+
+import (
+	"strings"
+	"testing"
+
+	"codeberg.org/lindenii/furgit/internal/testgit"
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/ref"
+	"codeberg.org/lindenii/furgit/repository"
+)
+
+func TestRefConvenienceMethods(t *testing.T) {
+	t.Parallel()
+
+	testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+		repoHarness := testgit.NewRepo(t, testgit.RepoOptions{
+			ObjectFormat: algo,
+			Bare:         true,
+			RefFormat:    "files",
+		})
+
+		_, _, commitID := repoHarness.MakeCommit(t, "refs wrappers")
+		repoHarness.UpdateRef(t, "refs/heads/main", commitID)
+		repoHarness.SymbolicRef(t, "HEAD", "refs/heads/main")
+		repoHarness.UpdateRef(t, "refs/tags/v1", commitID)
+
+		repo, err := repository.Open(repoHarness.Dir())
+		if err != nil {
+			t.Fatalf("repository.Open: %v", err)
+		}
+		defer func() { _ = repo.Close() }()
+
+		resolved, err := repo.ResolveRef("HEAD")
+		if err != nil {
+			t.Fatalf("ResolveRef(HEAD): %v", err)
+		}
+		sym, ok := resolved.(ref.Symbolic)
+		if !ok {
+			t.Fatalf("ResolveRef(HEAD) type = %T, want ref.Symbolic", resolved)
+		}
+		if sym.Target != "refs/heads/main" {
+			t.Fatalf("ResolveRef(HEAD) target = %q, want %q", sym.Target, "refs/heads/main")
+		}
+
+		fully, err := repo.ResolveRefFully("HEAD")
+		if err != nil {
+			t.Fatalf("ResolveRefFully(HEAD): %v", err)
+		}
+		if fully.ID != commitID {
+			t.Fatalf("ResolveRefFully(HEAD) id = %s, want %s", fully.ID, commitID)
+		}
+
+		refs, err := repo.ListRefs("refs/*/*")
+		if err != nil {
+			t.Fatalf("ListRefs: %v", err)
+		}
+		if len(refs) < 2 {
+			t.Fatalf("ListRefs returned %d refs, want >= 2", len(refs))
+		}
+
+		short, err := repo.ShortenRef("refs/heads/main")
+		if err != nil {
+			t.Fatalf("ShortenRef: %v", err)
+		}
+		if short != "heads/main" && short != "main" {
+			t.Fatalf("ShortenRef = %q, want %q or %q", short, "heads/main", "main")
+		}
+	})
+}
+
+func TestResolveRefErrorSurface(t *testing.T) {
+	t.Parallel()
+
+	testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+		repoHarness := testgit.NewRepo(t, testgit.RepoOptions{
+			ObjectFormat: algo,
+			Bare:         true,
+			RefFormat:    "files",
+		})
+
+		repo, err := repository.Open(repoHarness.Dir())
+		if err != nil {
+			t.Fatalf("repository.Open: %v", err)
+		}
+		defer func() { _ = repo.Close() }()
+
+		_, err = repo.ResolveRef("refs/heads/does-not-exist")
+		if err == nil {
+			t.Fatalf("ResolveRef missing: expected error")
+		}
+		if !strings.Contains(err.Error(), "not found") {
+			t.Fatalf("ResolveRef missing error = %v, want not found detail", err)
+		}
+	})
+}
--