From 845cd640384ed25ce3c18ade9aae37de2ed4c5e0 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Wed, 4 Mar 2026 12:52:53 +0800 Subject: refstore/chain: Split --- refstore/chain/chain.go | 157 +--------------------------------------------- refstore/chain/close.go | 21 +++++++ refstore/chain/list.go | 44 +++++++++++++ refstore/chain/new.go | 10 +++ refstore/chain/resolve.go | 66 +++++++++++++++++++ refstore/chain/shorten.go | 33 ++++++++++ 6 files changed, 175 insertions(+), 156 deletions(-) create mode 100644 refstore/chain/close.go create mode 100644 refstore/chain/list.go create mode 100644 refstore/chain/new.go create mode 100644 refstore/chain/resolve.go create mode 100644 refstore/chain/shorten.go (limited to 'refstore') diff --git a/refstore/chain/chain.go b/refstore/chain/chain.go index 9e04aeec..c20096d8 100644 --- a/refstore/chain/chain.go +++ b/refstore/chain/chain.go @@ -2,164 +2,9 @@ // of backends. package chain -import ( - "errors" - "fmt" - - "codeberg.org/lindenii/furgit/ref" - "codeberg.org/lindenii/furgit/refstore" -) +import "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...) -} diff --git a/refstore/chain/close.go b/refstore/chain/close.go new file mode 100644 index 00000000..440afb10 --- /dev/null +++ b/refstore/chain/close.go @@ -0,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...) +} diff --git a/refstore/chain/list.go b/refstore/chain/list.go new file mode 100644 index 00000000..e1594e95 --- /dev/null +++ b/refstore/chain/list.go @@ -0,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 +} diff --git a/refstore/chain/new.go b/refstore/chain/new.go new file mode 100644 index 00000000..8ea9b46c --- /dev/null +++ b/refstore/chain/new.go @@ -0,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...), + } +} diff --git a/refstore/chain/resolve.go b/refstore/chain/resolve.go new file mode 100644 index 00000000..66ba821e --- /dev/null +++ b/refstore/chain/resolve.go @@ -0,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) + } + } +} diff --git a/refstore/chain/shorten.go b/refstore/chain/shorten.go new file mode 100644 index 00000000..9e755de4 --- /dev/null +++ b/refstore/chain/shorten.go @@ -0,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 +} -- cgit v1.3.1-10-gc9f91