shithub: furgit

ref: dd027e1e5379019bfeffc48ff1274b5e05581ff3
dir: /refstore/chain/resolve.go/

View raw version
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.
//
//nolint:ireturn
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)
		}
	}
}