shithub: furgit

Download patch

ref: 845cd640384ed25ce3c18ade9aae37de2ed4c5e0
parent: b3028bc65ac03b0bb4a88482519fb45700c675f3
author: Runxi Yu <me@runxiyu.org>
date: Wed Mar 4 07:52:53 EST 2026

refstore/chain: Split

--- a/refstore/chain/chain.go
+++ b/refstore/chain/chain.go
@@ -2,164 +2,9 @@
 // of backends.
 package chain
 
-import (
-	"errors"
-	"fmt"
+import "codeberg.org/lindenii/furgit/refstore"
 
-	"codeberg.org/lindenii/furgit/ref"
-	"codeberg.org/lindenii/furgit/refstore"
-)
-
 // Chain queries multiple reference stores in order.
 type Chain struct {
 	backends []refstore.Store
-}
-
-// New creates an ordered reference store chain.
-func New(backends ...refstore.Store) *Chain {
-	return &Chain{
-		backends: append([]refstore.Store(nil), backends...),
-	}
-}
-
-// Resolve resolves a reference from the first backend that has it.
-func (chain *Chain) Resolve(name string) (ref.Ref, error) {
-	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
-		resolved, err := backend.Resolve(name)
-		if err == nil {
-			return resolved, nil
-		}
-
-		if errors.Is(err, refstore.ErrReferenceNotFound) {
-			continue
-		}
-
-		return nil, fmt.Errorf("refstore: backend %d resolve: %w", i, err)
-	}
-
-	return nil, refstore.ErrReferenceNotFound
-}
-
-// ResolveFully resolves symbolic references through Resolve until detached.
-//
-// It intentionally does not call backend ResolveFully. This allows symbolic
-// references to cross backends in the chain.
-func (chain *Chain) ResolveFully(name string) (ref.Detached, error) {
-	cur := name
-
-	seen := map[string]struct{}{}
-	for {
-		if _, ok := seen[cur]; ok {
-			return ref.Detached{}, fmt.Errorf("refstore: symbolic reference cycle at %q", cur)
-		}
-
-		seen[cur] = struct{}{}
-
-		resolved, err := chain.Resolve(cur)
-		if err != nil {
-			return ref.Detached{}, err
-		}
-
-		switch resolved := resolved.(type) {
-		case ref.Detached:
-			return resolved, nil
-		case ref.Symbolic:
-			if resolved.Target == "" {
-				return ref.Detached{}, fmt.Errorf("refstore: symbolic reference %q has empty target", resolved.Name())
-			}
-
-			cur = resolved.Target
-		default:
-			return ref.Detached{}, fmt.Errorf("refstore: unsupported reference type %T", resolved)
-		}
-	}
-}
-
-// List lists references from every backend and deduplicates by ref name.
-//
-// First-seen wins, so earlier backends have precedence.
-func (chain *Chain) List(pattern string) ([]ref.Ref, error) {
-	var refs []ref.Ref
-
-	seen := map[string]struct{}{}
-
-	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
-		listed, err := backend.List(pattern)
-		if err != nil {
-			return nil, fmt.Errorf("refstore: backend %d list: %w", i, err)
-		}
-
-		for _, entry := range listed {
-			if entry == nil {
-				continue
-			}
-
-			name := entry.Name()
-			if _, ok := seen[name]; ok {
-				continue
-			}
-
-			seen[name] = struct{}{}
-
-			refs = append(refs, entry)
-		}
-	}
-
-	return refs, nil
-}
-
-// Shorten shortens a full reference name using the chain-visible namespace.
-func (chain *Chain) Shorten(name string) (string, error) {
-	refs, err := chain.List("")
-	if err != nil {
-		return "", err
-	}
-
-	names := make([]string, 0, len(refs))
-	found := false
-
-	for _, entry := range refs {
-		if entry == nil {
-			continue
-		}
-
-		full := entry.Name()
-
-		names = append(names, full)
-		if full == name {
-			found = true
-		}
-	}
-
-	if !found {
-		return "", refstore.ErrReferenceNotFound
-	}
-
-	return refstore.ShortenName(name, names), nil
-}
-
-// Close closes all backends and joins close errors.
-func (chain *Chain) Close() error {
-	var errs []error
-
-	for _, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
-		err := backend.Close()
-		if err != nil {
-			errs = append(errs, err)
-		}
-	}
-
-	return errors.Join(errs...)
 }
