aboutsummaryrefslogtreecommitdiff
path: root/refstore/loose/resolve.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-02-21 11:25:50 +0800
committerGravatar Runxi Yu2026-02-21 11:25:50 +0800
commit5682de102bdd28741d0b7e371e8ee9bbd003d045 (patch)
tree9ce4b8c704c4a5d8b5f0f9537e19a2638e1ff871 /refstore/loose/resolve.go
parenttestgit: Add ref-related functions (diff)
signatureNo signature
refstore/loose: Add loose refs implementation
Diffstat (limited to 'refstore/loose/resolve.go')
-rw-r--r--refstore/loose/resolve.go87
1 files changed, 87 insertions, 0 deletions
diff --git a/refstore/loose/resolve.go b/refstore/loose/resolve.go
new file mode 100644
index 00000000..f54ab5a4
--- /dev/null
+++ b/refstore/loose/resolve.go
@@ -0,0 +1,87 @@
+package loose
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/ref"
+ "codeberg.org/lindenii/furgit/refstore"
+)
+
+// Resolve resolves a loose reference name to symbolic or detached form.
+func (store *Store) Resolve(name string) (ref.Ref, error) {
+ if name == "" {
+ return nil, refstore.ErrReferenceNotFound
+ }
+ resolved, err := store.resolveOne(name)
+ if err != nil {
+ return nil, err
+ }
+ return resolved, nil
+}
+
+// ResolveFully resolves symbolic references within the loose backend only.
+func (store *Store) ResolveFully(name string) (ref.Detached, error) {
+ if name == "" {
+ return ref.Detached{}, refstore.ErrReferenceNotFound
+ }
+
+ cur := name
+ seen := make(map[string]struct{})
+ for {
+ if _, ok := seen[cur]; ok {
+ return ref.Detached{}, fmt.Errorf("refstore/loose: symbolic reference cycle at %q", cur)
+ }
+ seen[cur] = struct{}{}
+
+ resolved, err := store.resolveOne(cur)
+ if err != nil {
+ return ref.Detached{}, err
+ }
+ switch resolved := resolved.(type) {
+ case ref.Detached:
+ return resolved, nil
+ case ref.Symbolic:
+ target := strings.TrimSpace(resolved.Target)
+ if target == "" {
+ return ref.Detached{}, fmt.Errorf("refstore/loose: symbolic reference %q has empty target", resolved.Name())
+ }
+ cur = target
+ default:
+ return ref.Detached{}, fmt.Errorf("refstore/loose: unsupported reference type %T", resolved)
+ }
+ }
+}
+
+// resolveOne resolves one loose ref file without symbolic recursion.
+func (store *Store) resolveOne(name string) (ref.Ref, error) {
+ data, err := store.root.ReadFile(name)
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return nil, refstore.ErrReferenceNotFound
+ }
+ return nil, err
+ }
+ line := strings.TrimSpace(string(data))
+ if strings.HasPrefix(line, "ref: ") {
+ target := strings.TrimSpace(line[len("ref: "):])
+ if target == "" {
+ return nil, fmt.Errorf("refstore/loose: symbolic reference %q has empty target", name)
+ }
+ return ref.Symbolic{
+ RefName: name,
+ Target: target,
+ }, nil
+ }
+ id, err := objectid.ParseHex(store.algo, line)
+ if err != nil {
+ return nil, fmt.Errorf("refstore/loose: invalid detached reference %q: %w", name, err)
+ }
+ return ref.Detached{
+ RefName: name,
+ ID: id,
+ }, nil
+}