--- /dev/null
+++ b/refstore/chain/close.go
@@ -1,0 +1,21 @@
+package chain
+
+import "errors"
+
+// Close closes all backends and joins close errors.
+func (chain *Chain) Close() error {
+	var errs []error
+
+	for _, backend := range chain.backends {
+		if backend == nil {
+			continue
+		}
+
+		err := backend.Close()
+		if err != nil {
+			errs = append(errs, err)
+		}
+	}
+
+	return errors.Join(errs...)
+}
--- /dev/null
+++ b/refstore/chain/list.go
@@ -1,0 +1,44 @@
+package chain
+
+import (
+	"fmt"
+
+	"codeberg.org/lindenii/furgit/ref"
+)
+
+// List lists references from every backend and deduplicates by ref name.
+//
+// First-seen wins, so earlier backends have precedence.
+func (chain *Chain) List(pattern string) ([]ref.Ref, error) {
+	var refs []ref.Ref
+
+	seen := map[string]struct{}{}
+
+	for i, backend := range chain.backends {
+		if backend == nil {
+			continue
+		}
+
+		listed, err := backend.List(pattern)
+		if err != nil {
+			return nil, fmt.Errorf("refstore: backend %d list: %w", i, err)
+		}
+
+		for _, entry := range listed {
+			if entry == nil {
+				continue
+			}
+
+			name := entry.Name()
+			if _, ok := seen[name]; ok {
+				continue
+			}
+
+			seen[name] = struct{}{}
+
+			refs = append(refs, entry)
+		}
+	}
+
+	return refs, nil
+}
--- /dev/null
+++ b/refstore/chain/new.go
@@ -1,0 +1,10 @@
+package chain
+
+import "codeberg.org/lindenii/furgit/refstore"
+
+// New creates an ordered reference store chain.
+func New(backends ...refstore.Store) *Chain {
+	return &Chain{
+		backends: append([]refstore.Store(nil), backends...),
+	}
+}
--- /dev/null
+++ b/refstore/chain/resolve.go
@@ -1,0 +1,66 @@
+package chain
+
+import (
+	"errors"
+	"fmt"
+
+	"codeberg.org/lindenii/furgit/ref"
+	"codeberg.org/lindenii/furgit/refstore"
+)
+
+// Resolve resolves a reference from the first backend that has it.
+func (chain *Chain) Resolve(name string) (ref.Ref, error) {
+	for i, backend := range chain.backends {
+		if backend == nil {
+			continue
+		}
+
+		resolved, err := backend.Resolve(name)
+		if err == nil {
+			return resolved, nil
+		}
+
+		if errors.Is(err, refstore.ErrReferenceNotFound) {
+			continue
+		}
+
+		return nil, fmt.Errorf("refstore: backend %d resolve: %w", i, err)
+	}
+
+	return nil, refstore.ErrReferenceNotFound
+}
+
+// ResolveFully resolves symbolic references through Resolve until detached.
+//
+// It intentionally does not call backend ResolveFully. This allows symbolic
+// references to cross backends in the chain.
+func (chain *Chain) ResolveFully(name string) (ref.Detached, error) {
+	cur := name
+
+	seen := map[string]struct{}{}
+	for {
+		if _, ok := seen[cur]; ok {
+			return ref.Detached{}, fmt.Errorf("refstore: symbolic reference cycle at %q", cur)
+		}
+
+		seen[cur] = struct{}{}
+
+		resolved, err := chain.Resolve(cur)
+		if err != nil {
+			return ref.Detached{}, err
+		}
+
+		switch resolved := resolved.(type) {
+		case ref.Detached:
+			return resolved, nil
+		case ref.Symbolic:
+			if resolved.Target == "" {
+				return ref.Detached{}, fmt.Errorf("refstore: symbolic reference %q has empty target", resolved.Name())
+			}
+
+			cur = resolved.Target
+		default:
+			return ref.Detached{}, fmt.Errorf("refstore: unsupported reference type %T", resolved)
+		}
+	}
+}
--- /dev/null
+++ b/refstore/chain/shorten.go
@@ -1,0 +1,33 @@
+package chain
+
+import "codeberg.org/lindenii/furgit/refstore"
+
+// Shorten shortens a full reference name using the chain-visible namespace.
+func (chain *Chain) Shorten(name string) (string, error) {
+	refs, err := chain.List("")
+	if err != nil {
+		return "", err
+	}
+
+	names := make([]string, 0, len(refs))
+	found := false
+
+	for _, entry := range refs {
+		if entry == nil {
+			continue
+		}
+
+		full := entry.Name()
+
+		names = append(names, full)
+		if full == name {
+			found = true
+		}
+	}
+
+	if !found {
+		return "", refstore.ErrReferenceNotFound
+	}
+
+	return refstore.ShortenName(name, names), nil
+}
--